SOLID设计原则
“SOLID”是五个面向对象程序设计和架构的基本原则的缩写,它有助于提高一个系统的可维护性和可扩展性。以下是这五个原则的简要介绍:
-
S — 单一职责原则 (Single Responsibility Principle, SRP)
这个原则认为一个类应该只有一个引起它变化的原因。换句话说,一个类应该只负责一项职责,这样可以使系统更容易理解和维护,也能降低类之间的耦合度。
反例:
在这个反例中,
UserHandler类既负责管理用户的信息,又负责将用户的信息保存到数据库中,这违反了单一职责原则。public class UserHandler { public void changeUserName(String userId, String newUserName) { // 修改用户的名字 } public void saveUserToDatabase(String userId) { // 将用户信息保存到数据库 } }正例:
在这个正例中,我们将
UserHandler类拆分成两个类,其中UserManager类负责管理用户的信息,而UserDatabase类负责将用户的信息保存到数据库中,从而满足单一职责原则。public class UserManager { public void changeUserName(String userId, String newUserName) { // 修改用户的名字 } } public class UserDatabase { public void saveUserToDatabase(String userId) { // 将用户信息保存到数据库 } }通过将不同的职责分配给不同的类,我们遵循了单一职责原则,从而使代码更加清晰和可维护。
-
O — 开闭原则 (Open/Closed Principle, OCP)
根据这个原则,软件实体(类、模块、函数等等)应该对扩展开放,对修改封闭。这意味着已有的代码可以被新的代码扩展,但是不应该修改已有的代码。
反例
在这个反例中,我们有一个
Greeter类,它有一个greet方法。每当我们想要添加一个新的问候消息时,我们都需要修改Greeter类,违反了开闭原则。public class Greeter { public String greet(String language) { if ("English".equals(language)) { return "Hello"; } else if ("Spanish".equals(language)) { return "Hola"; } else { return "Hello"; } } public static void main(String[] args) { Greeter greeter = new Greeter(); System.out.println(greeter.greet("English")); // Output: Hello } }正例
在这个正例中,我们使用一个接口和多个实现来遵循开闭原则。如果我们想要添加一个新的问候消息,我们只需要添加一个新的
Greeter实现,而不需要修改现有的代码。public interface Greeter { String greet(); } public class EnglishGreeter implements Greeter { @Override public String greet() { return "Hello"; } } public class SpanishGreeter implements Greeter { @Override public String greet() { return "Hola"; } } public class Main { public static void main(String[] args) { Greeter greeter = new EnglishGreeter(); System.out.println(greeter.greet()); // Output: Hello greeter = new SpanishGreeter(); System.out.println(greeter.greet()); // Output: Hola } }在这个正例中,我们可以看到
Greeter接口定义了一个greet方法,并且我们有多个类实现了这个接口,每个类提供了一种不同语言的问候。这样,我们可以轻松地添加更多的语言支持,而不需要修改现有的代码。 -
L — 里氏替换原则 (Liskov Substitution Principle, LSP)
这个原则是指程序中的对象应该可以被它的子类型所替换,而不会影响程序的正确性。它强调的是子类型应当能够替换掉它们的父类型。
反例
在这个反例中,我们违反了 LSP,因为当
Bird类型被Penguin类型替换时,它不能正常飞行。public class Bird { public void fly() { System.out.println("Bird is flying"); } } public class Penguin extends Bird { @Override public void fly() { throw new UnsupportedOperationException("Penguins can't fly"); } } public class Main { public static void main(String[] args) { Bird bird = new Penguin(); bird.fly(); // This will throw an exception, breaking the LSP } }在反例中,
Penguin类覆盖了Bird类的fly方法并抛出了一个异常,这违反了 LSP 原则,因为现在Penguin无法替换Bird而不改变程序的正确性。正因为如此,当我们尝试调用fly方法时,程序会抛出一个异常。正例
在这个正例中,
Rectangle和Square类均遵循了 LSP,因为Square是Rectangle的一个子类,并且可以替换Rectangle类而不影响程序的正确性。public class Rectangle { protected int width; protected int height; public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getArea() { return width * height; } } public class Square extends Rectangle { public void setWidth(int width) { this.width = width; this.height = width; } public void setHeight(int height) { this.width = height; this.height = height; } } public class Main { public static void main(String[] args) { Rectangle rect = new Square(); rect.setWidth(4); rect.setHeight(5); System.out.println("Area of rectangle: " + rect.getArea()); // Output: Area of rectangle: 25 } } -
I — 接口隔离原则 (Interface Segregation Principle, ISP)
该原则指出客户端不应该依赖它不使用的接口。换言之,一个类不应该被迫实现它不使用的方法,或者说,应该将臃肿的接口分割成更小的和更具体的接口,以满足客户端的特定需求。
反例
在这个反例中,我们有一个“臃肿”的接口,它包含了多个方法,而不是所有的实现类都需要所有的方法。
interface Worker { void work(); void eat(); } class HumanWorker implements Worker { public void work() { System.out.println("Human working..."); } public void eat() { System.out.println("Human eating..."); } } class RobotWorker implements Worker { public void work() { System.out.println("Robot working..."); } public void eat() { // Robots do not eat, this method is irrelevant for RobotWorker throw new UnsupportedOperationException("Robots do not eat"); } }在这个反例中,
RobotWorker类被迫实现一个它不需要的eat方法,违反了接口隔离原则。正例
在正例中,我们将原先的臃肿接口拆分成两个更小的接口,每个接口只包含相关的方法。
interface Worker { void work(); } interface Eater { void eat(); } class HumanWorker implements Worker, Eater { public void work() { System.out.println("Human working..."); } public void eat() { System.out.println("Human eating..."); } } class RobotWorker implements Worker { public void work() { System.out.println("Robot working..."); } }在这个正例中,
RobotWorker只实现了它真正需要的Worker接口,而没有实现Eater接口。这符合接口隔离原则,因为它避免了不必要的依赖关系。 -
D — 依赖倒置原则 (Dependency Inversion Principle, DIP)
该原则鼓励我们依赖于抽象而不是具体的实现。这有助于降低系统中不同模块之间的耦合度,并提高系统的灵活性。
该原则在依赖,依赖注入与依赖反转中有详细解释与说明。
反例
在下面的反例中,
HighLevelModule类直接依赖于一个具体的LowLevelModule类,这违反了依赖倒置原则。class LowLevelModule { void operation() { System.out.println("Low level operation"); } } class HighLevelModule { private final LowLevelModule lowLevelModule = new LowLevelModule(); void task() { lowLevelModule.operation(); } } public class Main { public static void main(String[] args) { HighLevelModule highLevelModule = new HighLevelModule(); highLevelModule.task(); } }正例
在下面的正例中,我们创建了一个
Module接口来实现依赖倒置原则。现在,HighLevelModule依赖于抽象,而不是一个具体的实现。interface Module { void operation(); } class LowLevelModule implements Module { @Override public void operation() { System.out.println("Low level operation"); } } class HighLevelModule { private final Module module; HighLevelModule(Module module) { this.module = module; } void task() { module.operation(); } } public class Main { public static void main(String[] args) { Module lowLevelModule = new LowLevelModule(); HighLevelModule highLevelModule = new HighLevelModule(lowLevelModule); highLevelModule.task(); } }在这个正例中,
HighLevelModule类依赖于Module接口而不是LowLevelModule类,因此我们成功地实现了依赖倒置原则。这种方式使得我们可以更灵活地扩展或修改低级模块的实现,而不会影响到高级模块。