▪ (SRP) The Single Responsibility Principle 单一责任原则
▪ (OCP) The Open-Closed Principle 开放-封闭原则
▪ (LSP) The Liskov Substitution Principle Liskov替换原则
▪ (DIP) The Dependency Inversion Principle 依赖转置原则
▪ (ISP) The Interface Segregation Principle 接口聚合原则
1. 单一责任原则
不应该有多于1个原因让你的ADT发生变化,否则就拆分开。
当粗粒度的类方法和属性太多,可以对这个类进行重构,拆分成粒度更细的类。
// 把用户类的注册登录,和发送邮件拆开。
// UserService类负责用户相关的操作,如用户注册、登录等
class UserService {
public void registerUser(String username, String password) {
// 实现用户注册逻辑
}
public boolean loginUser(String username, String password) {
// 实现用户登录逻辑
return false;
}
public void resetPassword(String username, String newPassword) {
// 实现重置用户密码逻辑
}
}
// EmailService类负责发送邮件
class EmailService {
public void sendEmail(String to, String subject, String body) {
// 实现发送邮件逻辑
}
}
// 用户管理类,负责用户的管理操作,如注册、登录等,并在需要时发送邮件通知用户
class UserManager {
private UserService userService;
private EmailService emailService;
public UserManager(UserService userService, EmailService emailService) {
this.userService = userService;
this.emailService = emailService;
}
public void registerUser(String username, String password) {
userService.registerUser(username, password);
emailService.sendEmail(username, "Welcome", "Welcome to our platform!");
}
public boolean loginUser(String username, String password) {
return userService.loginUser(username, password);
}
public void resetPassword(String username, String newPassword) {
userService.resetPassword(username, newPassword);
emailService.sendEmail(username, "Password Reset", "Your password has been reset successfully.");
}
}
2. 开放-封闭原则
对扩展性的开放:模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化
对修改的封闭:但模块自身的代码是不应被修改的。扩展模块行为的一般途径是修改模块的内部实现;如果一个模块不能被修改,那么它通常被认为是具有固定的行为。
当修改软件功能的时候,不能修改原有代码,只能新增代码。
考虑使用接口或抽象类,去代替掉一堆if-else
// 当需要新增一个新的图表类型时,
// 只需要创建新的图表类并实现Chart接口,而不需要修改Display类的代码
// 定义图表接口
interface Chart {
void display();
}
// 饼状图类
class PieChart implements Chart {
@Override
public void display() {
// 实现饼状图的绘制逻辑
}
}
// 柱状图类
class BarChart implements Chart {
@Override
public void display() {
// 实现柱状图的绘制逻辑
}
}
// 折线图类
class LineChart implements Chart {
@Override
public void display() {
// 实现折线图的绘制逻辑
}
}
// Display 类用于绘制各种类型的图表
class Display {
private Chart chart;
public void setChart(Chart chart) {
this.chart = chart;
}
public void display() {
if (chart != null) {
chart.display();
}
}
}
3. Liskov替换原则
子类型必须能够替换其基类型;派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异。
Liskov替换原则也是对开闭原则的补充,不仅适用于继承关系,还适用于实现关系的设计,常提到的 is a 关系是针对行为方式来说的,如果两个类的行为方式是不相容,那么就不应该使用继承,更好的方式是提取公共部分的方法来代替继承。
如:正方形/长方形问题。
如果
class Square extends Rectangle
Square
类继承自Rectangle
类,看起来似乎是符合“is-a”关系,即正方形是长方形的一种特殊情况。但问题在于,Square
类中重写了设置长度和宽度的方法,使得在设置正方形的边长时,也会同时修改另一个边,这违反了长方形的定义,因为长方形的长度和宽度可以是不同的。
Rectangle r = ...; // 返回具体类型对象
r.setWidth(5);
r.setLength(2);
assert(r.area() == 10);
当返回具体类型对象为 Suqare 类型,面积为 10 的断言就是失败。
改进:
interface Shape {
double calculateArea();
}
class Rectangle implements Shape {
protected double length;
protected double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
// get,set 略
@Override
public double calculateArea() {
return length * width;
}
}
class Square implements Shape {
private double sideLength;
public Square(double sideLength) {
this.sideLength = sideLength;
}
// get,set 略
@Override
public double calculateArea() {
return sideLength * sideLength;
}
}
4. 依赖转置原则
抽象的模块不应依赖于具体的模块。具体应依赖于抽象。
高层模块不应该依赖低层模块,应该共同依赖抽象。
delegation的时候, 要通过加装一层interface建立联系,而非具体子类
// 定义一个接口来抽象发送消息的行为
interface MessageSender {
void sendMessage(String message);
}
// 具体的消息发送类,实现了MessageSender接口
class EmailSender implements MessageSender {
@Override
public void sendMessage(String message) {
// 实现发送邮件的逻辑
System.out.println("Sending email: " + message);
}
}
// 具体的消息发送类,实现了MessageSender接口
class SMSSender implements MessageSender {
@Override
public void sendMessage(String message) {
// 实现发送短信的逻辑
System.out.println("Sending SMS: " + message);
}
}
// 高层模块,依赖于抽象MessageSender接口
class NotificationService {
private MessageSender messageSender;
// 通过构造函数注入依赖
public NotificationService(MessageSender messageSender) {
this.messageSender = messageSender;
}
// 业务逻辑方法,发送通知
public void sendNotification(String message) {
messageSender.sendMessage(message);
}
}
5. 接口聚合原则
客户端不应该依赖那些它不需要的接口。客户端应该只依赖它实际使用的方法。
减少定义大而全的接口,类所要实现的接口应该分解成多个接口,然后根据所需要的功能去实现,并且在使用到接口方法的地方,用对应的接口类型去声明,这样可以解除调用方与对象非相关方法的依赖关系。以防止暴露给客户端无相关的代码和方法,避免出现无关、不需要的部分。
// 将动物接口拆分成多个小的接口
// 避免interface Animal 下面同时包括 fly() 和 swim()
// 否则class Bird implements Animal 还要 @Override swim()
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// 实现了飞行接口的鸟类
class Bird implements Flyable {
@Override
public void fly() {
// 实现飞行的逻辑
}
}
// 客户端代码只需要使用飞行的动物
class Client {
public void process(Flyable flyingAnimal) {
flyingAnimal.fly();
}
}