设计模式01:设计模式的7大原则
七大设计原则概述
设计模式的七大设计原则如下
- 单一职责原则:一个类应该有且只有一个引起它变化的原因
- 接口隔离原则:类之间的依赖应该建立在最小接口上
- 依赖倒转原则:抽象不应该依赖于细节,细节应该依赖于抽象
- 里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
- 开闭原则:软件实体应该对扩展开放,对修改关闭
- 迪米特法则:一个对象应该对其他对象保持最少的了解
- 合成复用原则:尽量使用合成/聚合的方式替代继承
七大设计原则的具体示例
单一职责原则:一个类应该有且只有一个引起它变化的原因
基本介绍
严格来讲,单一职责原则是对类而言的,要求一个类应该只负责一项职责,当某个职责的需求发生更改时,不会影响其他职责的实现.
但当逻辑足够简单时,可以在类级别放弃单一职责原则,而在方法级别上遵守单一职责原则.
应用实例
下面例子演示了如何使用单一职责原则重构代码
-
违反单一职责原则的代码
Vehicle
类的run()
方法既负责陆上交通工具的运行,又负责空中交通工具的运行,违反单一职责原则// 交通工具类 class Vehicle { public void run(String vehicleName) { System.out.println(vehicleName + "在公路上运行..."); } } public class SingleResponsibility { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托车"); // 摩托车在公路上运行... vehicle.run("汽车"); // 汽车在公路上运行... vehicle.run("飞机"); // 飞机在公路上运行... } }
-
在类级别遵守单一职责原则
我们将不同种类的交通工具抽象成不同的类,这样在类级别上遵守了单一职责原则
// 陆地交通工具 class RoadVehicle { public void run(String vehicleName) { System.out.println(vehicleName + "在公路运行"); } } // 空中交通工具 class AirVehicle { public void run(String vehicleName) { System.out.println(vehicleName + "在空中运行"); } } // 水上交通工具 class WaterVehicle { public void run(String vehicleName) { System.out.println(vehicleName + "在水上运行"); } } public class SingleResponsibility { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); new RoadVehicle().run("摩托车"); new RoadVehicle().run("汽车"); new AirVehicle().run("飞机"); } }
这样虽然遵守了单一职责原则,但是在类级别进行了改动,改动较大.
-
在方法级别遵守单一职责原则
因为这个例子中的逻辑足够简单,为避免在类级别修造成的破坏,我们可以尝试将不同的职责赋给不同的方法来实现,在方法级别遵守了单一职责原则.
// 交通工具类,其不同方法旅行不同职责 class Vehicle { public void runOnRoad(String vehicleName) { System.out.println(vehicleName + "在公路运行"); } public void runOnAir(String vehicleName) { System.out.println(vehicleName + "在空中运行"); } public void runOnWater(String vehicleName) { System.out.println(vehicleName + "在水上运行"); } } public class SingleResponsibility { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.runOnRoad("汽车"); vehicle.runOnWater("轮船"); vehicle.runOnAir("飞机"); } }
接口隔离原则:类之间的依赖应该建立在最小接口上
基本介绍
为避免一个类实现其用不到的方法,类之间的依赖应该建立在最小接口上.若所依赖的不是最小接口,应将其加以拆分.
应用实例
下面例子演示了如何使用接口隔离原则重构代码
-
下面例子不满足接口隔离原则
// 公共接口PublicInterface,不满足接口隔离原则 interface PublicInterface { void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } // 公共接口PublicInterface的实现类A,需要实现公共接口定义的所有方法 class A implements PublicInterface { public void operation1() {System.out.println("A 实现了 operation1"); } public void operation2() {System.out.println("A 实现了 operation2"); } public void operation3() {System.out.println("A 实现了 operation3"); } public void operation4() {System.out.println("A 实现了 operation4"); } public void operation5() {System.out.println("A 实现了 operation5"); } } // 公共接口PublicInterface的实现类B,需要实现公共接口定义的所有方法 class B implements PublicInterface { public void operation1() {System.out.println("B 实现了 operation1"); } public void operation2() {System.out.println("B 实现了 operation2"); } public void operation3() {System.out.println("B 实现了 operation3"); } public void operation4() {System.out.println("B 实现了 operation4"); } public void operation5() {System.out.println("B 实现了 operation5"); } } // 通过公共接口依赖(使用B)类,但只用到方法1,2,3 class C { public void depend1(PublicInterface i) {i.operation1(); } public void depend2(PublicInterface i) {i.operation2(); } public void depend3(PublicInterface i) {i.operation3(); } } // 通过公共接口依赖(使用B)类,但只用到方法1,4,5 class D { public void depend1(PublicInterface i) {i.operation1(); } public void depend4(PublicInterface i) {i.operation4(); } public void depend5(PublicInterface i) {i.operation5(); } } public class Segregation { public static void main(String[] args) { // 类C通过公共接口依赖(使用A)类,但只用到方法1,2,3 C c = new C(); c.depend1(new A()); c.depend2(new A()); c.depend3(new A()); // 类D通过公共接口依赖(使用B)类,但只用到方法1,4,5 D d = new D(); d.depend1(new B()); d.depend4(new B()); d.depend5(new B()); } }
其UML类图如下:
-
我们对上边例子加以改进,将原公共接口
PublicInterface
拆分为三个最小接口Interface1
,Interface2
,Interface3
,类C,类D分别通过最小接口依赖类A,类B.// 最小接口1 interface Interface1 { void operation1(); } // 最小接口2 interface Interface2 { void operation2(); void operation3(); } // 最小接口3 interface Interface3 { void operation4(); void operation5(); } class A implements Interface1, Interface2 { public void operation1() {System.out.println("A 实现了 operation1"); } public void operation2() {System.out.println("A 实现了 operation2"); } public void operation3() {System.out.println("A 实现了 operation3"); } } class B implements Interface1, Interface3 { public void operation1() {System.out.println("B 实现了 operation1"); } public void operation4() {System.out.println("B 实现了 operation4"); } public void operation5() {System.out.println("B 实现了 operation5"); } } // 类C通过nterface1和Interface2依赖类A class C { public void depend1(Interface1 i) {i.operation1(); } public void depend2(Interface2 i) {i.operation2(); } public void depend3(Interface2 i) {i.operation3(); } } // 类D通过nterface1和Interface4依赖类B class D { public void depend1(Interface1 i) {i.operation1(); } public void depend4(Interface3 i) {i.operation4(); } public void depend5(Interface3 i) {i.operation5(); } }
其UML类图如下:
依赖倒转原则:抽象不应依赖细节,细节应该依赖抽象
基本介绍
依赖倒转原则的核心思想即为面向接口编程,所有的底层模块尽量都要有对应的抽象类或接口,变量类型也尽量声明为抽象类或接口,并引用其实现类对象.
应用实例
下面例子演示了如何使用依赖倒转原则重构代码
-
下面例子不满足依赖倒转原则.类
Person
依赖的是具体的Email
类和WeiXin
类,这样receive()
方法就依赖于细节(具体的信息种类)了.class Email { public String getInfo() {return "电子邮件信息:hello,world"; } } class WeiXin { public String getInfo() {return "微信信息:hello,world"; } } class Person { public void receive(Email email) {System.out.println(email.getInfo()); } public void receive(WeiXin weiXin) {System.out.println(weiXin.getInfo()); } } public class DependecyInversion { public static void main(String[] args) { Person person = new Person(); person.receive(new Email()); person.receive(new WeiXin()); } }
-
对上述程序进行修改,为
Email
类和WeiXin
类增加抽象接口IMessage
,并让Person
类依赖IMessage
接口.//定义接口 interface IMessage { public String getInfo(); } class Email implements IMessage { public String getInfo() {return "电子邮件信息:hello,world"; } } class WeiXin implements IMessage { public String getInfo() {return "微信信息:hello,world"; } } class Person { // 在这里依赖接口,而非具体实现类 public void receive(IMessage message) { System.out.println(message.getInfo()); } }
里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
基本介绍
里氏替换原则要求所有引用基类的地方必须能透明地使用其子类的对象.也就是要求我们在继承时在子类中尽量不要重写父类的方法,而尝试通过聚合,组合,依赖来解决问题.
应用实例
-
下面的例子中,子类无意间重写了父类的方法并更改其行为,这会使得类的调用者产生困惑
class Calculator { // 返回两个数的差 public int subtract(int num1, int num2) { return num1 - num2; } } class CalculatorPlus extends Calculator { // 无意间重写了Calculator的方法 public int subtract(int num1, int num2) { return num1 + num2; } public int add(int num1, int num2) { return num1 + num2; } } public class Liskov { public static void main(String[] args) { Calculator calculator = null; calculator = new Calculator(); System.out.println("11 - 3 = " + calculator.subtract(11, 3)); // 输出8 calculator = new CalculatorPlus(); System.out.println("11 - 3 = " + calculator.subtract(11, 3)); // 输出14 } }
-
可以将继承关系改为组合关系
class Calculator { CalculatorPlus calculatorPlus = new CalculatorPlus(); public int subtract(int num1, int num2) {return num1 - num2; } public int add(int num1, int num2) {return calculatorPlus.add(num1, num2); } } class CalculatorPlus { public int add(int num1, int num2) {return num1 + num2; } } public class Liskov { public static void main(String[] args) { Calculator calculator = null; calculator = new Calculator(); System.out.println("11 - 3 = " + calculator.subtract(11, 3)); System.out.println("11 + 3 = " + calculator.add(11, 3)); } }
开闭原则:软件实体应该对扩展开放,对修改关闭
基本介绍
开闭原则是我们使用设计模式的终极目标,即:软件实体应对扩展开放,对修改关闭.当需求变化时,尽量通过扩展代码而非修改原有代码的方式实现变化.
应用实例
-
下面例子模拟了一个画图程序,可以画出各种形状.在这个程序中,要想增加一种新的绘制团非常麻烦,需要我们修改暴露给使用者的
GraphicDrawer
类.// 各种图形类 abstract class Shape { int m_type; } class Rectangle extends Shape { Rectangle() {super.m_type = 1; } } class Circle extends Shape { Circle() {super.m_type = 2; } } // 暴露给用户的绘图类 class GraphicDrawer { public void drawShape(Shape s) { if (s.m_type == 1) drawRectangle(s); else if (s.m_type == 2) drawCircle(s); } public void drawRectangle(Shape r) {System.out.println("绘制矩形"); } public void drawCircle(Shape r) {System.out.println("绘制圆形"); } }
-
我们将绘图的职责赋给具体的图形类,而暴露给用户的
GraphicDrawer
类只负责调用图像类的绘图逻辑即可.// 各种图形类 abstract class Shape { int m_type; // 抽象的绘图方法,由子类负责实现 public abstract void draw(); } class Rectangle extends Shape { Rectangle() {super.m_type = 1; } @Override public void draw() {System.out.println("绘制矩形"); } } class Circle extends Shape { Circle() {super.m_type = 2; } @Override public void draw() {System.out.println("绘制圆形"); } } // 暴露给用户的绘图类 class GraphicDrawer { // 调用接收的Shape对象的draw()方法 public void drawShape(Shape s) { s.draw(); } }
迪米特法则:一个对象应该对其他对象保持最少的了解
一个对象应该对其他对象保持最少的了解.需要做到:一个类只与直接的朋友(成员变量,方法参数,方法返回值)通信,陌生的类最好不要以局部变量的形式出现在类的内部.
合成复用原则:尽量使用合成/聚合的方式替代继承
继承是一种比较重的耦合关系了,很多时候可以尝试使用合成/聚合的方式来替代.例如上面里氏替换原则中的例子也可以用来说明合成复用法则.