一、UML 类图

以下类图使用 PlantUML(opens new window)绘制,更多语法及使用请参考:http://plantuml.com/

泛化关系 (Generalization)

用来描述继承关系,在 Java 中使用 extends 关键字。
设计模式简介 - 图1

  1. @startuml
  2. title Generalization
  3. class Vihical
  4. class Car
  5. class Trunck
  6. Vihical <|-- Car
  7. Vihical <|-- Trunck
  8. @enduml

实现关系 (Realization)

用来实现一个接口,在 Java 中使用 implements 关键字。
设计模式简介 - 图2

  1. @startuml
  2. title Realization
  3. interface MoveBehavior
  4. class Fly
  5. class Run
  6. MoveBehavior <|.. Fly
  7. MoveBehavior <|.. Run
  8. @enduml

聚合关系 (Aggregation)

表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。
设计模式简介 - 图3

  1. @startuml
  2. title Aggregation
  3. class Computer
  4. class Keyboard
  5. class Mouse
  6. class Screen
  7. Computer o-- Keyboard
  8. Computer o-- Mouse
  9. Computer o-- Screen
  10. @enduml

组合关系 (Composition)

和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
设计模式简介 - 图4

  1. @startuml
  2. title Composition
  3. class Company
  4. class DepartmentA
  5. class DepartmentB
  6. Company *-- DepartmentA
  7. Company *-- DepartmentB
  8. @enduml

关联关系 (Association)

表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
设计模式简介 - 图5

  1. @startuml
  2. title Association
  3. class School
  4. class Student
  5. School "1" - "n" Student
  6. @enduml

依赖关系 (Dependency)

和关联关系不同的是,依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式:

  • A 类是 B 类方法的局部变量;
  • A 类是 B 类方法的参数;
  • A 类向 B 类发送消息,从而影响 B 类发生变化。

设计模式简介 - 图6

  1. @startuml
  2. title Dependency
  3. class Vihicle {
  4. move(MoveBehavior)
  5. }
  6. interface MoveBehavior {
  7. move()
  8. }
  9. note "MoveBehavior.move()" as N
  10. Vihicle ..> MoveBehavior
  11. Vihicle .. N
  12. @enduml

二、设计原则

S.O.L.I.D

简写 全拼 中文翻译
SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
ISP The Interface Segregation Principle 接口分离原则
DIP The Dependency Inversion Principle 依赖倒置原则

1. 单一责任原则

修改一个类的原因应该只有一个。

换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。

如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。

应用实例
方案1[分析说明]

  1. public class SingleResponsibility1 {
  2. public static void main(String[] args) {
  3. Vehicle vehicle = new Vehicle();
  4. vehicle.run("汽车");
  5. vehicle.run("轮船");
  6. vehicle.run("飞机");
  7. }
  8. }
  9. /**
  10. * 方式1的分析
  11. * 1.在方式1的run方法中,违反了单一职责原则
  12. * 2.解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可
  13. */
  14. class Vehicle{
  15. public void run(String type){
  16. if ("汽车".equals(type)) {
  17. System.out.println(type + "在公路上运行...");
  18. } else if ("轮船".equals(type)) {
  19. System.out.println(type + "在水面上运行...");
  20. } else if ("飞机".equals(type)) {
  21. System.out.println(type + "在天空上运行...");
  22. }
  23. }
  24. }

方案2[分析说明]

  1. public class SingleResponsibility2 {
  2. public static void main(String[] args) {
  3. RoadVehicle roadVehicle = new RoadVehicle();
  4. roadVehicle.run("汽车");
  5. WaterVehicle waterVehicle = new WaterVehicle();
  6. waterVehicle.run("轮船");
  7. AirVehicle airVehicle = new AirVehicle();
  8. airVehicle.run("飞机");
  9. }
  10. }
  11. /**
  12. * 方案2的分析
  13. * 1.遵守单一职责原则
  14. * 2.但是这样做的改动很大,即将类分解,同时修改客户端
  15. * 3.改进:直接修改Vehicle类,改动的代码会比较少=>方案3
  16. */
  17. class RoadVehicle{
  18. public void run(String type){
  19. System.out.println(type + "在公路上运行...");
  20. }
  21. }
  22. class WaterVehicle{
  23. public void run(String type){
  24. System.out.println(type + "在水面上运行...");
  25. }
  26. }
  27. class AirVehicle{
  28. public void run(String type){
  29. System.out.println(type + "在天空上运行...");
  30. }
  31. }

