设计模式七大原则
- 开闭原则(Open Close Principle)
- 单一职责原则(Single Responsibility Principle)
- 接口隔离原则(Interface Segregation Principle)
- 依赖倒置原则(Dependence Inversion Principle)
- 里氏替换原则(Liskov Substitution Principle)
- 迪米特法则(最少知道原则)(Law Of Demeter)
- 合成复用原则(Composite Reuse Principle)
开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放(提供方可扩展),对修改关闭(使用方不用修改)。强调的是用抽象构建框架,用实现扩展细节。
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
- 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。
- 实现开闭原则的核心思想是面向抽象编程。
违反开闭原则:
// 违反开闭原则,新增图形使用方 DrawEditor 需要修改
public class Shape {
int type;
}
public class Rect extends Shape {
public Rect() {
super.type = 1;
}
}
public class Circle extends Shape {
public Circle() {
super.type = 2;
}
}
// 作为使用方,新增图形需要修改
public class DrawEditor {
public void drawShape(Shape shape) {
if (shape.type == 1) {
drawRect();
} else if (shape.type == 2) {
drawCircle();
}
}
private void drawRect() {
System.out.println("绘制矩形");
}
private void drawCircle() {
System.out.println("绘制圆形");
}
}
遵循开闭原则:
// 遵循开闭原则,新增图形使用方 DrawEditor 不需要修改
abstract class Shape {
int type;
public abstract void draw();
}
public class Rect extends Shape {
public Rect() {
super.type = 1;
}
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
public class Circle extends Shape {
public Circle() {
super.type = 2;
}
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
// 作为使用方,新增图形不需要修改
public class DrawEditor {
public void drawShape(Shape shape) {
shape.draw();
}
}
单一职责原则
定义:不要存在多于一个导致类变更的原因;保持一个类/接口/方法只负责一项职责。
- 降低类的复杂度,一个类只负责一项职责
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 大部分情况应当遵守单一职责原则,当类逻辑相当简单,可以在类层面违反单一职责原则;当类中方法数量足够少,可以在方法级别违反单一职责原则
// 类级别遵守单一职责原则
public class AnimalCat {
public void eat(String name) {
System.out.println(name + "吃猫粮");
}
}
public class AnimalDog {
public void eat(String name) {
System.out.println(name + "吃狗粮");
}
}
public class AnimalRabbit {
public void eat(String name) {
System.out.println(name + "吃兔粮");
}
}
// 方法级别遵守单一职责原则
public class Animal {
public void catEat(String name) {
System.out.println(name + "吃猫粮");
}
public void dogEat(String name) {
System.out.println(name + "吃狗粮");
}
public void rabbitEat(String name) {
System.out.println(name + "吃兔粮");
}
}
接口隔离原则
定义:用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
- 一个类对应一个类的依赖应该建立在最小的接口上
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少
- 适度原则,一定要适度
违反接口隔离原则:
// 违反接口隔离原则示例;接口臃肿,方法过多
// 使用者 StudentA 只需要 wakeUp()/sleep()/schoolStart()/schoolEnd();但其实现类 Student 要实现接口的全部方法
// 使用者 WorkerA 只需要 wakeUp()/sleep()/workStart()/workEnd();但其实现类 Worker 要实现接口的全部方法
public interface Person {
void wakeUp();
void sleep();
void schoolStart();
void schoolEnd();
void workStart();
void workEnd();
}
public class Student implements Person {
@Override
public void wakeUp() { }
@Override
public void sleep() { }
@Override
public void schoolStart() { }
@Override
public void schoolEnd() { }
@Override
public void workStart() { }
@Override
public void workEnd() { }
}
public class Worker implements Person {
@Override
public void wakeUp() { }
@Override
public void sleep() { }
@Override
public void schoolStart() { }
@Override
public void schoolEnd() { }
@Override
public void workStart() { }
@Override
public void workEnd() { }
}
public class StudentA {
public void wakeUp(Student student) {
student.wakeUp();
}
public void sleep(Student student) {
student.sleep();
}
public void schoolStart(Student student) {
student.schoolStart();
}
public void schoolEnd(Student student) {
student.schoolEnd();
}
}
public class WorkerA {
public void wakeUp(Worker worker) {
worker.wakeUp();
}
public void sleep(Worker worker) {
worker.sleep();
}
public void workStart(Worker worker) {
worker.workStart();
}
public void workEnd(Worker worker) {
worker.workEnd();
}
}
遵守接口隔离原则:
// 遵守接口隔离原则示例;拆分接口,接口中的方法尽量少,使用者不用依赖它不需要的接口
public interface Life {
void wakeUp();
void sleep();
}
public interface School {
void schoolStart();
void schoolEnd();
}
public interface Work {
void workStart();
void workEnd();
}
public class Student implements Life, School {
@Override
public void wakeUp() { }
@Override
public void sleep() { }
@Override
public void schoolStart() { }
@Override
public void schoolEnd() { }
}
public class Worker implements Life, Work {
@Override
public void wakeUp() { }
@Override
public void sleep() { }
@Override
public void workStart() { }
@Override
public void workEnd() { }
}
public class StudentA {
public void wakeUp(Student student) {
student.wakeUp();
}
public void sleep(Student student) {
student.sleep();
}
public void schoolStart(Student student) {
student.schoolStart();
}
public void schoolEnd(Student student) {
student.schoolEnd();
}
}
public class WorkerA {
public void wakeUp(Worker worker) {
worker.wakeUp();
}
public void sleep(Worker worker) {
worker.sleep();
}
public void workStart(Worker worker) {
worker.workStart();
}
public void workEnd(Worker worker) {
worker.workEnd();
}
}
依赖倒置原则
定义:依赖抽象,而不是依赖细节。
- 高层模块不应该依赖低层模块,两者都应该依赖于抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒置的中心思想是面向接口编程
- 相较于细节的多变性,抽象要稳定的多;以抽象为基础搭建的架构,比以细节为基础搭建的架构要稳定的多。抽象指的是接口或抽象类,细节指的是具体的实现类。
- 使用接口或抽象类的目的是制定规范,而不涉及任何具体的操作,展现细节的任务交给实现类完成
依赖倒置原则注意事项:
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性会更好
- 变量的声明类型尽量是抽象类或接口,利于程序扩展和优化
- 继承时遵守里氏替换原则
违反依赖倒置原则:
// 违反依赖倒置原则示例;没有依赖抽象,而是依赖细节
public class Car {
public void run() { }
}
public class Subway {
public void run() { }
}
public class Driver {
public void operationCar(Car car) {
car.run();
}
public void operationSubway(Subway subway) {
subway.run();
}
}
遵守依赖倒置原则:
// 遵守依赖倒置原则示例;依赖抽象,没有依赖细节
public interface Vehicle {
void run();
}
public class Car implements Vehicle {
@Override
public void run() { }
}
public class Subway implements Vehicle {
@Override
public void run() { }
}
public class Driver {
public void operation(Vehicle vehicle) {
vehicle.run();
}
}
依赖关系传递的三种方式
- 通过接口传递
public interface IDriver {
// 在接口方法中传递
void operation(IVehicle vehicle);
}
public interface IVehicle {
void run();
}
public class Driver implements IDriver {
@Override
public void operation(IVehicle vehicle) {
vehicle.run();
}
}
- 通过构造方法传递
public interface IDriver {
void operation();
}
public interface IVehicle {
void run();
}
public class Driver implements IDriver {
private IVehicle vehicle;
// 在构造方法中传递
public Driver(IVehicle vehicle) {
this.vehicle = vehicle;
}
@Override
public void operation() {
this.vehicle.run();
}
}
- 通过 setter 方法传递
public interface IDriver {
void operation();
}
public interface IVehicle {
void run();
}
public class Driver implements IDriver {
private IVehicle vehicle;
// 在 setter 方法中传递
public void setVehicle(IVehicle vehicle) {
this.vehicle = vehicle;
}
@Override
public void operation() {
this.vehicle.run();
}
}
里氏替换原则
定义:如果调用一个父类的方法可以成功运行,那么替换成子类调用也应该完全可以运行。
- 子类可以扩展父类的功能,但不能改变父类原有的功能。
- 子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
违反里式替换原则:
public class Calculator {
public int addition(int a, int b) {
return a + b;
}
}
public class Count extends Calculator {
// 违反里式替换原则,重写了父类的方法,改变了父类原有的功能,导致出错
@Override
public int addition(int a, int b) {
return a * b;
}
public int subtraction(int a, int b) {
return a - b;
}
}
里式替换原则告诉我们,继承实际上让两个类耦合性增强,在适当情况下,可以通过聚合、组合、依赖来解决问题。
遵守里式替换原则:
// 遵守里式替换原则
// 创建更加基础的基类,都继承自基类
public class Base {
}
public class Calculator extends Base {
public int addition(int a, int b) {
return a + b;
}
}
public class Count extends Base {
// 与 Calculator 不再是继承关系,而是组合关系;降低耦合性
private final Calculator calculator = new Calculator();
public int addition(int a, int b) {
return calculator.addition(a, b);
}
public int subtraction(int a, int b) {
return a - b;
}
}
迪米特法则(最少知道原则)
定义:一个对象对其他对象保持最少的了解,尽量降低类与类之间的耦合,强调只与直接的朋友交流。
- 迪米特法则又称:只与直接的朋友交流。
- 直接的朋友:成员变量、方法的参数、方法的返回值。
- 方法中的局部变量不是直接的朋友。因此,陌生的类最好不要以局部变量的形式出现在类内部。
- 类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对令一个类的影响也越大。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
- 也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量的将逻辑封装在类的内部,对外除了提供的 public 方法,不对外泄露任何信息。
违反迪米特法则:
public class HighSchoolStudent {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class SchoolStudent {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class HighSchoolManager {
public List<HighSchoolStudent> getAllStudent() {
List<HighSchoolStudent> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
HighSchoolStudent student = new HighSchoolStudent();
student.setId("HighSchoolStudent:" + i);
list.add(student);
}
return list;
}
}
public class SchoolManager {
public List<SchoolStudent> getAllStudent() {
List<SchoolStudent> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SchoolStudent student = new SchoolStudent();
student.setId("SchoolStudent:" + i);
list.add(student);
}
return list;
}
public void printAllStudent(HighSchoolManager manager) {
// HighSchoolStudent 不是 SchoolManager 的直接朋友
// HighSchoolStudent 以局部变量的方式出现在 SchoolManager 类内部
// 违反迪米特法则,需要优化
List<HighSchoolStudent> highStudents = manager.getAllStudent();
for (HighSchoolStudent student : highStudents) {
System.out.println(student.getId());
}
List<SchoolStudent> middleStudents = this.getAllStudent();
for (SchoolStudent student : middleStudents) {
System.out.println(student.getId());
}
}
}
遵守迪米特法则:
public class HighSchoolStudent {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class SchoolStudent {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class HighSchoolManager {
public List<HighSchoolStudent> getAllStudent() {
List<HighSchoolStudent> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
HighSchoolStudent student = new HighSchoolStudent();
student.setId("HighSchoolStudent:" + i);
list.add(student);
}
return list;
}
public void printAllStudent() {
List<HighSchoolStudent> highStudents = this.getAllStudent();
for (HighSchoolStudent student : highStudents) {
System.out.println(student.getId());
}
}
}
public class SchoolManager {
public List<SchoolStudent> getAllStudent() {
List<SchoolStudent> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SchoolStudent student = new SchoolStudent();
student.setId("SchoolStudent:" + i);
list.add(student);
}
return list;
}
public void printAllStudent(HighSchoolManager manager) {
// 遵守迪米特法则,只与直接朋友交流
manager.printAllStudent();
List<SchoolStudent> middleStudents = this.getAllStudent();
for (SchoolStudent student : middleStudents) {
System.out.println(student.getId());
}
}
}
合成复用原则
定义:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的。
合成复用原则又叫组合/聚合复用原则,它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点。
- 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
- 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
- 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。
- 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
- 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。
- 合成复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
违反合成复用原则;使用继承关系,达到软件复用的目的。
public class Car {
public void run() {
System.out.println("Car run");
}
}
public class Driver extends Car {
@Override
public void run() {
super.run();
}
}
遵守合成复用原则;使用依赖关系,达到软件复用的目的。
public class Car {
public void run() {
System.out.println("Car run");
}
}
public class Driver {
public void run(Car car) {
car.run();
}
}
遵守合成复用原则;使用聚合关系,达到软件复用的目的。
public class Car {
public void run() {
System.out.println("Car run");
}
}
public class Driver {
private Car car;
public void setCar(Car car) {
this.car = car;
}
public void run() {
this.car.run();
}
}
遵守合成复用原则;使用组合关系,达到软件复用的目的。
public class Car {
public void run() {
System.out.println("Car run");
}
}
public class Driver {
private Car car = new Car();
public void run() {
this.car.run();
}
}