编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性、可扩展性、重用性、灵活性等多方面的挑战,设计模式是为了让程序,具有更好代码重用性、可读性、可扩展性、可靠性,使程序呈现高内聚,低耦合的特性。设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础,即设计模式为什么这样设计的依据。
第一章:开闭原则
1.1、原则介绍
开闭原则(open closed principle): 在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用质量的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。
1.2、错误示范
public class OpenClosed1 { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle());//+新增绘制三角形 } } //这是一个用于绘图的类 class GraphicEditor { //接收 Shape 对象,然后根据 type 来绘制不同的图形 public void drawShape(Shape s) { if (s.m_type == 1) drawRectangle(s); else if (s.m_type == 2) drawCircle(s); else if (s.m_type == 3)//+新增绘制三角形 drawTriangle(s); } //绘制矩形 public void drawRectangle(Shape r) { System.out.println("绘制矩形"); } //绘制圆形 public void drawCircle(Shape r) { System.out.println("绘制圆形"); } //+新增绘制三角形 public void drawTriangle(Shape r) { System.out.println("绘制三角形"); } } class Shape { int m_type; } //以前就写好的 class Rectangle extends Shape { Rectangle() { super.m_type = 1; } } //以前就写好的 class Circle extends Shape { Circle() { super.m_type = 2; } } //+新增绘制三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } }
结果:
绘制矩形
绘制圆形
绘制三角形
1.3、正确示范
public class OpenClosed2 { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle());//+新增绘制三角形 } } //这是一个用于绘图的类 class GraphicEditor { //接收 Shape 对象,调用 draw 方法 public void drawShape(Shape s) { s.draw(); } } 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 Triangle extends Shape { Triangle() { super.m_type = 3; } @Override public void draw() { System.out.println("绘制三角形"); } }
结果:
绘制矩形
绘制圆形
绘制三角形
第二章:里氏替换原则
2.1、原则介绍
里氏替换原则(liskov substitution principle): 子类可以扩展父类的功能,但不能改变原有父类的功能。
继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障。
在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合、组合、依赖来解决问题。
2.2、错误示范
public class LiskovSubstitution1 { public static void main(String[] args) { CalcCommon calcCommon = new CalcCommon(); calcCommon.add(100, 20); calcCommon.sub(100, 20); System.out.println("--------------------"); SubCalcCommon subCalcCommon = new SubCalcCommon(); subCalcCommon.add(100, 20); subCalcCommon.sub(100, 20); } } class CalcCommon { //通用加法运算 public void add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } //通用减法运算 public void sub(int a, int b) { System.out.println(a + "-" + b + "=" + (a - b)); } } class SubCalcCommon extends CalcCommon { //子类重写父类的方法可能会改变父类方法的意思,从而导致继承体系崩溃 @Override public void add(int a, int b) { System.out.println(a + "*" + b + "=" + (a * b)); } //子类重写父类的方法可能会改变父类方法的意思,从而导致继承体系崩溃 @Override public void sub(int a, int b) { System.out.println(a + "/" + b + "=" + (a / b)); } }
结果:
100+20=120
100-20=80
--------------------
100*20=2000
100/20=5
2.3、正确示范
public class LiskovSubstitution2 { public static void main(String[] args) { CalcCommon calcCommon = new CalcCommon(); calcCommon.add(100, 20); calcCommon.sub(100, 20); System.out.println("--------------------"); SubCalcCommon subCalcCommon = new SubCalcCommon(); subCalcCommon.add(100, 20); subCalcCommon.sub(100, 20); subCalcCommon.mul(100, 20); subCalcCommon.div(100, 20); } } class CalcCommon { //通用加法运算 public void add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } //通用减法运算 public void sub(int a, int b) { System.out.println(a + "-" + b + "=" + (a - b)); } } class SubCalcCommon extends CalcCommon { //子类特有乘法运算 public void mul(int a, int b) { System.out.println(a + "*" + b + "=" + (a * b)); } //子类特有除法运算 public void div(int a, int b) { System.out.println(a + "/" + b + "=" + (a / b)); } }
结果:
100+20=120
100-20=80
--------------------
100+20=120
100-20=80
100*20=2000
100/20=5
第三章:依赖倒置原则
3.1、原则介绍
依赖倒置原则(dependence inversion principle): 是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。
面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化,这大大降低了客户程序与实现细节的耦合度。
3.2、错误示范
public class DependenceInversion1 { public static void main(String[] args) { /** * 当你功能需求明确时,我们实现了接收qq用户信息的方法 * 但是,在某一天,市场上突然qq没有了,只有wx这一家了 * 你要是新增加wx用户信息接受的方法就必须修改receive * receive(QQ qq) --》 receive(WX wx) */ Client1 client = new Client1(); client.receive(new QQ()); } } class WX { public void getUserInfo(Integer uid) { System.out.println("WX getUserInfo " + uid); } } class QQ { public void getUserInfo(Integer uid) { System.out.println("QQ getUserInfo " + uid); } } class Client1 { public void receive(QQ qq) { qq.getUserInfo(774908833); } }
结果:
QQ getUserInfo 774908833
3.3、正确示范
public class DependenceInversion2 { public static void main(String[] args) { Client2 client = new Client2(); client.receive(new QQImpl()); client.receive(new WXImpl()); } } interface IReceive { public void getUserInfo(Integer uid); } class WXImpl implements IReceive { public void getUserInfo(Integer uid) { System.out.println("WX getUserInfo " + uid); } } class QQImpl implements IReceive { public void getUserInfo(Integer uid) { System.out.println("QQ getUserInfo " + uid); } } class Client2 { public void receive(IReceive iReceive) { iReceive.getUserInfo(774908833); } }
结果:
QQ getUserInfo 774908833
WX getUserInfo 774908833
第四章:接口隔离原则
4.1、原则介绍
接口隔离原则(interface segregation principle): 客户端不应该依赖它不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上。
4.2、错误示范
//A、B总的接口 interface InterfaceAll { void operation1(); void operation2(); void operation3(); } //实现类A只用InterfaceAll中的operation1、operation2方法,所以实现两个方法 class A implements InterfaceAll { @Override public void operation1() { System.out.println("A 实现了 operation1..."); } @Override public void operation2() { System.out.println("A 实现了 operation2..."); } @Override public void operation3() { //A用不到,但是还需要空实现 } } //实现类B只用InterfaceAll中的operation1、operation3方法,所以实现两个方法 class B implements InterfaceAll { @Override public void operation1() { System.out.println("B 实现了 operation1..."); } @Override public void operation2() { //B用不到,但是还需要空实现 } @Override public void operation3() { System.out.println("B 实现了 operation3..."); } }
4.3、正确示范
//接口Interface1 interface Interface1 { void operation1(); } //接口Interface2 interface Interface2 { void operation2(); } //接口Interface3 interface Interface3 { void operation3(); } //实现类A用Interface1中的operation1和Interface2中的operation2 class A implements Interface1, Interface2 { @Override public void operation1() { System.out.println("A 实现了 operation1..."); } @Override public void operation2() { System.out.println("A 实现了 operation2..."); } } //实现类B用Interface1中的operation1和Interface3中的operation3 class B implements Interface1, Interface3 { @Override public void operation1() { System.out.println("B 实现了 operation1..."); } @Override public void operation3() { System.out.println("B 实现了 operation3..."); } }
第五章:迪米特法则(最少知道原则)
5.1、原则介绍
迪米特法则(最少知道原则):如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
5.2、错误示范
Company 类中的 printEmployee 确实成功打印了人员信息,但是 Employee 类只作为局部变量出现在 printEmployee() 方法中,为 Company 类的间接朋友,违背了迪米特法则(只与直接的朋友通信)
/** * 雇员 */ public class Employee { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } /** * 部门经理 */ public class Manager { public List<Employee> getEmployees(String department) { List<Employee> employees = new ArrayList<>(); for (int i = 0; i < 10; i++) { Employee employee = new Employee(); // 雇员姓名 employee.setName(department + i); employees.add(employee); } return employees; } } /** * 公司 */ public class Company { private Manager manager = new Manager(); public void printEmployee(String name){ List<Employee> employees = manager.getEmployees(name); for (Employee employee : employees) { System.out.print(employee.getName() + ";"); } } }
5.3、正确示范
将 Company 类中打印雇员信息的方法放在 Manager 类中,Company 中只调用 Manager 中 printEmployee() 方法即可,Employee 类不再 Company 类中出现,但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
/** * 雇员 */ public class Employee { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } /** * 部门经理 */ public class Manager { public List<Employee> getEmployees(String department) { List<Employee> employees = new ArrayList<>(); for (int i = 0; i < 10; i++) { Employee employee = new Employee(); // 雇员姓名 employee.setName(department + i); employees.add(employee); } return employees; } public void printEmployee(String name){ List<Employee> employees = this.getEmployees(name); for (Employee employee : employees) { System.out.print(employee.getName() + ";"); } } } /** * 公司 */ public class Company { private Manager manager = new Manager(); public void printEmployee(String name){ manager.printEmployee(name); } }
第六章:单一职责原则
6.1、原则介绍
单一职责原则(single responsibility principle): 对类来说,即一个类应该只负责一项职责。如类 A 负责两个不同职责:职责 1,职责 2。当职责 1 需求变更而改变 A 时,可能造成职责 2 执行错误,所以需要将类 A 的粒度分解为 A1,A2。
单一职责原则可以降低类的复杂度,一个类只负责一项职责,提高类的可读性,可维护性,降低变更引起的风险。
6.2、错误示范
public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("汽车"); vehicle.run("飞机"); vehicle.run("轮船"); } } class Vehicle { public void run(String vehicle) { System.out.println(vehicle + " 在公路运行..."); } }
结果:
汽车 在公路运行...
飞机 在公路运行...
轮船 在公路运行...
6.3、正确示范
public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("汽车"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飞机"); WaterVehicle waterVehicle = new WaterVehicle(); waterVehicle.run("轮船"); } } class RoadVehicle { public void run(String vehicle) { System.out.println(vehicle + " 在公路运行..."); } } class AirVehicle { public void run(String vehicle) { System.out.println(vehicle + " 在天空运行..."); } } class WaterVehicle { public void run(String vehicle) { System.out.println(vehicle + " 在水上运行..."); } }
结果:
汽车 在公路运行...
飞机 在天空运行...
轮船 在水上运行...