方案3[分析说明]

  1. public class SingleResponsibility3 {
  2. public static void main(String[] args) {
  3. Vehicle2 vehicle = new Vehicle2();
  4. vehicle.run("汽车");
  5. vehicle.runWater("轮船");
  6. vehicle.runAir("飞机");
  7. }
  8. }
  9. /**
  10. * 方式3的分析
  11. * 1.这种修改方法没有对原来的类做大的修改,只是增加方法
  12. * 2.这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责
  13. */
  14. class Vehicle2{
  15. public void run(String type){
  16. System.out.println(type + "在公路上运行...");
  17. }
  18. public void runWater(String type){
  19. System.out.println(type + "在水面上运行...");
  20. }
  21. public void runAir(String type){
  22. System.out.println(type + "在天空上运行...");
  23. }
  24. }

2. 开放封闭原则

类应该对扩展开放,对修改关闭。

扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。

符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。

一个画图形的功能,类图设计如下:
设计模式简介 - 图7

  1. public class Ocp{
  2. public static void main(String[] args){
  3. GraphicEditor graphicEditor = new GraphicEditor();
  4. GraphicEditor.drawShape(new Rectangle());
  5. GraphicEditor.drawShape(new Circle());
  6. GraphicEditor.drawShape(new Triangle());
  7. }
  8. }
  9. class GraphicEditor {
  10. public void drawShape(Shape s) {
  11. if (s.m_type == 1) {
  12. drawRectangle(s);
  13. } else if (s.m_type == 2) {
  14. drawCircle(s);
  15. } else if (s.m_type == 3) {
  16. drawTriangle(s);
  17. }
  18. }
  19. public void drawRectangle(Shape r) {
  20. System.out.println("矩形");
  21. }
  22. public void drawCircle(Shape r) {
  23. System.out.println("圆形");
  24. }
  25. public void drawTriangle(Shape r) {
  26. System.out.println("三角形");
  27. }
  28. }
  29. class Shape {
  30. public int m_type;
  31. }
  32. class RectangleShape extends Shape {
  33. RectangleShape() {
  34. m_type = 1;
  35. }
  36. }
  37. class CircleShape extends Shape {
  38. CircleShape() {
  39. m_type = 2;
  40. }
  41. }
  42. class TriangleShape extends Shape {
  43. TriangleShape() {
  44. m_type = 3;
  45. }
  46. }

方式 1 的优缺点

  • 1)优点是比较好理解,简单易操作
  • 2)缺点是违反了设计模式的 OCP 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时喉,尽量不修改代码,或者尽可能少修改代码
  • 3)比如我们这时要新增加一个图形种类,我们需要做如下修改,修改的地方较多4)代码演示

方式 1 的改进的思路分析

把创建 Shape 类做成抽象类,并提供一个抽象的 draw 方法,让子类去实现即可

这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可

使用方的代码就不需要修改,满足了开闭原则

方式 2 来解决

1)方式 2 的设计方案:定义一个 Shape 抽象类

2)看代码示例

  1. public class Ocp{
  2. public static void main(String[] args){
  3. GraphicEditor graphicEditor = new GraphicEditor();
  4. GraphicEditor.drawShape(new Rectangle());
  5. GraphicEditor.drawShape(new Circle());
  6. GraphicEditor.drawShape(new Triangle());
  7. }
  8. }
  9. class GraphicEditor {
  10. public void drawShape(Shape s) {
  11. s.draw();
  12. }
  13. }
  14. abstract class Shape {
  15. int m_type;
  16. public abstract void draw();
  17. }
  18. class RectangleShape extends Shape {
  19. RectangleShape() {
  20. m_type = 1;
  21. }
  22. @Override
  23. public void draw() {
  24. System.out.println("矩形");
  25. }
  26. }
  27. class CircleShape extends Shape {
  28. CircleShape() {
  29. m_type = 2;
  30. }
  31. @Override
  32. public void draw() {
  33. System.out.println("圆形");
  34. }
  35. }
  36. class TriangleShape extends Shape {
  37. TriangleShape() {
  38. m_type = 3;
  39. }
  40. @Override
  41. public void draw() {
  42. System.out.println("三角形");
  43. }
  44. }

3)从方式 2 看,代码满足了 OCP 原则

3. 里氏替换原则

子类对象必须能够替换掉所有父类对象。

继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。

一个程序引出的问题和思考

先看个程序,思考下问题和解决思路

  1. public void test() {
  2. A a = new A();
  3. System.out.println("11-3=" + a.func1(11, 3));
  4. System.out.println("1-8=" + a.func1(1, 8));
  5. System.out.println("---------------------");
  6. B b = new B();
  7. System.out.println("11-3=" + b.func1(11, 3));
  8. System.out.println("1-8=" + b.func1(1, 8));
  9. System.out.println("11+3+9=" + b.func2(11, 3));
  10. }
  11. class A {
  12. //返回两个数的差
  13. public int func1(int num1, int num2) {
  14. return num1 - num2;
  15. }
  16. }
  17. class B extends A {
  18. @Override
  19. public int func1(int num1, int num2) {
  20. return num1 + num2;
  21. }
  22. //增加了一个新功能:完成两个数相加,然后和9求和
  23. public int func2(int num1, int num2) {
  24. return func1(num1, num2) + 9;
  25. }
  26. }

解决方法

1)我们发现原来运行正常的相减功能发生了错误。原因就是类 B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候

2)通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替

3)改进方案
设计模式简介 - 图8

  1. //创建一个更加基础的基类
  2. class Base {
  3. //将更基础的成员和方法写到Base类中
  4. }
  5. class A extends Base {
  6. //返回两个数的差
  7. public int func1(int num1, int num2) {
  8. return num1 - num2;
  9. }
  10. }
  11. class B extends Base {
  12. //如果B需要使用A类的方法,使用组合关系
  13. private A a;
  14. public int func1(int num1, int num2) {
  15. return num1 + num2;
  16. }
  17. //增加了一个新功能:完成两个数相加,然后和9求和
  18. public int func2(int num1, int num2) {
  19. return func1(num1, num2) + 9;
  20. }
  21. public int func3(int num1, int num2) {
  22. return this.a.func1(num1, num2);
  23. }
  24. }

4. 接口分离原则

不应该强迫客户依赖于它们不用的方法。

因此使用多个专门的接口比使用单一的总接口要好。
应用实例

  • 1)类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,请编写代码完成此应用实例
  • 2)看老师代码

设计模式简介 - 图9

  1. interface Interface1 {
  2. void operation1();
  3. void operation2();
  4. void operation3();
  5. void operation4();
  6. void operation5();
  7. }
  8. class B implements Interface1 {
  9. @Override
  10. public void operation1() {
  11. System.out.println("B 实现了 operation1");
  12. }
  13. @Override
  14. public void operation2() {
  15. System.out.println("B 实现了 operation2");
  16. }
  17. @Override
  18. public void operation3() {
  19. System.out.println("B 实现了 operation3");
  20. }
  21. @Override
  22. public void operation4() {
  23. System.out.println("B 实现了 operation4");
  24. }
  25. @Override
  26. public void operation5() {
  27. System.out.println("B 实现了 operation5");
  28. }
  29. }
  30. class D implements Interface1 {
  31. @Override
  32. public void operation1() {
  33. System.out.println("D 实现了 operation1");
  34. }
  35. @Override
  36. public void operation2() {
  37. System.out.println("D 实现了 operation2");
  38. }
  39. @Override
  40. public void operation3() {
  41. System.out.println("D 实现了 operation3");
  42. }
  43. @Override
  44. public void operation4() {
  45. System.out.println("D 实现了 operation4");
  46. }
  47. @Override
  48. public void operation5() {
  49. System.out.println("D 实现了 operation5");
  50. }
  51. }
  52. /**
  53. * A类通过接口Interface1依赖(使用)B类,但是只会用到1,2,3方法
  54. */
  55. class A {
  56. public void depend1(Interface1 i) {
  57. i.operation1();
  58. }
  59. public void depend2(Interface1 i) {
  60. i.operation2();
  61. }
  62. public void depend3(Interface1 i) {
  63. i.operation3();
  64. }
  65. }
  66. /**
  67. * C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
  68. */
  69. class C {
  70. public void depend1(Interface1 i) {
  71. i.operation1();
  72. }
  73. public void depend4(Interface1 i) {
  74. i.operation4();
  75. }
  76. public void depend5(Interface1 i) {
  77. i.operation5();
  78. }
  79. }

问题与改进

  • 1)类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,如果接口 Interface1 对于类 A 和类 C 来说不是最小接口,那么类 B 和类 D 必须去实现他们不需要的方法
  • 2)将接口 Interface1 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
  • 3)接口 Interface1 中出现的方法,根据实际情况拆分为三个接口
  • 4)代码实现

image.png

  1. interface Interface1 {
  2. void operation1();
  3. }
  4. interface Interface2 {
  5. void operation2();
  6. void operation3();
  7. }
  8. interface Interface3 {
  9. void operation4();
  10. void operation5();
  11. }
  12. class B implements Interface1, Interface2 {
  13. @Override
  14. public void operation1() {
  15. System.out.println("B 实现了 operation1");
  16. }
  17. @Override
  18. public void operation2() {
  19. System.out.println("B 实现了 operation2");
  20. }
  21. @Override
  22. public void operation3() {
  23. System.out.println("B 实现了 operation3");
  24. }
  25. }
  26. class D implements Interface1, Interface3 {
  27. @Override
  28. public void operation1() {
  29. System.out.println("D 实现了 operation1");
  30. }
  31. @Override
  32. public void operation4() {
  33. System.out.println("D 实现了 operation4");
  34. }
  35. @Override
  36. public void operation5() {
  37. System.out.println("D 实现了 operation5");
  38. }
  39. }
  40. /**
  41. * A类通过接口Interface1,Interface2依赖(使用)B类,但是只会用到1,2,3方法
  42. */
  43. class A {
  44. public void depend1(Interface1 i) {
  45. i.operation1();
  46. }
  47. public void depend2(Interface2 i) {
  48. i.operation2();
  49. }
  50. public void depend3(Interface2 i) {
  51. i.operation3();
  52. }
  53. }
  54. /**
  55. * C类通过接口Interface1,Interface3依赖(使用)D类,但是只会用到1,4,5方法
  56. */
  57. class C {
  58. public void depend1(Interface1 i) {
  59. i.operation1();
  60. }
  61. public void depend4(Interface3 i) {
  62. i.operation4();
  63. }
  64. public void depend5(Interface3 i) {
  65. i.operation5();
  66. }
  67. }

5. 依赖倒置原则

高层模块不应该依赖于低层模块,二者都应该依赖于抽象;
抽象不应该依赖于细节,细节应该依赖于抽象。

高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。

依赖于抽象意味着:

  • 任何变量都不应该持有一个指向具体类的指针或者引用;
  • 任何类都不应该从具体类派生;
  • 任何方法都不应该覆写它的任何基类中的已经实现的方法。

应用实例

1)请编程完成 Person 接收消息的功能

2)实现方案 1 + 分析说明

  1. /**
  2. * 方式1分析
  3. * 1.简单,比较容易想到
  4. * 2.如果我们获取的对象是微信,短信等等,则新增类,同时 Peron也要增加相应的接收方法
  5. * 3.解决思路:
  6. * 引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖
  7. * 因为Email,Weixin等等属于接收的范围,他们各自实现IReceiver接口就ok,这样我们就符号依赖倒转原则
  8. */
  9. class Email {
  10. public String getInfo() {
  11. return "电子邮件信息:Hello World!";
  12. }
  13. }
  14. class Person {
  15. public void receive(Email email) {
  16. System.out.println(email.getInfo());
  17. }
  18. }

3)实现方案 2 + 分析说明

  1. interface IReceiver {
  2. String getInfo();
  3. }
  4. class Email implements IReceiver {
  5. @Override
  6. public String getInfo() {
  7. return "电子邮件信息:Hello World!";
  8. }
  9. }
  10. class Weixin implements IReceiver {
  11. @Override
  12. public String getInfo() {
  13. return "微信消息:Hello World!";
  14. }
  15. }
  16. class ShortMessage implements IReceiver {
  17. @Override
  18. public String getInfo() {
  19. return "短信信息:Hello World!";
  20. }
  21. }
  22. class Person {
  23. public void receive(IReceiver receiver) {
  24. System.out.println(receiver.getInfo());
  25. }
  26. }

其他常见原则

除了上述的经典原则,在实际开发中还有下面这些常见的设计原则。

简写 全拼 中文翻译
LOD The Law of Demeter 迪米特法则
CRP The Composite Reuse Principle 合成复用原则
CCP The Common Closure Principle 共同封闭原则
SAP The Stable Abstractions Principle 稳定抽象原则
SDP The Stable Dependencies Principle 稳定依赖原则

1. 迪米特法则

迪米特法则又叫作最少知识原则(Least Knowledge Principle,简写 LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

2. 合成复用原则

尽量使用对象组合,而不是通过继承来达到复用的目的。

3. 共同封闭原则

一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。

4. 稳定抽象原则

最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟它的稳定性成正比。

5. 稳定依赖原则

包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。