23种设计模式

1、概述

  • 编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好的:
    • 代码重用性 (相同功能的代码,不用多次编写)。
    • 可读性 (编程规范性, 便于其他程序员的阅读和理解)。
    • 可扩展性 (当需要增加新的功能时,非常的方便,称为可维护)。
    • 可靠性 (当我们增加新的功能后,对原来的功能没有影响)。
    • 使程序呈现高内聚,低耦合的特性。

2、七大原则

  • 设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(设计模式为什么这样设计的依据)。
  • 设计模式常用的七大原则有:
    • 单一职责原则。
    • 接口隔离原则。
    • 依赖倒转(倒置)原则。
    • 里氏替换原则。
    • 开闭原则。
    • 迪米特法则。
    • 合成复用原则。
  • 设计原则核心思想:
    • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
    • 针对接口编程,而不是针对实现编程。
    • 为了交互对象之间的松耦合设计而努力。

2.1 单一职责原则

  • 此原则是对于类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2。

  • 以交通工具案例分析:

    • 方式一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class SingleResponsibility {
      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 + " 在公路上运行....");
      }
      }
      • 在run方法中,违反了单一职责原则。
      • 解决的方案非常的简单,即根据交通工具运行方法不同,分解成不同类即可。
    • 方式二:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      public class SingleResponsibility {
      public static void main(String[] args) {
      RoadVehicle roadVehicle = new RoadVehicle();
      roadVehicle.run("摩托车");
      roadVehicle.run("汽车");

      AirVehicle airVehicle = new AirVehicle();

      airVehicle.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 + "水中运行");
      }
      }
      • 此方式遵守单一职责原则,但是这样做的改动很大,即将类分解,同时修改客户端。
      • 改进方式是直接修改Vehicle类,改动的代码会比较少。
    • 方式三:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public class SingleResponsibility {
      public static void main(String[] args) {
      Vehicle vehicle = new Vehicle();
      vehicle.run("汽车");
      vehicle.runWater("轮船");
      vehicle.runAir("飞机");
      }
      }

      class Vehicle {
      public void run(String vehicle) {
      System.out.println(vehicle + " 在公路上运行....");
      }

      public void runAir(String vehicle) {
      System.out.println(vehicle + " 在天空上运行....");
      }

      public void runWater(String vehicle) {
      System.out.println(vehicle + " 在水中行....");
      }
      }
      • 这种修改方法没有对原来的类做大的修改,只是增加方法。
      • 这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责。
  • 单一职责原则注意事项和细节:

    • 降低类的复杂度,一个类只负责一项职责。
    • 提高类的可读性,可维护性。
    • 降低变更引起的风险。
    • 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则。

2.2 接口隔离原则

  • 此原则指客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

  • 例如类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法,图示如下:

    image-20210608214917411
    • 代码实现如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      interface Interface1 {
      void operation1();

      void operation2();

      void operation3();

      void operation4();

      void operation5();
      }

      class B implements Interface1 {
      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");
      }
      }

      class D implements Interface1 {
      public void operation1() {
      System.out.println("D 实现了 operation1");
      }

      public void operation2() {
      System.out.println("D 实现了 operation2");
      }

      public void operation3() {
      System.out.println("D 实现了 operation3");
      }

      public void operation4() {
      System.out.println("D 实现了 operation4");
      }

      public void operation5() {
      System.out.println("D 实现了 operation5");
      }
      }

      //A 类通过接口Interface1 依赖(使用) B类,但是只会用到1,2,3方法
      class A {
      public void depend1(Interface1 i) {
      i.operation1();
      }

      public void depend2(Interface1 i) {
      i.operation2();
      }

      public void depend3(Interface1 i) {
      i.operation3();
      }
      }

      //C 类通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法
      class C {
      public void depend1(Interface1 i) {
      i.operation1();
      }

      public void depend4(Interface1 i) {
      i.operation4();
      }

      public void depend5(Interface1 i) {
      i.operation5();
      }
      }
  • 应传统方法的问题和使用接口隔离原则改进:

    • 将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

    • 接口Interface1中出现的方法,根据实际情况拆分为三个接口。

      image-20210608220003334
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      interface Interface1 {
      void operation1();

      }

      interface Interface2 {
      void operation2();

      void operation3();
      }

      interface Interface3 {
      void operation4();

      void operation5();
      }

      class B implements Interface1, Interface2 {
      public void operation1() {
      System.out.println("B 实现了 operation1");
      }

      public void operation2() {
      System.out.println("B 实现了 operation2");
      }

      public void operation3() {
      System.out.println("B 实现了 operation3");
      }

      }

      class D implements Interface1, Interface3 {
      public void operation1() {
      System.out.println("D 实现了 operation1");
      }

      public void operation4() {
      System.out.println("D 实现了 operation4");
      }

      public void operation5() {
      System.out.println("D 实现了 operation5");
      }
      }

      // A 类通过接口Interface1,Interface2 依赖(使用) B类,但是只会用到1,2,3方法
      class A {
      public void depend1(Interface1 i) {
      i.operation1();
      }

      public void depend2(Interface2 i) {
      i.operation2();
      }

      public void depend3(Interface2 i) {
      i.operation3();
      }
      }

      // C 类通过接口Interface1,Interface3 依赖(使用) D类,但是只会用到1,4,5方法
      class C {
      public void depend1(Interface1 i) {
      i.operation1();
      }

      public void depend4(Interface3 i) {
      i.operation4();
      }

      public void depend5(Interface3 i) {
      i.operation5();
      }
      }

2.3 依赖倒转原则

  • 此原则是指高层模块不应该依赖低层模块,二者都应该依赖其抽象。即抽象不应该依赖细节,细节应该依赖抽象。

  • 中心思想是面向接口编程。

  • 基于此设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。

  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

  • 以接收消息的案例分析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class DependencyInversion {
    public static void main(String[] args) {
    Person person = new Person();
    person.receive(new Email());
    }
    }

    class Email {
    public String getInfo() {
    return "电子邮件信息: hello,world";
    }
    }

    //完成Person接收消息的功能
    //方式1分析
    //1. 简单,比较容易想到
    //2. 如果我们获取的对象是 微信,短信等等,则新增类,同时Person也要增加相应的接收方法
    //3. 解决思路:引入一个抽象的接口IReceiver, 表示接收者, 这样Person类与接口IReceiver发生依赖
    // 因为Email, WeiXin 等等属于接收的范围,他们各自实现IReceiver 接口就ok, 这样我们就符号依赖倒转原则
    class Person {
    public void receive(Email email) {
    System.out.println(email.getInfo());
    }
    }
    • 虽然简单,但是如果我们获取的对象是微信,短信等,则新增类的同时Person也要增加相应的接收方法。
    • 解决方式是引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖,接着Email, WeiXin等接收端各自实现IReceiver接口即可,即符合依赖倒转原则。
  • 使用依赖倒转原则改进的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class DependencyInversion {
    public static void main(String[] args) {
    //客户端无需改变
    Person person = new Person();
    person.receive(new Email());
    person.receive(new WeiXin());
    }
    }

    interface IReceiver {
    public String getInfo();
    }

    class Email implements IReceiver {
    public String getInfo() {
    return "电子邮件信息: hello,world";
    }
    }

    class WeiXin implements IReceiver {
    public String getInfo() {
    return "微信信息: hello,ok";
    }
    }

    class Person {
    public void receive(IReceiver receiver) {
    System.out.println(receiver.getInfo());
    }
    }
  • 依赖倒转原则的注意事项和细节:

    • 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好。
    • 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
    • 继承时遵循里氏替换原则。
  • 依赖关系传递的三种方式

    • ①接口传递。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      public class DependencyPass {
      public static void main(String[] args) {
      ChangHong changHong = new ChangHong();
      OpenAndClose openAndClose = new OpenAndClose();
      openAndClose.open(changHong);
      }
      }

      interface IOpenAndClose {
      public void open(ITV tv);
      }

      interface ITV {
      public void play();
      }

      class ChangHong implements ITV {
      @Override
      public void play() {
      System.out.println("长虹电视机,打开");
      }

      }

      class OpenAndClose implements IOpenAndClose {
      public void open(ITV tv) {
      tv.play();
      }
      }
    • ②构造方法传递。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      public class DependencyPass {
      public static void main(String[] args) {
      ChangHong changHong = new ChangHong();
      OpenAndClose openAndClose = new OpenAndClose(changHong);
      openAndClose.open();
      }
      }

      interface IOpenAndClose {
      public void open();
      }

      interface ITV {
      public void play();
      }

      class ChangHong implements ITV {
      @Override
      public void play() {
      System.out.println("长虹电视机,打开");
      }
      }

      class OpenAndClose implements IOpenAndClose {
      public ITV tv

      public OpenAndClose(ITV tv) {
      this.tv = tv;
      }

      public void open() {
      this.tv.play();
      }
      }
    • ③setter方式传递。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      public class DependencyPass {
      public static void main(String[] args) {
      ChangHong changHong = new ChangHong();
      OpenAndClose openAndClose = new OpenAndClose();
      openAndClose.setTv(changHong);
      openAndClose.open();
      }
      }

      // 方式3 , 通过setter方法传递
      interface IOpenAndClose {
      public void open();

      public void setTv(ITV tv);
      }

      interface ITV {
      public void play();
      }

      class OpenAndClose implements IOpenAndClose {
      private ITV tv;

      public void setTv(ITV tv) {
      this.tv = tv;
      }

      public void open() {
      this.tv.play();
      }
      }

      class ChangHong implements ITV {
      @Override
      public void play() {
      System.out.println("长虹电视机,打开");
      }
      }

2.4 里氏替换原则

  • 此原则指如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。

  • 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。

  • 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。

  • 案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class Liskov {
    public static void main(String[] args) {
    A a = new A();
    System.out.println("11-3=" + a.func1(11, 3));
    System.out.println("1-8=" + a.func1(1, 8));
    B b = new B();
    System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出11-3
    System.out.println("1-8=" + b.func1(1, 8));
    System.out.println("11+3+9=" + b.func2(11, 3));
    }
    }

    class A {
    // 返回两个数的差
    public int func1(int num1, int num2) {
    return num1 - num2;
    }
    }

    // B类继承了A,并增加了一个新功能:完成两个数相加,然后和9求和
    class B extends A {
    //重写了A类的方法, 可能是无意识
    public int func1(int a, int b) {
    return a + b;
    }

    public int func2(int a, int b) {
    return func1(a, b) + 9;
    }
    }
    • 我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。
    • 用的做法是原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替。
  • 使用里氏替换原则改进的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public class Liskov {
    public static void main(String[] args) {
    A a = new A();
    System.out.println("11-3=" + a.func1(11, 3));
    System.out.println("1-8=" + a.func1(1, 8));
    System.out.println("-----------------------");
    B b = new B(); //因为B类不再继承A类,因此调用者,不会再func1是求减法 //调用完成的功能就会很明确
    System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出11+3
    System.out.println("1+8=" + b.func1(1, 8));
    System.out.println("11+3+9=" + b.func2(11, 3));
    //使用组合仍然可以使用到A类相关方法
    System.out.println("11-3=" + b.func3(11, 3));//这里本意是求出11-3
    }
    }

    //创建一个更加基础的基类
    class Base {
    //把更加基础的方法和成员写到Base类
    }

    class A extends Base {
    // 返回两个数的差
    public int func1(int num1, int num2) {
    return num1 - num2;
    }
    }

    // B类继承了A
    // 增加了一个新功能:完成两个数相加,然后和9求和
    class B extends Base {
    //如果B需要使用A类的方法,使用组合关系
    private A a = new A();

    //这里,重写了A类的方法, 可能是无意识
    public int func1(int a, int b) {
    return a + b;
    }

    public int func2(int a, int b) {
    return func1(a, b) + 9;
    }

    //我们仍然想使用A的方法
    public int func3(int a, int b) {
    return this.a.func1(a, b);
    }
    }

2.5 开闭原则

  • 此原则指一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。

  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

  • 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。

  • 案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    public class Ocp {
    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;
    }
    }
    • 优点是比较好理解,简单易操作。
    • 缺点是违反了设计模式的ocp原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。比如我们这时要新增加一个图形种类 三角形,需要修改的地方较多。
  • 使用开闭原则改进的代码如下:

    • 把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,使用方的代码就不需要修改,即满足了开闭原则。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      public class Ocp {
      public static void main(String[] args) {
      GraphicEditor graphicEditor = new GraphicEditor();
      graphicEditor.drawShape(new Rectangle());
      graphicEditor.drawShape(new Circle());
      graphicEditor.drawShape(new Triangle());
      graphicEditor.drawShape(new OtherGraphic());
      }
      }

      //这是一个用于绘图的类 [使用方]
      class GraphicEditor {
      //接收Shape对象,调用draw方法
      public void drawShape(Shape s) {
      s.draw();
      }
      }

      //Shape类,基类
      abstract class Shape {
      public abstract void draw();
      //抽象方法
      }

      class Rectangle extends Shape {
      @Override
      public void draw() {
      System.out.println(" 绘制矩形 ");
      }
      }

      class Circle extends Shape {
      @Override
      public void draw() {
      System.out.println(" 绘制圆形 ");
      }
      }

      //新增画三角形
      class Triangle extends Shape {
      @Override
      public void draw() {
      System.out.println(" 绘制三角形 ");
      }
      }

      //新增一个图形
      class OtherGraphic extends Shape {
      @Override
      public void draw() {
      System.out.println(" 绘制其它图形 ");
      }
      }

2.6 迪米特法则

  • 此法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息。

  • 一个对象应该对其他对象保持最少的了解。

  • 类与类关系越密切,耦合度越大。

  • 迪米特法则还有个更简单的定义:只与直接的朋友通信。

  • 直接的朋友是指每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

  • 案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    public class Demeter1 {
    public static void main(String[] args) {
    //创建了一个SchoolManager对象
    SchoolManager schoolManager = new SchoolManager();
    //输出学院的员工id和学校总部的员工信息
    schoolManager.printAllEmployee(new CollegeManager());
    }
    }

    //学校总部员工类
    class Employee {
    private String id;

    public void setId(String id) {
    this.id = id;
    }

    public String getId() {
    return id;
    }
    }

    //学院的员工类
    class CollegeEmployee {
    private String id;

    public void setId(String id) {
    this.id = id;
    }

    public String getId() {
    return id;
    }
    }

    //管理学院员工的管理类
    class CollegeManager {
    //返回学院的所有员工
    public List<CollegeEmployee> getAllEmployee() {
    List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
    for (int i = 0; i < 10; i++) {
    //这里我们增加了10个员工
    CollegeEmployee emp = new CollegeEmployee();
    emp.setId("学院员工id= " + i);
    list.add(emp);
    }
    return list;
    }
    }

    //学校管理类
    //SchoolManager类的直接朋友类有:Employee、CollegeManager
    //CollegeEmployee是以局部变量方式出现在SchoolManager,所以不是SchoolManager类的直接朋友,而是一个陌生类,这样违背了迪米特法则
    class SchoolManager {
    //返回学校总部的员工
    public List<Employee> getAllEmployee() {
    List<Employee> list = new ArrayList<Employee>();
    for (int i = 0; i < 5; i++) {
    //这里我们增加了5个员工到 list
    Employee emp = new Employee();
    emp.setId("学校总部员工id= " + i);
    list.add(emp);
    }
    return list;
    }

    //该方法完成输出学校总部和学院员工信息(id)
    void printAllEmployee(CollegeManager sub) {
    //获取到学院员工
    List<CollegeEmployee> list1 = sub.getAllEmployee();
    System.out.println("------------学院员工------------");
    for (CollegeEmployee e : list1) {
    System.out.println(e.getId());
    }
    //获取到学校总部员工
    List<Employee> list2 = this.getAllEmployee();
    System.out.println("------------学校总部员工------------");
    for (Employee e : list2) {
    System.out.println(e.getId());
    }
    }
    }
    • 前面设计的问题在于SchoolManager中,CollegeEmployee类并不是SchoolManager类的直接朋友 。
    • 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。
  • 使用迪米特法则改进的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    //客户端
    public class Demeter1 {
    public static void main(String[] args) {
    System.out.println("~~~使用迪米特法则的改进~~~");
    //创建了一个SchoolManager对象
    SchoolManager schoolManager = new SchoolManager();
    //输出学院的员工id 和学校总部的员工信息
    schoolManager.printAllEmployee(new CollegeManager());
    }
    }

    //学校总部员工类
    class Employee {
    private String id;

    public void setId(String id) {
    this.id = id;
    }

    public String getId() {
    return id;
    }
    }

    //学院的员工类
    class CollegeEmployee {
    private String id;

    public void setId(String id) {
    this.id = id;
    }

    public String getId() {
    return id;
    }
    }

    //管理学院员工的管理类
    class CollegeManager {
    //返回学院的所有员工
    public List<CollegeEmployee> getAllEmployee() {
    List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
    for (int i = 0; i < 10; i++) {
    //这里我们增加了10个员工
    CollegeEmployee emp = new CollegeEmployee();
    emp.setId("学院员工id= " + i);
    list.add(emp);
    }
    return list;
    }

    //输出学院员工的信息
    public void printEmployee() {
    //获取到学院员工
    List<CollegeEmployee> list1 = getAllEmployee();
    System.out.println("------------学院员工------------");
    for (CollegeEmployee e : list1) {
    System.out.println(e.getId());
    }
    }
    }

    //学校管理类
    class SchoolManager {
    //返回学校总部的员工
    public List<Employee> getAllEmployee() {
    List<Employee> list = new ArrayList<Employee>();
    for (int i = 0; i < 5; i++) {
    //这里我们增加了5个员工到 list
    Employee emp = new Employee();
    emp.setId("学校总部员工id= " + i);
    list.add(emp);
    }
    return list;
    }

    //该方法完成输出学校总部和学院员工信息(id)
    void printAllEmployee(CollegeManager sub) {
    //将输出学院的员工方法封装到CollegeManager
    sub.printEmployee();
    //获取到学校总部员工
    List<Employee> list2 = this.getAllEmployee();
    System.out.println("------------学校总部员工------------");
    for (Employee e : list2) {
    System.out.println(e.getId());
    }
    }
    }
  • 迪米特法则注意事项和细节:

    • 核心是降低类之间的耦合。
    • 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系。

2.7 合成复用原则

  • 该原则是指尽量使用合成、聚合的方式,而不是使用继承。

    image-20210608224322889

3、设计模式

  • 设计模式分为三种类型,共23种:
    • ①创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
    • ②结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
    • ③行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者 模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

3.1 创建型设计模式

  • 创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
  • 创建型模式分为以下几种。
    • 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
    • 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
    • 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
    • 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
    • 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
  • 以上5种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式,

3.1.1 单例模式

  • 单例设计模式就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

  • 比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。

  • 单例模式有八种方式:

    • 饿汉式(静态常量)。
    • 饿汉式(静态代码块)。
    • 懒汉式(线程不安全)。
    • 懒汉式(线程安全,同步方法)。
    • 懒汉式(线程安全,同步代码块)。
    • 双重检查。
    • 静态内部类。
    • 枚举。
  • 单例模式在JDK应用的源码:

    image-20210608230405884
  • 单例模式注意事项和细节说明:

    • 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
    • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。
    • 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。
  • 步骤如下:

    • 构造器私有化 (防止new)。
    • 类的内部创建对象。
    • 向外暴露一个静态的公共方法getInstance。

3.1.1.1 饿汉式(静态常量)

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class SingletonTest {
    public static void main(String[] args) {
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
    }

    class Singleton {
    //1. 构造器私有化, 外部能new
    private Singleton() {
    }

    //2.本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
    return instance;
    }
    }
    • 优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。
    • 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
    • 这种方式基于classloder机制避免了多线程的同步问题,不过instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果。
    • 结论:这种单例模式可用,可能造成内存浪费。

3.1.1.2 饿汉式(静态代码块)

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class SingletonTest {
    public static void main(String[] args) {
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
    }

    class Singleton {
    //1. 构造器私有化, 外部能new
    private Singleton() {
    }

    //2.本类内部创建对象实例
    private static Singleton instance;

    static {
    // 在静态代码块中,创建单例对象
    instance = new Singleton();
    }

    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
    return instance;
    }
    }
    • 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
    • 结论:这种单例模式可用,但是可能造成内存浪费。

3.1.1.3 懒汉式(线程不安全)

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class SingletonTest {
    public static void main(String[] args) {
    System.out.println("懒汉式1 , 线程不安全~");
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
    }

    class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    //即懒汉式
    public static Singleton getInstance() {
    if (instance == null) {
    instance = new Singleton();
    }
    return instance;
    }
    }
    • 起到了Lazy Loading的效果,但是只能在单线程下使用。
    • 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
    • 结论:在实际开发中,不要使用这种方式。

3.1.1.4 懒汉式(线程安全,同步方法)

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class SingletonTest {
    public static void main(String[] args) {
    System.out.println("懒汉式2 , 线程安全~");
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
    }

    // 懒汉式(线程安全,同步方法)
    class Singleton {
    private static Singleton instance;

    private Singleton() {}

    //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
    //即懒汉式
    public static synchronized Singleton getInstance() {
    if(instance == null) {
    instance = new Singleton();
    }
    return instance;
    }
    }
    • 解决了线程不安全问题。
    • 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。
    • 结论:在实际开发中,不推荐使用这种方式。

3.1.1.5 懒汉式(线程不安全,同步代码块)

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class SingletonTest {
    public static void main(String[] args) {
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }

    }

    class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
    if(instance == null) {
    synchronized (Singleton.class){
    instance = new Singleton();
    }
    }
    return instance;
    }
    }
    • 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块。
    • 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
    • 结论:在实际开发中,不能使用这种方式。

3.1.1.6 双重检查

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class SingletonTest {
    public static void main(String[] args) {
    System.out.println("双重检查");
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
    }

    // 懒汉式(线程安全,同步方法)
    class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
    //同时保证了效率, 推荐使用
    public static Singleton getInstance() {
    if(instance == null) {
    synchronized (Singleton.class) {
    if(instance == null) {
    instance = new Singleton();
    }
    }
    }
    return instance;
    }
    }
    • Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。
    • 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步。
    • 线程安全;延迟加载;效率较高。
    • 结论:在实际开发中,推荐使用这种单例设计模式。

3.1.1.7 静态内部类

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class SingletonTest {
    public static void main(String[] args) {
    System.out.println("使用静态内部类完成单例模式");
    Singleton instance = Singleton.getInstance();
    Singleton instance2 = Singleton.getInstance();
    System.out.println(instance == instance2); // true
    System.out.println("instance.hashCode=" + instance.hashCode());
    System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
    }

    // 静态内部类完成, 推荐使用
    class Singleton {
    //构造器私有化
    private Singleton() {}

    //写一个静态内部类,该类中有一个静态属性 Singleton
    private static class SingletonInstance {
    private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
    public static Singleton getInstance() {
    return SingletonInstance.INSTANCE;
    }
    }
    • 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
    • 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
    • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
    • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
    • 结论:推荐使用。

3.1.1.8 枚举

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class SingletonTest {
    public static void main(String[] args) {
    Singleton instance = Singleton.INSTANCE;
    Singleton instance2 = Singleton.INSTANCE;
    System.out.println(instance == instance2);

    System.out.println(instance.hashCode());
    System.out.println(instance2.hashCode());

    instance.sayOK();
    }
    }

    //使用枚举,可以实现单例, 推荐
    enum Singleton {
    INSTANCE; //属性
    public void sayOK() {
    System.out.println("ok~");
    }
    }
    • 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
    • 这种方式是Effective Java作者Josh Bloch 提倡的方式。
    • 结论:推荐使用。

3.1.2 工厂设计模式

  • 工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的”创建与使用相分离”的特点。
  • 按实际业务场景划分,工厂模式有3种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。

3.1.2.1 简单工厂模式

  • 我们把被创建的对象称为”产品”,把创建产品的对象称为”工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫”简单工厂模式”。

  • 在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。

  • 简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。简单工厂模式不在 GoF 23 种设计模式之列。

  • 简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了”开闭原则”。

  • 简单工厂模式的主要角色如下:

    • 简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
    • 抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
    • 具体产品(ConcreteProduct):是简单工厂模式的创建目标。
    image-20210608231758650代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class Client {
    //抽象产品
    public interface Product {
    void show();
    }

    //具体产品:ProductA
    static class ConcreteProduct1 implements Product {
    public void show() {
    System.out.println("具体产品1显示...");
    }
    }

    //具体产品:ProductB
    static class ConcreteProduct2 implements Product {
    public void show() {
    System.out.println("具体产品2显示...");
    }
    }

    final class Const {
    static final int PRODUCT_A = 0;
    static final int PRODUCT_B = 1;
    static final int PRODUCT_C = 2;
    }

    static class SimpleFactory {
    public static Product makeProduct(int kind) {
    switch (kind) {
    case Const.PRODUCT_A:
    return new ConcreteProduct1();
    case Const.PRODUCT_B:
    return new ConcreteProduct2();
    }
    return null;
    }
    }
    }
    • 优点:
      • 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
      • 客户端无需知道所创建具体产品的类名,只需知道参数即可。
      • 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
    • 缺点:
      • 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
      • 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
      • 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
      • 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
    • 应用场景:
      • 对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
  • 举一个生产披萨的项目案例:

    image-20210609105543359
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class PizzaTest {
    public static void main(String[] args) {
    PizzaFactory pizzaFactory = new PizzaFactory();
    CheesePizza cheesePizza = (CheesePizza) pizzaFactory.producePizza("Cheese");
    GreekPizza greek = (GreekPizza) pizzaFactory.producePizza("Greek");
    }
    }

    abstract class Pizza {
    public abstract void produce();
    }

    class CheesePizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作奶酪披萨...");
    }
    }

    class GreekPizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作希腊披萨...");
    }
    }

    class PizzaFactory {
    public Pizza producePizza(String type) {
    if (type.equals("Cheese")) {
    return new CheesePizza();
    } else if (type.equals("Greek")) {
    return new GreekPizza();
    }
    return null;
    }
    }

3.1.2.2 工厂方法模式

  • “工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。即定义了一个创建对象的抽象方法,由子类决定要实例化的类,工厂方法模式将对象的实例化推迟到子类。

  • 工厂方法模式的主要角色如下:

    • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
    • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
    • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
    • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    //抽象产品:提供了产品的接口
    interface Product {
    public void show();
    }

    //具体产品1:实现抽象产品中的抽象方法
    class ConcreteProduct1 implements Product {
    public void show() {
    System.out.println("具体产品1显示...");
    }
    }

    //具体产品2:实现抽象产品中的抽象方法
    class ConcreteProduct2 implements Product {
    public void show() {
    System.out.println("具体产品2显示...");
    }
    }

    //抽象工厂:提供了厂品的生成方法
    interface AbstractFactory {
    public Product newProduct();
    }

    //具体工厂1:实现了厂品的生成方法
    class ConcreteFactory1 implements AbstractFactory {
    public Product newProduct() {
    System.out.println("具体工厂1生成-->具体产品1...");
    return new ConcreteProduct1();
    }
    }

    //具体工厂2:实现了厂品的生成方法
    class ConcreteFactory2 implements AbstractFactory {
    public Product newProduct() {
    System.out.println("具体工厂2生成-->具体产品2...");
    return new ConcreteProduct2();
    }
    }
    • 优点:
      • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
      • 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
      • 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
    • 缺点:
      • 类的个数容易过多,增加复杂度。
      • 增加了系统的抽象性和理解难度。
      • 抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
    • 应用场景:
      • 客户只知道创建产品的工厂名,而不知道具体的产品名。如TCL电视工厂、海信电视工厂等。
      • 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
      • 客户不关心创建产品的细节,只关心产品的品牌。
  • 举一个生产披萨的项目案例:

    image-20210609111318139
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    public class PizzaTest {
    public static void main(String[] args) {
    CheesePizzaFactory cheesePizzaFactory = new CheesePizzaFactory();
    GreekPizzaFactory greekPizzaFactory = new GreekPizzaFactory();
    CheesePizza cheesePizza = (CheesePizza) cheesePizzaFactory.producePizza();
    GreekPizza greekPizza = (GreekPizza) greekPizzaFactory.producePizza();
    }
    }

    abstract class Pizza {
    public abstract void produce();
    }

    class CheesePizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作奶酪披萨...");
    }
    }

    class GreekPizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作希腊披萨...");
    }
    }

    abstract class PizzaFactory {
    public abstract Pizza producePizza();
    }

    class CheesePizzaFactory extends PizzaFactory {
    @Override
    public Pizza producePizza() {
    return new CheesePizza();
    }
    }

    class GreekPizzaFactory extends PizzaFactory {
    @Override
    public Pizza producePizza() {
    return new GreekPizza();
    }
    }

3.1.2.3 抽象工厂模式

  • 该模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

  • 抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

  • 使用抽象工厂模式一般要满足以下条件:

    • 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
    • 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
  • 抽象工厂模式的主要角色如下:

    • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
    • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
    • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
    • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
    image-20210608233724055
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    //抽象产品:提供了产品的接口
    interface Product1 {
    public void show();
    }

    interface Product2 {
    public void show();
    }

    interface AbstractFactory {
    public Product1 newProduct1();
    public Product2 newProduct2();
    }

    class ConcreteFactory1 implements AbstractFactory {
    public Product1 newProduct1() {
    System.out.println("具体工厂 1 生成-->具体产品 11...");
    return new ConcreteProduct11();
    }
    public Product2 newProduct2() {
    System.out.println("具体工厂 1 生成-->具体产品 21...");
    return new ConcreteProduct21();
    }
    }

    class ConcreteFactory2 implements AbstractFactory {
    public Product1 newProduct1() {
    System.out.println("具体工厂 2 生成-->具体产品 12...");
    return new ConcreteProduct12();
    }
    public Product2 newProduct2() {
    System.out.println("具体工厂 2 生成-->具体产品 22...");
    return new ConcreteProduct22();
    }
    }

    class ConcreteProduct11 implements Product1 {
    public void show() {
    System.out.println("具体产品11显示...");
    }
    }

    class ConcreteProduct21 implements Product2 {
    public void show() {
    System.out.println("具体产品21显示...");
    }
    }

    class ConcreteProduct12 implements Product1 {
    public void show() {
    System.out.println("具体产品12显示...");
    }
    }

    class ConcreteProduct22 implements Product2 {
    public void show() {
    System.out.println("具体产品22显示...");
    }
    }
    • 优点:
      • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
      • 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
      • 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
    • 缺点:
      • 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
  • 举一个生产披萨的项目案例:假如披萨项目有个新的需求,即客户在点披萨时,可以点不同口味的披萨,比如北京的奶酪pizza、北京的胡椒pizza;或者是伦敦的奶酪pizza、伦敦的胡椒pizza。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    public class PizzaTest {
    public static void main(String[] args) {
    LDPizzaFactory ldPizzaFactory = new LDPizzaFactory();
    ldPizzaFactory.produceCheesePizza().produce();
    ldPizzaFactory.producePepperPizza().produce();

    BJPizzaFactory bjPizzaFactory = new BJPizzaFactory();
    bjPizzaFactory.produceCheesePizza().produce();
    bjPizzaFactory.producePepperPizza().produce();
    }
    }

    abstract class Pizza {
    public abstract void produce();
    }

    class LDPepperPizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作伦敦的胡椒pizza...");
    }
    }

    class LDCheesePizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作伦敦的奶酪pizza...");
    }
    }

    class BJPepperPizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作北京的胡椒pizza...");
    }
    }

    class BJCheesePizza extends Pizza {
    @Override
    public void produce() {
    System.out.println("制作北京的奶酪pizza...");
    }
    }

    abstract class PizzaFactory {
    public abstract Pizza produceCheesePizza();

    public abstract Pizza producePepperPizza();
    }

    class LDPizzaFactory extends PizzaFactory {
    @Override
    public Pizza produceCheesePizza() {
    return new LDCheesePizza();
    }

    @Override
    public Pizza producePepperPizza() {
    return new LDPepperPizza();
    }

    }

    class BJPizzaFactory extends PizzaFactory {
    @Override
    public Pizza produceCheesePizza() {
    return new BJCheesePizza();
    }

    @Override
    public Pizza producePepperPizza() {
    return new BJPepperPizza();
    }
    }

3.1.3 原型模式

  • 此模式是指用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。

  • 工作原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()。

  • 原型模式的优点:

    • Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
    • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
  • 原型模式的缺点:

    • 需要为每一个类都配置一个clone方法。
    • clone方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
    • 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
  • 原型模式包含以下主要角色:

    • 抽象原型类:规定了具体原型对象必须实现的接口。
    • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
    • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
    image-20210609194926785
  • 传统方式解决克隆羊问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    public class Sheep {
    private String name;
    private int age;
    private String color;
    public Sheep(String name, int age, String color) {
    super();
    this.name = name;
    this.age = age;
    this.color = color;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public int getAge() {
    return age;
    }
    public void setAge(int age) {
    this.age = age;
    }
    public String getColor() {
    return color;
    }
    public void setColor(String color) {
    this.color = color;
    }
    @Override
    public String toString() {
    return "Sheep [name=" + name + ", age=" + age + ", color=" + color + "]";
    }
    }

    public class Client {
    public static void main(String[] args) {
    //传统的方法
    Sheep sheep = new Sheep("tom", 1, "白色");

    Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    Sheep sheep5 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
    //....

    System.out.println(sheep);
    System.out.println(sheep2);
    System.out.println(sheep3);
    System.out.println(sheep4);
    System.out.println(sheep5);
    //...
    }
    }
    • 优点是比较好理解,简单易操作。
    • 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂 时,效率较低。
    • 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活。
  • 使用原型模式解决克隆羊问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;
    private String address = "蒙古羊";

    public Sheep(String name, int age, String color) {
    super();
    this.name = name;
    this.age = age;
    this.color = color;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getColor() {
    return color;
    }

    public void setColor(String color) {
    this.color = color;
    }


    @Override
    public String toString() {
    return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";
    }

    //克隆该实例,使用默认的clone方法来完成
    @Override
    protected Object clone() {
    Sheep sheep = null;
    try {
    sheep = (Sheep) super.clone();
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    return sheep;
    }
    }

    public class Client {
    public static void main(String[] args) {
    System.out.println("原型模式完成对象的创建");
    Sheep sheep = new Sheep("tom", 1, "白色");
    Sheep sheep2 = (Sheep) sheep.clone(); //克隆
    Sheep sheep3 = (Sheep) sheep.clone(); //克隆
    Sheep sheep4 = (Sheep) sheep.clone(); //克隆
    Sheep sheep5 = (Sheep) sheep.clone(); //克隆
    System.out.println(sheep2);//Sheep [name=tom, age=1, color=白色, address=蒙古羊]
    System.out.println(sheep3);//Sheep [name=tom, age=1, color=白色, address=蒙古羊]
    System.out.println(sheep4);//Sheep [name=tom, age=1, color=白色, address=蒙古羊]
    System.out.println(sheep5);//Sheep [name=tom, age=1, color=白色, address=蒙古羊]
    }
    }
  • 原型模式的注意事项和细节:

    • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
    • 不用重新初始化对象,而是动态地获得对象运行时的状态。
    • 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化, 无需修改代码。
    • 在实现深克隆的时候可能需要比较复杂的代码。
    • 缺点是需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。

3.1.3.1 浅拷贝

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个 实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。浅拷贝是使用默认的clone()方法来实现,前面我们克隆羊就是浅拷贝。

  • 克隆羊浅拷贝案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;
    private String address = "蒙古羊";
    public Sheep friend;

    public Sheep(String name, int age, String color) {
    super();
    this.name = name;
    this.age = age;
    this.color = color;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getColor() {
    return color;
    }

    public void setColor(String color) {
    this.color = color;
    }


    @Override
    public String toString() {
    return "Sheep [name=" + name + ", age=" + age + ", color=" + color + ", address=" + address + "]";
    }

    //克隆该实例,使用默认的clone方法来完成
    @Override
    protected Object clone() {
    Sheep sheep = null;
    try {
    sheep = (Sheep) super.clone();
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    return sheep;
    }
    }

    public class Client {
    public static void main(String[] args) {
    System.out.println("原型模式完成对象的创建");
    Sheep sheep = new Sheep("tom", 1, "白色");
    sheep.friend = new Sheep("jack", 2, "黑色");

    Sheep sheep2 = (Sheep) sheep.clone(); //克隆
    Sheep sheep3 = (Sheep) sheep.clone(); //克隆
    Sheep sheep4 = (Sheep) sheep.clone(); //克隆
    Sheep sheep5 = (Sheep) sheep.clone(); //克隆

    System.out.println("sheep2 =" + sheep2 + "sheep2.friend=" + sheep2.friend.hashCode());//sheep2 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep2.friend=1735600054
    System.out.println("sheep3 =" + sheep3 + "sheep3.friend=" + sheep3.friend.hashCode());//sheep3 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep3.friend=1735600054
    System.out.println("sheep4 =" + sheep4 + "sheep4.friend=" + sheep4.friend.hashCode());//sheep4 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep4.friend=1735600054
    System.out.println("sheep5 =" + sheep5 + "sheep5.friend=" + sheep5.friend.hashCode());//sheep5 =Sheep [name=tom, age=1, color=白色, address=蒙古羊]sheep5.friend=1735600054
    }
    }

3.1.3.2 深拷贝

  • 复制对象的所有基本数据类型的成员变量值。

  • 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。

  • 深拷贝实现方式1:重写clone方法来实现深拷贝。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    class DeepCloneableTarget implements Cloneable {
    private String cloneName;

    private String cloneClass;

    //构造器
    public DeepCloneableTarget(String cloneName, String cloneClass) {
    this.cloneName = cloneName;
    this.cloneClass = cloneClass;
    }

    //因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }

    class DeepProtoType implements Cloneable {

    public String name; //String 属性
    public DeepCloneableTarget deepCloneableTarget;// 引用类型

    public DeepProtoType() {
    super();
    }

    //深拷贝 - 方式 1 使用clone 方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
    Object deep = null;
    //这里完成对基本数据类型(属性)和String的克隆
    deep = super.clone();
    //对引用类型的属性,进行单独处理
    DeepProtoType deepProtoType = (DeepProtoType) deep;
    deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
    return deepProtoType;
    }
    }

    public class Client {
    public static void main(String[] args) throws Exception {
    DeepProtoType p = new DeepProtoType();
    p.name = "宋江";
    p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");

    //方式1 完成深拷贝
    DeepProtoType p2 = (DeepProtoType) p.clone();

    System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());//p.name=宋江p.deepCloneableTarget=1735600054
    System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());//p2.name=宋江p2.deepCloneableTarget=21685669
    }
    }
  • 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    class DeepProtoType implements Serializable {
    public String name; //String 属性
    public DeepCloneableTarget deepCloneableTarget;// 引用类型

    public DeepProtoType() {
    super();
    }

    //深拷贝 - 方式2 通过对象的序列化实现 (推荐)
    public Object deepClone() {
    //创建流对象
    ByteArrayOutputStream bos = null;
    ObjectOutputStream oos = null;
    ByteArrayInputStream bis = null;
    ObjectInputStream ois = null;
    try {
    //序列化
    bos = new ByteArrayOutputStream();
    oos = new ObjectOutputStream(bos);
    oos.writeObject(this); //当前这个对象以对象流的方式输出
    //反序列化
    bis = new ByteArrayInputStream(bos.toByteArray());
    ois = new ObjectInputStream(bis);
    DeepProtoType copyObj = (DeepProtoType) ois.readObject();
    return copyObj;
    } catch (Exception e) {
    e.printStackTrace();
    return null;
    } finally {
    //关闭流
    try {
    if (bos != null) {
    bos.close();
    }
    if (oos != null) {
    oos.close();
    }
    if (bis != null) {
    bis.close();
    }
    if (ois != null) {
    ois.close();
    }
    } catch (Exception e2) {
    System.out.println(e2.getMessage());
    }
    }

    }

    }

    class DeepCloneableTarget implements Serializable {
    private String cloneName;

    private String cloneClass;

    //构造器
    public DeepCloneableTarget(String cloneName, String cloneClass) {
    this.cloneName = cloneName;
    this.cloneClass = cloneClass;
    }
    }

    public class Client {
    public static void main(String[] args) throws Exception {
    DeepProtoType p = new DeepProtoType();
    p.name = "宋江";
    p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
    //方式2 完成深拷贝
    DeepProtoType p2 = (DeepProtoType) p.deepClone();

    System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());//p.name=宋江p.deepCloneableTarget=2016447921
    System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());//p2.name=宋江p2.deepCloneableTarget=1323165413
    }
    }

3.1.4 建造者模式

  • 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。

  • 建造者模式是创建型设计模式。

  • 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

  • 建造者模式的优点:

    • 封装性好,构建和表示分离。
    • 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
    • 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
  • 建造者模式的缺点:

    • 产品的组成部分必须相同,这限制了其使用范围。
    • 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
  • 建造者(Builder)模式的主要角色如下:

    • Product(产品角色):一个具体的产品对象。
    • Builder(抽象建造者):创建一个Product对象的各个部件指定的 接口/抽象类。
    • ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。
    • Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是隔离了客户与对象的生产过程,二是负责控制产品对象的生产过程。
    image-20210609202419804
  • 盖房子案例:需要建房子,这一过程为打桩、砌墙、封顶。房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的。

    image-20210609203400130
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    class House {
    private String basic;
    private String wall;
    private String roofed;

    public String getBasic() {
    return basic;
    }

    public void setBasic(String basic) {
    this.basic = basic;
    }

    public String getWall() {
    return wall;
    }

    public void setWall(String wall) {
    this.wall = wall;
    }

    public String getRoofed() {
    return roofed;
    }

    public void setRoofed(String roofed) {
    this.roofed = roofed;
    }
    }


    abstract class HouseBuilder {
    protected House house = new House();

    //将建造的流程写好, 抽象的方法
    public abstract void buildBasic();

    public abstract void buildWalls();

    public abstract void roofed();

    //建造房子好, 将产品(房子) 返回
    public House buildHouse() {
    return house;
    }
    }

    class HighBuilding extends HouseBuilder {
    @Override
    public void buildBasic() {
    System.out.println(" 高楼的打地基100米 ");
    }

    @Override
    public void buildWalls() {
    System.out.println(" 高楼的砌墙20cm ");
    }

    @Override
    public void roofed() {
    System.out.println(" 高楼的透明屋顶 ");
    }
    }

    class CommonHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
    System.out.println(" 普通房子打地基5米 ");
    }

    @Override
    public void buildWalls() {
    System.out.println(" 普通房子砌墙10cm ");
    }

    @Override
    public void roofed() {
    System.out.println(" 普通房子屋顶 ");
    }
    }

    class HouseDirector {
    HouseBuilder houseBuilder = null;

    //构造器传入 houseBuilder
    public HouseDirector(HouseBuilder houseBuilder) {
    this.houseBuilder = houseBuilder;
    }

    //通过setter 传入 houseBuilder
    public void setHouseBuilder(HouseBuilder houseBuilder) {
    this.houseBuilder = houseBuilder;
    }

    //如何处理建造房子的流程,交给指挥者
    public House constructHouse() {
    houseBuilder.buildBasic();
    houseBuilder.buildWalls();
    houseBuilder.roofed();
    return houseBuilder.buildHouse();
    }
    }

    public class Client {
    public static void main(String[] args) {
    //盖普通房子
    CommonHouse commonHouse = new CommonHouse();
    //准备创建房子的指挥者
    HouseDirector houseDirector = new HouseDirector(commonHouse);

    //完成盖房子,返回产品(普通房子)
    houseDirector.constructHouse();

    System.out.println("--------------------------");
    //盖高楼
    HighBuilding highBuilding = new HighBuilding();
    //重置建造者
    houseDirector.setHouseBuilder(highBuilding);
    //完成盖房子,返回产品(高楼)
    houseDirector.constructHouse();
    }
    }
  • 建造者模式的注意事项和细节:

    • 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象。
    • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”。
    • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
    • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式。
  • 抽象工厂模式VS建造者模式:

    • 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品,是具有不同分类维度的产品组合。采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可;而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

3.2 结构型设计模式

  • 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
  • 结构型模式分为以下7种:
    • 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
    • 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
    • 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
    • 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
    • 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
    • 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
    • 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
  • 以上7种结构型模式,除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式。

3.2.1 适配器模式

  • 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。

  • 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。

  • 该模式的主要优点如下:

    • 客户端通过适配器可以透明地调用目标接口。
    • 复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
    • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
    • 在很多业务场景中符合开闭原则。
  • 其缺点是:

    • 适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
    • 增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。
  • 适配器模式(Adapter)包含以下主要角色:

    • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
    • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
    • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
    image-20210610170210556
  • SpringMVC中的HandlerAdapter,就使用了适配器模式。

    • 使用HandlerAdapter的原因是处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了OCP原则。

    • Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类。

    • 适配器代替Controller执行相应的方法。

    • 扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了。

    • 简单版的SpringMVC代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      //被适配
      interface Controller {
      }

      class HttpController implements Controller {
      public void doHttpHandler() {
      System.out.println("http...");
      }
      }

      class SimpleController implements Controller {
      public void doSimplerHandler() {
      System.out.println("simple...");
      }
      }

      class AnnotationController implements Controller {
      public void doAnnotationHandler() {
      System.out.println("annotation...");
      }
      }

      //适配器
      interface HandlerAdapter {
      void handle(Object handler);

      boolean supports(Object handler);
      }

      class SimpleHandlerAdapter implements HandlerAdapter {
      public void handle(Object handler) {
      ((SimpleController) handler).doSimplerHandler();
      }

      public boolean supports(Object handler) {
      return (handler instanceof SimpleController);
      }
      }

      class HttpHandlerAdapter implements HandlerAdapter {
      public void handle(Object handler) {
      ((HttpController) handler).doHttpHandler();
      }

      public boolean supports(Object handler) {
      return (handler instanceof HttpController);
      }
      }

      class AnnotationHandlerAdapter implements HandlerAdapter {
      public void handle(Object handler) {
      ((AnnotationController) handler).doAnnotationHandler();
      }

      public boolean supports(Object handler) {
      return (handler instanceof AnnotationController);
      }
      }

      class DispatchServlet {
      public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();

      public DispatchServlet() {
      handlerAdapters.add(new AnnotationHandlerAdapter());
      handlerAdapters.add(new HttpHandlerAdapter());
      handlerAdapters.add(new SimpleHandlerAdapter());
      }

      public void doDispatch() {
      // 此处模拟SpringMVC从request取handler的对象,
      // 适配器可以获取到希望的Controller
      HttpController controller = new HttpController();
      // AnnotationController controller = new AnnotationController();
      //SimpleController controller = new SimpleController();
      // 得到对应适配器
      HandlerAdapter adapter = getHandler(controller);
      // 通过适配器执行对应的controller对应方法
      adapter.handle(controller);

      }

      public HandlerAdapter getHandler(Controller controller) {
      //遍历:根据得到的controller(handler), 返回对应适配器
      for (HandlerAdapter adapter : this.handlerAdapters) {
      if (adapter.supports(controller)) {
      return adapter;
      }
      }
      return null;
      }

      public static void main(String[] args) {
      new DispatchServlet().doDispatch();
      }

      }
  • 以生活中充电器为例,充电器本身相当于Adapter,220V交流电相当于Adaptee (即被适配者),5V直流电相当于目标(Target)接口。下面适配器模式中的三种模式(类适配器模式、对象适配器模式、接口适配器模式)举例。

3.2.1.1 类适配器模式

  • 整体类图如下:

    image-20210610171344355
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //被适配的类
    class Voltage220V {
    //输出220V的电压
    public int output220V() {
    int src = 220;
    System.out.println("被适配电压=" + src + "伏");
    return src;
    }
    }

    //目标接口
    interface IVoltage5V {
    public int output5V();
    }

    //适配器类
    class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5V() {
    //获取到220V电压
    int srcV = output220V();
    int dstV = srcV / 44; //转成 5v
    System.out.println("适配后电压=" + dstV + "伏");
    return dstV;
    }
    }

    public class Client {
    public static void main(String[] args) {
    IVoltage5V voltageAdapter = new VoltageAdapter();
    voltageAdapter.output5V();
    }
    }
  • 类适配器模式注意事项和细节:

    • Java是单继承机制,所以类适配器需要继承Adaptee类这一点算是一个缺点,因为这要求Target必须是接口,有一定局限性。
    • Adaptee类的方法在Adapter中都会暴露出来,也增加了使用的成本。
    • 由于其继承了Adaptee类,所以它可以根据需求重写Adaptee类的方法,使得Adapter的灵活性增强了。

3.2.1.2 对象适配器模式

  • 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承Adaptee类,而是持有Adaptee类的实例,以解决兼容性的问题。

  • 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。

  • 对象适配器模式是适配器模式常用的一种。

  • 整体类图如下:

    image-20210610171955615
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    //被适配的类
    class Voltage220V {
    //输出220V的电压
    public int output220V() {
    int src = 220;
    System.out.println("被适配电压=" + src + "伏");
    return src;
    }
    }

    //目标接口
    interface IVoltage5V {
    public int output5V();
    }

    //适配器类
    class VoltageAdapter implements IVoltage5V {
    private Voltage220V voltage220V; // 关联关系-聚合

    //通过构造器,传入一个 Voltage220V 实例
    public VoltageAdapter(Voltage220V voltage220v) {
    this.voltage220V = voltage220v;
    }

    @Override
    public int output5V() {
    int dst = 0;
    if(null != voltage220V) {
    int src = voltage220V.output220V();//获取220V 电压
    System.out.println("使用对象适配器,进行适配~~");
    dst = src / 44;
    System.out.println("适配完成,输出的电压为=" + dst);
    }
    return dst;
    }
    }

    public class Client {
    public static void main(String[] args) {
    IVoltage5V voltageAdapter = new VoltageAdapter(new Voltage220V());
    voltageAdapter.output5V();
    }
    }
  • 对象适配器模式注意事项和细节:

    • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承Adaptee的局限性问题,也不再要求Target必须是接口。
    • 使用成本更低,更灵活。

3.2.1.3 接口适配器模式

  • 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。

  • 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。

  • 适用于一个接口不想使用其所有的方法的情况。

  • 整体类图如下:

    image-20210610172441260
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    interface Interface4 {
    public void m1();
    public void m2();
    public void m3();
    public void m4();
    }

    //在AbsAdapter 将 Interface4 的方法进行默认实现
    abstract class AbsAdapter implements Interface4 {
    //默认实现
    public void m1() {

    }

    public void m2() {

    }

    public void m3() {

    }

    public void m4() {

    }
    }

    public class Client {
    public static void main(String[] args) {
    AbsAdapter absAdapter = new AbsAdapter() {
    //只需要去覆盖我们 需要使用 接口方法
    @Override
    public void m1() {
    System.out.println("使用了m1的方法");
    }
    };
    absAdapter.m1();
    }
    }

3.2.2 桥接模式

  • 桥接模式(Bridge模式)是指将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。

  • Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。

  • 桥接(Bridge)模式的优点是:

    • 抽象与实现分离,扩展能力强。
    • 符合开闭原则。
    • 符合合成复用原则。
    • 其实现细节对客户透明。
  • 缺点:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。

  • 桥接(Bridge)模式包含以下主要角色(这里的抽象类和接口是聚合的关系,是调用和被调用关系):

    • 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
    • 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
    • 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
    • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
    image-20210610231237836
  • 解决手机操作问题,现在对不同手机类型的不同品牌实现操作编程(比如: 开机、关机、上网,打电话等), 如图:

    无标题
    • 传统的解决方式类图如下:

      • 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
      • 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
      • 解决方案-使用桥接模式。
    • 使用桥接模式改进如下:

      image-20210610234750478
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      //接口
      interface Brand {
      void open();

      void close();

      void call();
      }

      class Vivo implements Brand {
      @Override
      public void open() {
      System.out.println(" Vivo手机开机 ");
      }

      @Override
      public void close() {
      System.out.println(" Vivo手机关机 ");
      }

      @Override
      public void call() {
      System.out.println(" Vivo手机打电话 ");
      }
      }

      class XiaoMi implements Brand {
      @Override
      public void open() {
      System.out.println(" 小米手机开机 ");
      }

      @Override
      public void close() {
      System.out.println(" 小米手机关机 ");
      }

      @Override
      public void call() {
      System.out.println(" 小米手机打电话 ");
      }
      }

      abstract class Phone {
      //组合品牌
      private Brand brand;

      //构造器
      public Phone(Brand brand) {
      super();
      this.brand = brand;
      }

      protected void open() {
      this.brand.open();
      }

      protected void close() {
      brand.close();
      }

      protected void call() {
      brand.call();
      }
      }


      //折叠式手机类,继承 抽象类 Phone
      class FoldedPhone extends Phone {

      public FoldedPhone(Brand brand) {
      super(brand);
      }

      public void open() {
      super.open();
      System.out.println(" 折叠样式手机 ");
      }

      public void close() {
      super.close();
      System.out.println(" 折叠样式手机 ");
      }

      public void call() {
      super.call();
      System.out.println(" 折叠样式手机 ");
      }
      }

      class UpRightPhone extends Phone {

      public UpRightPhone(Brand brand) {
      super(brand);
      }

      public void open() {
      super.open();
      System.out.println(" 直立样式手机 ");
      }

      public void close() {
      super.close();
      System.out.println(" 直立样式手机 ");
      }

      public void call() {
      super.call();
      System.out.println(" 直立样式手机 ");
      }
      }

      public class Client {
      public static void main(String[] args) {
      //获取折叠式手机 (样式 + 品牌)
      Phone phone1 = new FoldedPhone(new XiaoMi());
      phone1.open();
      phone1.call();
      phone1.close();

      System.out.println("==============");

      Phone phone2 = new FoldedPhone(new Vivo());
      phone2.open();
      phone2.call();
      phone2.close();

      System.out.println("==============");

      Phone phone3 = new UpRightPhone(new XiaoMi());
      phone3.open();
      phone3.call();
      phone3.close();

      System.out.println("==============");

      Phone phone4 = new UpRightPhone(new Vivo());
      phone4.open();
      phone4.call();
      phone4.close();
      }
      }
  • 桥接模式的注意事项和细节:

    • 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
    • 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
    • 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
    • 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
    • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。

3.2.3 装饰者模式

  • 装饰者模式是指动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。

  • 装饰器模式的主要优点有:

    • 装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用。
    • 通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果。
    • 装饰器模式完全遵守开闭原则。
  • 其主要缺点是:装饰器模式会增加许多子类,过度使用会增加程序得复杂性。

  • 装饰器模式主要包含以下角色:

    • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
    • 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
    • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
    • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
    image-20210618213730177
  • 使用装饰者模式解决星巴克咖啡订单项目(咖啡馆)问题:

    • 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式 咖啡)、Decaf(无因咖啡)

    • 调料:Milk、Soy(豆浆)、Chocolate

    • 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。

    • 计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。

      • 较差的解决方案如下,这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料, 类的数量就会倍增,就会出现类爆炸:

        image-20210618214307656
        • Drink 是一个抽象类,表示饮料。
        • description就是对咖啡的描述,比如咖啡的名字。
        • cost()方法就是计算费用,Drink类中做成一个抽象方法。
        • Decaf就是单品咖啡,继承Drink,并实现cost。
        • Espress && Milk就是单品咖啡+调料,这个组合很多。
      • 较好的解决方案如下,前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性:

        image-20210618214701499
        • milk、soy、chocolate可以设计为Boolean,表示是否要添加相应的调料。
        • 可以控制类的数量,不至于造成很多的类。
        • 在增加或者删除调料种类时,代码的维护量很大。
      • 使用装饰者模式:

        image-20210618215629499
        • Drink类就是前面说的抽象类,Component。
        • ShortBlack就单品咖啡。
        • Decorator是一个装饰类,含有一个被装饰的对象(Drink obj)。
        • Decorator的cost方法进行一个费用的叠加计算,递归的计算价格。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        abstract class Drink {
        public String des; // 描述
        private float price = 0.0f;

        public String getDes() {
        return des;
        }

        public void setDes(String des) {
        this.des = des;
        }

        public float getPrice() {
        return price;
        }

        public void setPrice(float price) {
        this.price = price;
        }

        //计算费用的抽象方法
        //子类来实现
        public abstract float cost();
        }

        class Coffee extends Drink {
        @Override
        public float cost() {
        return super.getPrice();
        }
        }

        class LongBlack extends Coffee {
        public LongBlack() {
        setDes("longblack");
        setPrice(5.0f);
        }
        }

        class Espresso extends Coffee {
        public Espresso() {
        setDes("意大利咖啡");
        setPrice(6.0f);
        }
        }

        class ShortBlack extends Coffee {
        public ShortBlack() {
        setDes("shortblack");
        setPrice(4.0f);
        }
        }

        class DeCaf extends Coffee {
        public DeCaf() {
        setDes("无因咖啡");
        setPrice(1.0f);
        }
        }

        class Decorator extends Drink {
        private Drink obj;

        public Decorator(Drink obj) { //组合
        this.obj = obj;
        }

        @Override
        public float cost() {
        // getPrice 自己价格
        return super.getPrice() + obj.cost();
        }

        @Override
        public String getDes() {
        // obj.getDes() 输出被装饰者的信息
        return des + " " + getPrice() + " && " + obj.getDes();
        }
        }

        //具体的Decorator, 这里就是调味品
        class Chocolate extends Decorator {
        public Chocolate(Drink obj) {
        super(obj);
        setDes("巧克力");
        setPrice(3.0f); // 调味品 的价格
        }
        }

        class Milk extends Decorator {
        public Milk(Drink obj) {
        super(obj);
        setDes("牛奶");
        setPrice(2.0f);
        }
        }

        public class CoffeeBar {
        public static void main(String[] args) {
        // 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack

        // 1. 点一份 LongBlack
        Drink order = new LongBlack();
        System.out.println("点一份LongBlack,累计费用:" + order.cost());
        System.out.println("点一份LongBlack,描述:" + order.getDes());
        System.out.println();
        // 2. order 加入一份牛奶
        order = new Milk(order);
        System.out.println("再加入一份牛奶,累计费用:" + order.cost());
        System.out.println("再加入一份牛奶,描述:" + order.getDes());
        System.out.println();
        // 3. order 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("再加入一份巧克力,累计费用:" + order.cost());
        System.out.println("再加入一份巧克力,描述 = " + order.getDes());
        System.out.println();
        // 3. order 加入一份巧克力
        order = new Chocolate(order);
        System.out.println("再加入一份巧克力,累计费用:" + order.cost());
        System.out.println("再加入一份巧克力,描述 = " + order.getDes());
        }
        }

        运行结果如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        点一份LongBlack,累计费用:5.0
        点一份LongBlack,描述:longblack

        再加入一份牛奶,累计费用:7.0
        再加入一份牛奶,描述:牛奶 2.0 && longblack

        再加入一份巧克力,累计费用:10.0
        再加入一份巧克力,描述 = 巧克力 3.0 && 牛奶 2.0 && longblack

        再加入一份巧克力,累计费用:13.0
        再加入一份巧克力,描述 = 巧克力 3.0 && 巧克力 3.0 && 牛奶 2.0 && longblack

3.2.4 组合模式

  • 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。

  • 组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。

    image-20210809223534121
  • 由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。这种类型的设计模式属于结构型模式。

    • 这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
  • 组合模式的主要优点有:

    • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码。
    • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”。
  • 主要缺点是:

    • 设计较复杂,客户端需要花更多时间理清类之间的层次关系。
    • 不容易限制容器中的构件。
    • 不容易用继承的方法来增加构件的新功能。
  • 组合模式包含以下主要角色:

    • 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)

    • 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。

    • 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

    • 组合模式分为透明式的组合模式和安全式的组合模式。

      • ①透明方式。在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有Add()、Remove()及GetChild()方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。其结构图如图所示。

        image-20210809224348545
      • ②安全方式。在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。其结构图如图所示。

        image-20210809224443839
  • 使用组合模式展示一个学校院系结构,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    public class Client {
    public static void main(String[] args) {
    //从大到小创建对象,学校
    OrganizationComponent university = new University("清华大学", " 中国顶级大学 ");

    //创建学院
    OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
    OrganizationComponent infoEngineerCollege = new College("信息工程学院", " 信息工程学院 ");

    //创建各个学院下面的系(专业)
    computerCollege.add(new Department("软件工程", " 软件工程不错 "));
    computerCollege.add(new Department("网络工程", " 网络工程不错 "));
    computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));

    infoEngineerCollege.add(new Department("通信工程", " 通信工程不好学 "));
    infoEngineerCollege.add(new Department("信息工程", " 信息工程好学 "));

    //将学院加入到 学校
    university.add(computerCollege);
    university.add(infoEngineerCollege);

    university.print();
    }
    }

    abstract class OrganizationComponent {
    private String name; // 名字
    private String des; // 说明

    protected void add(OrganizationComponent organizationComponent) {
    //默认实现
    throw new UnsupportedOperationException();
    }

    protected void remove(OrganizationComponent organizationComponent) {
    //默认实现
    throw new UnsupportedOperationException();
    }

    //构造器
    public OrganizationComponent(String name, String des) {
    super();
    this.name = name;
    this.des = des;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public String getDes() {
    return des;
    }

    public void setDes(String des) {
    this.des = des;
    }

    //方法print, 做成抽象的, 子类都需要实现
    protected abstract void print();
    }

    //University 就是 Composite , 可以管理College
    class University extends OrganizationComponent {

    List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();

    // 构造器
    public University(String name, String des) {
    super(name, des);
    }

    // 重写add
    @Override
    protected void add(OrganizationComponent organizationComponent) {
    organizationComponents.add(organizationComponent);
    }

    // 重写remove
    @Override
    protected void remove(OrganizationComponent organizationComponent) {
    organizationComponents.remove(organizationComponent);
    }

    @Override
    public String getName() {
    return super.getName();
    }

    @Override
    public String getDes() {
    return super.getDes();
    }

    // print方法,就是输出University 包含的学院
    @Override
    protected void print() {
    System.out.println("--------------" + getName() + "--------------");
    //遍历 organizationComponents
    for (OrganizationComponent organizationComponent : organizationComponents) {
    organizationComponent.print();
    }
    }
    }

    class College extends OrganizationComponent {
    //List中存放Department
    List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();

    // 构造器
    public College(String name, String des) {
    super(name, des);
    }

    // 重写add
    @Override
    protected void add(OrganizationComponent organizationComponent) {
    //将来实际业务中,College的add()和University的add()不一定完全一样
    organizationComponents.add(organizationComponent);
    }

    // 重写remove
    @Override
    protected void remove(OrganizationComponent organizationComponent) {
    organizationComponents.remove(organizationComponent);
    }

    @Override
    public String getName() {
    return super.getName();
    }

    @Override
    public String getDes() {
    return super.getDes();
    }

    // print方法,就是输出University包含的学院
    @Override
    protected void print() {
    System.out.println("--------------" + getName() + "--------------");
    //遍历 organizationComponents
    for (OrganizationComponent organizationComponent : organizationComponents) {
    organizationComponent.print();
    }
    }
    }

    class Department extends OrganizationComponent {

    //没有集合

    public Department(String name, String des) {
    super(name, des);
    }


    //add,remove就不用写了,因为他是叶子节点
    @Override
    public String getName() {
    return super.getName();
    }

    @Override
    public String getDes() {
    return super.getDes();
    }

    @Override
    protected void print() {
    System.out.println(getName());
    }
    }

    执行结果:

  • 组合模式的注意事项和细节:

    • 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
    • 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动。
    • 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构。
    • 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式。
    • 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式。

3.2.5 外观模式

  • 外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

  • 在日常编码工作中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调度多个子系统(2个以上的类对象),我们都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。

  • 外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点:

    • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
    • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
    • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
  • 主要缺点如下:

    • 不能很好地限制客户使用子系统类,很容易带来未知风险。
    • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
  • 外观(Facade)模式包含以下主要角色:

    • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。

    • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

    • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

      image-20210810092852495
  • 传统方式解决影院管理问题:

    • 在ClientTest的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程。
    • 不利于在ClientTest中,去维护对子系统的操作。
    • 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法ready, play, pause, end ),用来访问子系统中的一群接口。
    • 也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节,即外观模式。
  • 使用外观模式解决影院管理问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    class TheaterLight {
    private static TheaterLight instance = new TheaterLight();

    public static TheaterLight getInstance() {
    return instance;
    }

    public void on() {
    System.out.println(" TheaterLight on ");
    }

    public void off() {
    System.out.println(" TheaterLight off ");
    }

    public void dim() {
    System.out.println(" TheaterLight dim.. ");
    }

    public void bright() {
    System.out.println(" TheaterLight bright.. ");
    }
    }


    class Stereo {

    private static Stereo instance = new Stereo();

    public static Stereo getInstance() {
    return instance;
    }

    public void on() {
    System.out.println(" Stereo on ");
    }

    public void off() {
    System.out.println(" Screen off ");
    }

    public void up() {
    System.out.println(" Screen up.. ");
    }
    }

    class Screen {

    private static Screen instance = new Screen();

    public static Screen getInstance() {
    return instance;
    }
    public void up() {
    System.out.println(" Screen up ");
    }

    public void down() {
    System.out.println(" Screen down ");
    }
    }

    class Projector {
    private static Projector instance = new Projector();

    public static Projector getInstance() {
    return instance;
    }

    public void on() {
    System.out.println(" Projector on ");
    }

    public void off() {
    System.out.println(" Projector ff ");
    }

    public void focus() {
    System.out.println(" Projector is Projector ");
    }
    }

    class Popcorn {

    private static Popcorn instance = new Popcorn();

    public static Popcorn getInstance() {
    return instance;
    }

    public void on() {
    System.out.println(" popcorn on ");
    }

    public void off() {
    System.out.println(" popcorn ff ");
    }

    public void pop() {
    System.out.println(" popcorn is poping ");
    }
    }

    class DVDPlayer {
    //使用单例模式, 使用饿汉式
    private static DVDPlayer instance = new DVDPlayer();

    public static DVDPlayer getInstance() {
    return instance;
    }

    public void on() {
    System.out.println(" dvd on ");
    }
    public void off() {
    System.out.println(" dvd off ");
    }

    public void play() {
    System.out.println(" dvd is playing ");
    }

    public void pause() {
    System.out.println(" dvd pause ..");
    }
    }

    //外观角色
    class HomeTheaterFacade {
    //定义各个子系统对象
    private TheaterLight theaterLight;
    private Popcorn popcorn;
    private Stereo stereo;
    private Projector projector;
    private Screen screen;
    private DVDPlayer dVDPlayer;


    //构造器
    public HomeTheaterFacade() {
    super();
    this.theaterLight = TheaterLight.getInstance();
    this.popcorn = Popcorn.getInstance();
    this.stereo = Stereo.getInstance();
    this.projector = Projector.getInstance();
    this.screen = Screen.getInstance();
    this.dVDPlayer = DVDPlayer.getInstance();
    }

    //操作分成 4 步
    public void ready() {
    popcorn.on();
    popcorn.pop();
    screen.down();
    projector.on();
    stereo.on();
    dVDPlayer.on();
    theaterLight.dim();
    }

    public void play() {
    dVDPlayer.play();
    }

    public void pause() {
    dVDPlayer.pause();
    }

    public void end() {
    popcorn.off();
    theaterLight.bright();
    screen.up();
    projector.off();
    stereo.off();
    dVDPlayer.off();
    }
    }

    public class Client {
    public static void main(String[] args) {
    HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
    homeTheaterFacade.ready();
    homeTheaterFacade.play();
    homeTheaterFacade.end();
    }
    }
  • 外观模式的注意事项和细节:

    • 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。
    • 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展。
    • 通过合理的使用外观模式,可以帮我们更好的划分访问的层次。
    • 当系统需要进行分层设计时,可以考虑使用Facade模式。
    • 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性。
    • 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。

3.2.6 享元模式

  • 享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

  • 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。

  • 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。

  • 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。

  • 享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

  • 其主要缺点是:

    • 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
    • 读取享元模式的外部状态会使得运行时间稍微变长。
  • 享元模式的主要角色有如下:

    • 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。

    • 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。

    • 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。

    • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

  • 内部状态和外部状态:

    • 比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态。
      • 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态。
      • 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
      • 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
      • 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。
  • 使用享元模式解决网站展现项目问题:

    image-20210810100541414
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    //抽象享元角色
    abstract class WebSite {
    public abstract void use(User user);//抽象方法
    }

    //具体享元角色
    class ConcreteWebSite extends WebSite {
    //共享的部分,内部状态
    private String type = ""; //网站发布的形式(类型)

    //构造器
    public ConcreteWebSite(String type) {
    this.type = type;
    }

    @Override
    public void use(User user) {
    System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
    }
    }

    //享元工厂角色
    class WebSiteFactory {
    //集合,充当池的作用
    private HashMap<String, ConcreteWebSite> pool = new HashMap<>();

    //根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回
    public WebSite getWebSiteCategory(String type) {
    if(!pool.containsKey(type)) {
    //就创建一个网站,并放入到池中
    pool.put(type, new ConcreteWebSite(type));
    }

    return (WebSite)pool.get(type);
    }

    //获取网站分类的总数 (池中有多少个网站类型)
    public int getWebSiteCount() {
    return pool.size();
    }
    }

    //非享元角色
    class User {
    private String name;

    public User(String name) {
    super();
    this.name = name;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }
    }

    public class Client {
    public static void main(String[] args) {
    // 创建一个工厂类
    WebSiteFactory factory = new WebSiteFactory();

    // 客户要一个以新闻形式发布的网站
    WebSite webSite1 = factory.getWebSiteCategory("新闻");


    webSite1.use(new User("tom"));

    // 客户要一个以博客形式发布的网站
    WebSite webSite2 = factory.getWebSiteCategory("博客");

    webSite2.use(new User("jack"));

    // 客户要一个以博客形式发布的网站
    WebSite webSite3 = factory.getWebSiteCategory("博客");

    webSite3.use(new User("smith"));

    // 客户要一个以博客形式发布的网站
    WebSite webSite4 = factory.getWebSiteCategory("博客");

    webSite4.use(new User("king"));

    System.out.println("网站的分类共=" + factory.getWebSiteCount());
    }
    }
  • 享元模式的注意事项和细节:

    • 在享元模式这样理解,“享”就表示共享,“元”表示对象。
    • 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式。
    • 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储。
    • 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
    • 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。
    • 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
    • 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。

3.2.7 代理模式

  • 该模式指为一个对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象。这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
  • 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
  • 代理模式有不同的形式,主要有三种方式:静态代理、JDK代理(动态代理)、和Cglib代理(动态代理)。
  • 代理模式的主要优点有:
    • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
    • 代理对象可以扩展目标对象的功能。
    • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性。
  • 其主要缺点是:
    • 代理模式会造成系统设计中类的数量增加。
    • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。
    • 增加了系统的复杂度。

3.2.7.1 静态代理

  • 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

    image-20210613214307734
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public class Client {
    public static void main(String[] args) {
    //创建目标对象(被代理对象)
    TeacherDao teacherDao = new TeacherDao();

    //创建代理对象, 同时将被代理对象传递给代理对象
    TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);

    //通过代理对象,调用到被代理对象的方法
    //即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
    teacherDaoProxy.teach();
    }
    }

    //接口
    interface ITeacherDao {
    void teach(); // 授课的方法
    }

    class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
    System.out.println("老师授课中...");
    }
    }

    //代理对象,静态代理
    class TeacherDaoProxy implements ITeacherDao{

    private ITeacherDao target; // 目标对象,通过接口来聚合

    //构造器
    public TeacherDaoProxy(ITeacherDao target) {
    this.target = target;
    }

    @Override
    public void teach() {
    System.out.println("开始代理,开始某些操作...");
    target.teach();
    System.out.println("结束代理,完成某些操作...");
    }
    }
  • 静态代理优缺点:

    • 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
    • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。
    • 一旦接口增加方法,目标对象与代理对象都要维护。

3.2.7.2 JDK代理

  • 代理对象不需要实现接口,但是目标对象要实现接口,否则不能用JDK代理。

  • JDK代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。

    image-20210613220232118
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public class Client {
    public static void main(String[] args) {
    //创建目标对象
    ITeacherDao target = new TeacherDao();

    //给目标对象,创建代理对象, 可以转成 ITeacherDao
    ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();

    // proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
    System.out.println("proxyInstance=" + proxyInstance.getClass());

    //通过代理对象,调用目标对象的方法
    proxyInstance.teach();
    }
    }

    //接口
    interface ITeacherDao {
    void teach(); // 授课的方法
    }

    class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
    System.out.println("老师授课中...");
    }
    }

    class ProxyFactory {
    //维护一个目标对象 , Object
    private Object target;

    //构造器,对target 进行初始化
    public ProxyFactory(Object target) {
    this.target = target;
    }

    //给目标对象 生成一个代理对象
    public Object getProxyInstance() {
    /*
    * public static Object newProxyInstance(ClassLoader loader,
    * Class<?>[] interfaces,
    * InvocationHandler h)
    *
    * 1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
    * 2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
    * 3. InvocationHandler h : 事件处理,执行代理对象的方法时,会触发事件处理器方法, 会把当前执行的目标对象方法作为参数传入
    */
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("JDK代理开始~~");
    Object returnVal = method.invoke(target, args);
    System.out.println("JDK代理提交");
    return returnVal;
    }
    });
    }
    }

3.2.7.3 CGLIB代理

  • 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,这就是Cglib代理。

  • Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。

  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。

  • 在AOP编程中如何选择代理模式:

    • 目标对象需要实现接口,用JDK代理。
    • 目标对象不需要实现接口,用Cglib代理。
  • Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。

    • 在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException。

    • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      public class Client {
      public static void main(String[] args) {
      //创建目标对象
      TeacherDao target = new TeacherDao();
      //获取到代理对象,并且将目标对象传递给代理对象
      TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();

      //执行代理对象的方法,触发intercept方法,从而实现对目标对象的调用
      proxyInstance.teach();
      }
      }

      class TeacherDao {
      public void teach() {
      System.out.println("老师授课中...");
      }
      }

      class ProxyFactory implements MethodInterceptor {
      //维护一个目标对象
      private Object target;

      //构造器,传入一个被代理的对象
      public ProxyFactory(Object target) {
      this.target = target;
      }

      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
      System.out.println("Cglib代理模式 ~~ 开始");
      Object returnVal = method.invoke(target, objects);
      System.out.println("Cglib代理模式 ~~ 提交");
      return returnVal;
      }

      //返回一个代理对象: 是 target 对象的代理对象
      public Object getProxyInstance() {
      //1. 创建一个工具类
      Enhancer enhancer = new Enhancer();
      //2. 设置父类
      enhancer.setSuperclass(target.getClass());
      //3. 设置回调函数
      enhancer.setCallback(this);
      //4. 创建子类对象,即代理对象
      return enhancer.create();
      }
      }

3.3 行为型设计模式

  • 行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

  • 行为型模式是GoF设计模式中最为庞大的一类,它包含以下 11 种模式。

    • 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
    • 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
    • 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
    • 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
    • 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
    • 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
    • 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
    • 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
    • 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
    • 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
    • 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
  • 以上11种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。

3.3.1 模板方法模式

  • 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),它是在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

  • 简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。

  • 该模式的主要优点如下:

    • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
    • 它在父类中提取了公共的部分代码,便于代码复用。
    • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  • 该模式的主要缺点如下:

    • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
    • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
    • 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
  • 模板方法模式包含以下主要角色:

    • 抽象类/抽象模板(Abstract Class):抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

    • 具体子类/具体实现(Concrete Class):具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

      image-20210810103606168
  • 模板方法模式解决豆浆制作问题,制作豆浆的流程如下:

    • 选材—>添加配料—>浸泡—>放到豆浆机打碎。

    • 通过添加不同的配料,可以制作出不同口味的豆浆。

    • 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的。

      image-20210810104108402
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      //抽象类,表示豆浆
      abstract class SoyaMilk {
      //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
      final void make() {
      select();
      addCondiments();
      soak();
      beat();
      }

      //选材料
      void select() {
      System.out.println("第一步:选择好的新鲜黄豆 ");
      }

      //添加不同的配料, 抽象方法, 子类具体实现
      abstract void addCondiments();

      //浸泡
      void soak() {
      System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
      }

      void beat() {
      System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");
      }
      }

      class PeanutSoyaMilk extends SoyaMilk {
      @Override
      void addCondiments() {
      System.out.println(" 加入上好的花生 ");
      }
      }

      class RedBeanSoyaMilk extends SoyaMilk {
      @Override
      void addCondiments() {
      System.out.println(" 加入上好的红豆 ");
      }
      }

      public class Client {
      public static void main(String[] args) {
      //制作红豆豆浆
      System.out.println("----制作红豆豆浆----");
      SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
      redBeanSoyaMilk.make();

      System.out.println("----制作花生豆浆----");
      SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
      peanutSoyaMilk.make();
      }
      }
    • 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。

    • 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,使用钩子方法对前面的模板方法进行改造如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      //抽象类,表示豆浆
      abstract class SoyaMilk {
      //模板方法, make , 模板方法可以做成final , 不让子类去覆盖.
      final void make() {
      select();
      if(customerWantCondiments()) {
      addCondiments();
      }
      soak();
      beat();
      }

      //选材料
      void select() {
      System.out.println("第一步:选择好的新鲜黄豆 ");
      }

      //添加不同的配料, 抽象方法, 子类具体实现
      abstract void addCondiments();

      //浸泡
      void soak() {
      System.out.println("第三步, 黄豆和配料开始浸泡, 需要3小时 ");
      }

      void beat() {
      System.out.println("第四步:黄豆和配料放到豆浆机去打碎 ");
      }

      //钩子方法,决定是否需要添加配料
      boolean customerWantCondiments() {
      return true;
      }
      }

      class PureSoyaMilk extends SoyaMilk{
      @Override
      void addCondiments() {
      //空实现
      }

      @Override
      boolean customerWantCondiments() {
      return false;
      }
      }

      public class Client {
      public static void main(String[] args) {
      System.out.println("----制作纯豆浆----");
      SoyaMilk pureSoyaMilk = new PureSoyaMilk();
      pureSoyaMilk.make();
      }
      }
  • 模板方法模式的注意事项和细节:

    • 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
    • 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
    • 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
    • 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大。
    • 一般模板方法都加上final关键字, 防止子类重写模板方法。
    • 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理。

3.3.2 命令模式

  • 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计。

  • 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。

  • 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。

  • 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。

    • Invoker是调用者(将军)。
    • Receiver是被调用者(士兵)。
    • MyCommand是命令,实现了Command接口,持有接收对象。
  • 命令模式的主要优点如下:

    • 通过引入中间件(抽象接口)降低系统的耦合度。
    • 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
    • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
    • 方便实现Undo和Redo操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
    • 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
  • 其缺点是:

    • 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
    • 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。
  • 命令模式包含以下主要角色:

    • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法execute()。

    • 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。

    • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。

    • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

      image-20210810163359699
  • 命令模式解决智能家居项目问题:

    • 假如我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。

    • 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。

      image-20210810165254925
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      226
      227
      //创建命令接口
      interface Command {
      //执行动作(操作)
      public void execute();
      //撤销动作(操作)
      public void undo();
      }

      class LightOnCommand implements Command {
      //聚合LightReceiver
      LightReceiver light;

      //构造器
      public LightOnCommand(LightReceiver light) {
      super();
      this.light = light;
      }

      @Override
      public void execute() {
      //调用接收者的方法
      light.on();
      }

      @Override
      public void undo() {
      //调用接收者的方法
      light.off();
      }
      }

      class LightOffCommand implements Command {
      // 聚合LightReceiver
      LightReceiver light;

      // 构造器
      public LightOffCommand(LightReceiver light) {
      super();
      this.light = light;
      }

      @Override
      public void execute() {
      // 调用接收者的方法
      light.off();
      }

      @Override
      public void undo() {
      // 调用接收者的方法
      light.on();
      }
      }

      class TVOnCommand implements Command {
      // 聚合TVReceiver
      TVReceiver tv;

      // 构造器
      public TVOnCommand(TVReceiver tv) {
      super();
      this.tv = tv;
      }

      @Override
      public void execute() {
      // 调用接收者的方法
      tv.on();
      }

      @Override
      public void undo() {
      // 调用接收者的方法
      tv.off();
      }
      }

      class TVOffCommand implements Command {
      // 聚合TVReceiver
      TVReceiver tv;

      // 构造器
      public TVOffCommand(TVReceiver tv) {
      super();
      this.tv = tv;
      }

      @Override
      public void execute() {
      // 调用接收者的方法
      tv.off();
      }

      @Override
      public void undo() {
      // 调用接收者的方法
      tv.on();
      }
      }

      /**
      * 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做
      * 其实,这样是一种设计模式, 可以省掉对空判断
      */
      class NoCommand implements Command {
      @Override
      public void execute() {

      }

      @Override
      public void undo() {

      }
      }

      class LightReceiver {
      public void on() {
      System.out.println(" 电灯打开了.. ");
      }

      public void off() {
      System.out.println(" 电灯关闭了.. ");
      }
      }

      class TVReceiver {
      public void on() {
      System.out.println(" 电视机打开了.. ");
      }

      public void off() {
      System.out.println(" 电视机关闭了.. ");
      }
      }

      class RemoteController {
      // 开按钮的命令数组
      Command[] onCommands;
      Command[] offCommands;

      // 执行撤销的命令
      Command undoCommand;

      // 构造器,完成对按钮初始化
      public RemoteController() {
      onCommands = new Command[5];
      offCommands = new Command[5];

      for (int i = 0; i < 5; i++) {
      onCommands[i] = new NoCommand();
      offCommands[i] = new NoCommand();
      }
      }

      // 给我们的按钮设置你需要的命令
      public void setCommand(int no, Command onCommand, Command offCommand) {
      onCommands[no] = onCommand;
      offCommands[no] = offCommand;
      }

      // 按下开按钮
      public void onButtonWasPushed(int no) { // no 0
      // 找到你按下的开的按钮, 并调用对应方法
      onCommands[no].execute();
      // 记录这次的操作,用于撤销
      undoCommand = onCommands[no];
      }

      // 按下开按钮
      public void offButtonWasPushed(int no) { // no 0
      // 找到你按下的关的按钮, 并调用对应方法
      offCommands[no].execute();
      // 记录这次的操作,用于撤销
      undoCommand = offCommands[no];
      }

      // 按下撤销按钮
      public void undoButtonWasPushed() {
      undoCommand.undo();
      }
      }

      public class Client {

      public static void main(String[] args) {
      //使用命令设计模式,完成通过遥控器,对电灯的操作

      //创建电灯的对象(接受者)
      LightReceiver lightReceiver = new LightReceiver();

      //创建电灯相关的开关命令
      LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
      LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);

      //需要一个遥控器
      RemoteController remoteController = new RemoteController();

      //给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作
      remoteController.setCommand(0, lightOnCommand, lightOffCommand);

      System.out.println("--------按下灯的开按钮-----------");
      remoteController.onButtonWasPushed(0);
      System.out.println("--------按下灯的关按钮-----------");
      remoteController.offButtonWasPushed(0);
      System.out.println("--------按下撤销按钮-----------");
      remoteController.undoButtonWasPushed();


      System.out.println("=========使用遥控器操作电视机==========");

      TVReceiver tvReceiver = new TVReceiver();

      TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);
      TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);

      //给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作
      remoteController.setCommand(1, tvOnCommand, tvOffCommand);

      System.out.println("--------按下电视机的开按钮-----------");
      remoteController.onButtonWasPushed(1);
      System.out.println("--------按下电视机的关按钮-----------");
      remoteController.offButtonWasPushed(1);
      System.out.println("--------按下撤销按钮-----------");
      remoteController.undoButtonWasPushed();
      }
      }

      执行结果:

      image-20210810165345088
  • 命令模式的注意事项和细节:

    • 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
    • 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令。
    • 容易实现对请求的撤销和重做。
    • 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意。
    • 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
    • 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制。

3.3.3 访问者模式

  • 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

  • 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。

  • 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口。

  • 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作”污染”这些对象的类,可以选用访问者模式解决。

  • 访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:

    • 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
    • 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
    • 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
    • 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
  • 主要缺点如下:

    • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
    • 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
    • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
  • 访问者模式包含以下主要角色:

    • 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit() ,该操作中的参数类型标识了被访问的具体元素。

    • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。

    • 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。

    • 具体元素(ConcreteElement)角色:实现抽象元素角色提供的accept() 操作,其方法体通常都是visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。

    • 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

  • 使用访问者模式完成测评系统需求:

    • 将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不同的种类,比如成功、失败等)

      image-20210810201017108
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      abstract class Action {
      //得到男性的测评
      public abstract void getManResult(Man man);

      //得到女的测评
      public abstract void getWomanResult(Woman woman);
      }

      class Fail extends Action {
      @Override
      public void getManResult(Man man) {
      System.out.println(" 男人给的评价该歌手失败 !");
      }

      @Override
      public void getWomanResult(Woman woman) {
      System.out.println(" 女人给的评价该歌手失败 !");
      }
      }

      class Success extends Action {
      @Override
      public void getManResult(Man man) {
      System.out.println(" 男人给的评价该歌手很成功 !");
      }

      @Override
      public void getWomanResult(Woman woman) {
      System.out.println(" 女人给的评价该歌手很成功 !");
      }
      }

      class Wait extends Action {
      @Override
      public void getManResult(Man man) {
      System.out.println(" 男人给的评价是该歌手待定 ..");
      }

      @Override
      public void getWomanResult(Woman woman) {
      System.out.println(" 女人给的评价是该歌手待定 ..");
      }
      }

      abstract class Person {
      //提供一个方法,让访问者可以访问
      public abstract void accept(Action action);
      }

      //说明
      //1. 这里我们使用到了双分派, 即首先在客户端程序中,将具体状态作为参数传递Woman中(第一次分派)
      //2. 然后Woman 类调用作为参数的 "具体方法" 中方法getWomanResult, 同时将自己(this)作为参数
      // 传入,完成第二次的分派
      class Woman extends Person{
      @Override
      public void accept(Action action) {
      action.getWomanResult(this);
      }
      }

      class Man extends Person {
      @Override
      public void accept(Action action) {
      action.getManResult(this);
      }
      }

      //数据结构,管理很多人(Man , Woman)
      class ObjectStructure {
      //维护了一个集合
      private List<Person> persons = new LinkedList<>();

      //增加到list
      public void attach(Person p) {
      persons.add(p);
      }
      //移除
      public void detach(Person p) {
      persons.remove(p);
      }

      //显示测评情况
      public void display(Action action) {
      for(Person p: persons) {
      p.accept(action);
      }
      }
      }

      public class Client {
      public static void main(String[] args) {
      //创建ObjectStructure
      ObjectStructure objectStructure = new ObjectStructure();

      objectStructure.attach(new Man());
      objectStructure.attach(new Woman());

      Success success = new Success();
      objectStructure.display(success);

      System.out.println("===============");
      Fail fail = new Fail();
      objectStructure.display(fail);

      System.out.println("===============");
      Wait wait = new Wait();
      objectStructure.display(wait);
      }
      }

      执行结果:

3.3.4 迭代器模式

  • 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。

  • 其主要优点如下:

    • 访问一个聚合对象的内容而无须暴露它的内部表示。
    • 遍历任务交由迭代器完成,这简化了聚合类。
    • 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
    • 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
    • 封装性良好,为遍历不同的聚合结构提供一个统一的接口。
  • 主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。

  • 迭代器模式主要包含以下角色:

    • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。

    • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。

    • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。

    • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

      image-20210810201330563
  • 使用迭代器模式展示学校院系结构:

    • 要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      class ComputerCollegeIterator implements Iterator {
      //这里我们需要Department 是以怎样的方式存放=>数组
      Department[] departments;
      int position = 0; //遍历的位置

      public ComputerCollegeIterator(Department[] departments) {
      this.departments = departments;
      }

      //判断是否还有下一个元素
      @Override
      public boolean hasNext() {
      if(position >= departments.length || departments[position] == null) {
      return false;
      }else {

      return true;
      }
      }

      @Override
      public Object next() {
      Department department = departments[position];
      position += 1;
      return department;
      }

      //删除的方法,默认空实现
      public void remove() {

      }
      }

      class InfoColleageIterator implements Iterator {
      List<Department> departmentList; // 信息工程学院是以List方式存放系
      int index = -1;//索引

      public InfoColleageIterator(List<Department> departmentList) {
      this.departmentList = departmentList;
      }

      //判断list中还有没有下一个元素
      @Override
      public boolean hasNext() {
      if(index >= departmentList.size() - 1) {
      return false;
      } else {
      index += 1;
      return true;
      }
      }

      @Override
      public Object next() {
      return departmentList.get(index);
      }

      //空实现remove
      public void remove() {

      }
      }

      class Department {
      private String name;
      private String desc;
      public Department(String name, String desc) {
      super();
      this.name = name;
      this.desc = desc;
      }
      public String getName() {
      return name;
      }
      public void setName(String name) {
      this.name = name;
      }
      public String getDesc() {
      return desc;
      }
      public void setDesc(String desc) {
      this.desc = desc;
      }
      }

      interface College {

      public String getName();

      //增加系的方法
      public void addDepartment(String name, String desc);

      //返回一个迭代器,遍历
      public Iterator createIterator();
      }

      class ComputerCollege implements College {
      Department[] departments;
      int numOfDepartment = 0 ;// 保存当前数组的对象个数

      public ComputerCollege() {
      departments = new Department[5];
      addDepartment("Java专业", " Java专业 ");
      addDepartment("PHP专业", " PHP专业 ");
      addDepartment("大数据专业", " 大数据专业 ");
      }

      @Override
      public String getName() {
      return "计算机学院";
      }

      @Override
      public void addDepartment(String name, String desc) {
      Department department = new Department(name, desc);
      departments[numOfDepartment] = department;
      numOfDepartment += 1;
      }

      @Override
      public Iterator createIterator() {
      return new ComputerCollegeIterator(departments);
      }
      }

      class InfoCollege implements College {
      List<Department> departmentList;

      public InfoCollege() {
      departmentList = new ArrayList<Department>();
      addDepartment("信息安全专业", " 信息安全专业 ");
      addDepartment("网络安全专业", " 网络安全专业 ");
      addDepartment("服务器安全专业", " 服务器安全专业 ");
      }

      @Override
      public String getName() {
      return "信息工程学院";
      }

      @Override
      public void addDepartment(String name, String desc) {
      Department department = new Department(name, desc);
      departmentList.add(department);
      }

      @Override
      public Iterator createIterator() {
      return new InfoColleageIterator(departmentList);
      }
      }

      class OutPutImpl {

      //学院集合
      List<College> collegeList;

      public OutPutImpl(List<College> collegeList) {
      this.collegeList = collegeList;
      }

      //遍历所有学院,然后调用printDepartment 输出各个学院的系
      public void printCollege() {
      //从collegeList 取出所有学院, Java 中的 List 已经实现Iterator
      Iterator<College> iterator = collegeList.iterator();

      while(iterator.hasNext()) {
      //取出一个学院
      College college = iterator.next();
      System.out.println("=== "+college.getName() +"=====" );
      printDepartment(college.createIterator()); //得到对应迭代器
      }
      }

      //输出学院的系
      public void printDepartment(Iterator iterator) {
      while(iterator.hasNext()) {
      Department d = (Department)iterator.next();
      System.out.println(d.getName());
      }
      }
      }

      public class Client {
      public static void main(String[] args) {
      //创建学院
      List<College> collegeList = new ArrayList<College>();

      ComputerCollege computerCollege = new ComputerCollege();
      InfoCollege infoCollege = new InfoCollege();

      collegeList.add(computerCollege);
      collegeList.add(infoCollege);

      OutPutImpl outPutImpl = new OutPutImpl(collegeList);
      outPutImpl.printCollege();
      }
      }

      执行结果:

3.3.5 观察者模式

  • 观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

  • 其主要优点如下:

    • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
    • 目标与观察者之间建立了一套触发机制。
  • 主要缺点如下:

    • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
    • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
  • 观察者模式的主要角色如下:

    • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。

    • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。

    • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。

    • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

      image-20210810203130651
  • 使用观察者模式完成天气预报项目需求:

    • 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)。

    • 需要设计开放型API,便于其他第三方也能接入气象站获取数据。

    • 提供温度、气压和湿度的接口。

    • 测量数据更新时,要能实时的通知给第三方。

      image-20210810203635031
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      //接口, 让WeatherData 来实现
      interface Subject {
      public void registerObserver(Observer o);
      public void removeObserver(Observer o);
      public void notifyObservers();
      }

      //观察者接口,有观察者来实现
      interface Observer {
      public void update(float temperature, float pressure, float humidity);
      }

      /**
      * 类是核心
      * 1. 包含最新的天气情况信息
      * 2. 含有 观察者集合,使用ArrayList管理
      * 3. 当数据有更新时,就主动的调用 ArrayList, 通知所有的(接入方)就看到最新的信息
      */
      class WeatherData implements Subject {
      private float temperatrue;
      private float pressure;
      private float humidity;
      //观察者集合
      private ArrayList<Observer> observers;

      public WeatherData() {
      observers = new ArrayList<Observer>();
      }

      public float getTemperature() {
      return temperatrue;
      }

      public float getPressure() {
      return pressure;
      }

      public float getHumidity() {
      return humidity;
      }

      public void dataChange() {
      //调用 接入方的 update
      notifyObservers();
      }

      //当数据有更新时,就调用 setData
      public void setData(float temperature, float pressure, float humidity) {
      this.temperatrue = temperature;
      this.pressure = pressure;
      this.humidity = humidity;
      dataChange();
      }

      //注册一个观察者
      @Override
      public void registerObserver(Observer o) {
      observers.add(o);
      }

      //移除一个观察者
      @Override
      public void removeObserver(Observer o) {
      if(observers.contains(o)) {
      observers.remove(o);
      }
      }

      //遍历所有的观察者,并通知
      @Override
      public void notifyObservers() {
      for(int i = 0; i < observers.size(); i++) {
      observers.get(i).update(this.temperatrue, this.pressure, this.humidity);
      }
      }
      }

      class CurrentConditions implements Observer {
      // 温度,气压,湿度
      private float temperature;
      private float pressure;
      private float humidity;

      // 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
      public void update(float temperature, float pressure, float humidity) {
      this.temperature = temperature;
      this.pressure = pressure;
      this.humidity = humidity;
      display();
      }

      // 显示
      public void display() {
      System.out.println("***Today mTemperature: " + temperature + "***");
      System.out.println("***Today mPressure: " + pressure + "***");
      System.out.println("***Today mHumidity: " + humidity + "***");
      }
      }

      class BaiduSite implements Observer {
      // 温度,气压,湿度
      private float temperature;
      private float pressure;
      private float humidity;

      // 更新 天气情况,是由 WeatherData 来调用,我使用推送模式
      public void update(float temperature, float pressure, float humidity) {
      this.temperature = temperature;
      this.pressure = pressure;
      this.humidity = humidity;
      display();
      }

      // 显示
      public void display() {
      System.out.println("===百度网站====");
      System.out.println("***百度网站 气温 : " + temperature + "***");
      System.out.println("***百度网站 气压: " + pressure + "***");
      System.out.println("***百度网站 湿度: " + humidity + "***");
      }
      }

      public class Client {
      public static void main(String[] args) {
      //创建一个WeatherData
      WeatherData weatherData = new WeatherData();

      //创建观察者
      CurrentConditions currentConditions = new CurrentConditions();
      BaiduSite baiduSite = new BaiduSite();

      //注册到weatherData
      weatherData.registerObserver(currentConditions);
      weatherData.registerObserver(baiduSite);

      System.out.println("通知各个注册的观察者, 看看信息");
      weatherData.setData(10f, 100f, 30.3f);

      weatherData.removeObserver(currentConditions);

      System.out.println();
      System.out.println("通知各个注册的观察者, 看看信息");
      weatherData.setData(10f, 100f, 30.3f);
      }
      }

3.3.6 中介者模式

  • 中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

  • 中介者模式是一种对象行为型模式,其主要优点如下:

    • 类之间各司其职,符合迪米特法则。
    • 降低了对象之间的耦合性,使得对象易于独立地被复用。
    • 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
  • 主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

  • 中介者模式包含以下主要角色:

    • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。

    • 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。

    • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。

    • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

  • 使用中介者模式实现智能家庭管理:

    • 智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘等。

    • 主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放。

      image-20210811110303754
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      abstract class Mediator {
      //将给中介者的对象,加入到集合中
      public abstract void Register(String colleagueName, Colleague colleague);

      //接收消息, 具体的同事对象发出
      public abstract void GetMessage(int stateChange, String colleagueName);

      public abstract void SendMessage();
      }

      //具体的中介者类
      class ConcreteMediator extends Mediator {
      //集合,放入所有的同事对象
      private HashMap<String, Colleague> colleagueMap;
      private HashMap<String, String> interMap;

      public ConcreteMediator() {
      colleagueMap = new HashMap<String, Colleague>();
      interMap = new HashMap<String, String>();
      }

      @Override
      public void Register(String colleagueName, Colleague colleague) {
      colleagueMap.put(colleagueName, colleague);

      if (colleague instanceof Alarm) {
      interMap.put("Alarm", colleagueName);
      } else if (colleague instanceof CoffeeMachine) {
      interMap.put("CoffeeMachine", colleagueName);
      } else if (colleague instanceof TV) {
      interMap.put("TV", colleagueName);
      } else if (colleague instanceof Curtains) {
      interMap.put("Curtains", colleagueName);
      }
      }

      //具体中介者的核心方法
      //1. 根据得到消息,完成对应任务
      //2. 中介者在这个方法,协调各个具体的同事对象,完成任务
      @Override
      public void GetMessage(int stateChange, String colleagueName) {
      //处理闹钟发出的消息
      if (colleagueMap.get(colleagueName) instanceof Alarm) {
      if (stateChange == 0) {
      ((CoffeeMachine) (colleagueMap.get(interMap
      .get("CoffeeMachine")))).StartCoffee();
      ((TV) (colleagueMap.get(interMap.get("TV")))).StartTv();
      } else if (stateChange == 1) {
      ((TV) (colleagueMap.get(interMap.get("TV")))).StopTv();
      }

      } else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
      ((Curtains) (colleagueMap.get(interMap.get("Curtains"))))
      .UpCurtains();

      } else if (colleagueMap.get(colleagueName) instanceof TV) {//如果TV发现消息

      } else if (colleagueMap.get(colleagueName) instanceof Curtains) {
      //如果是以窗帘发出的消息,这里处理...
      }

      }

      @Override
      public void SendMessage() {

      }

      }

      //同事抽象类
      abstract class Colleague {
      private Mediator mediator;
      public String name;

      public Colleague(Mediator mediator, String name) {
      this.mediator = mediator;
      this.name = name;
      }

      public Mediator GetMediator() {
      return this.mediator;
      }

      public abstract void SendMessage(int stateChange);
      }

      //具体的同事类
      class Alarm extends Colleague {
      //构造器
      public Alarm(Mediator mediator, String name) {
      super(mediator, name);
      //在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
      mediator.Register(name, this);
      }

      public void SendAlarm(int stateChange) {
      SendMessage(stateChange);
      }

      @Override
      public void SendMessage(int stateChange) {
      //调用的中介者对象的getMessage
      this.GetMediator().GetMessage(stateChange, this.name);
      }
      }

      class CoffeeMachine extends Colleague {
      public CoffeeMachine(Mediator mediator, String name) {
      super(mediator, name);
      mediator.Register(name, this);
      }

      @Override
      public void SendMessage(int stateChange) {
      this.GetMediator().GetMessage(stateChange, this.name);
      }

      public void StartCoffee() {
      System.out.println("It's time to startcoffee!");
      }

      public void FinishCoffee() {
      System.out.println("After 5 minutes!");
      System.out.println("Coffee is ok!");
      SendMessage(0);
      }
      }

      class Curtains extends Colleague {

      public Curtains(Mediator mediator, String name) {
      super(mediator, name);
      mediator.Register(name, this);
      }

      @Override
      public void SendMessage(int stateChange) {
      this.GetMediator().GetMessage(stateChange, this.name);
      }

      public void UpCurtains() {
      System.out.println("I am holding Up Curtains!");
      }
      }

      class TV extends Colleague {
      public TV(Mediator mediator, String name) {
      super(mediator, name);
      mediator.Register(name, this);
      }

      @Override
      public void SendMessage(int stateChange) {
      this.GetMediator().GetMessage(stateChange, this.name);
      }

      public void StartTv() {
      System.out.println("It's time to StartTv!");
      }

      public void StopTv() {
      System.out.println("StopTv!");
      }
      }

      public class Client {
      public static void main(String[] args) {
      //创建一个中介者对象
      Mediator mediator = new ConcreteMediator();

      //创建Alarm对象并且加入到ConcreteMediator对象的HashMap
      Alarm alarm = new Alarm(mediator, "alarm");

      //创建了CoffeeMachine对象并且加入到ConcreteMediator对象的HashMap
      CoffeeMachine coffeeMachine = new CoffeeMachine(mediator,
      "coffeeMachine");

      //创建Curtains并且加入到ConcreteMediator对象的HashMap
      Curtains curtains = new Curtains(mediator, "curtains");
      TV tV = new TV(mediator, "TV");

      //让闹钟发出消息
      alarm.SendAlarm(0);
      coffeeMachine.FinishCoffee();
      alarm.SendAlarm(1);
      }
      }
  • 中介者模式的注意事项和细节:

    • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦。
    • 减少类间依赖,降低了耦合,符合迪米特原则。
    • 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响。
    • 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意。

3.3.7 备忘录模式

  • 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

  • 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。

  • 备忘录模式是一种对象行为型模式,其主要优点如下:

    • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
    • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
    • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
  • 主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

  • 备忘录模式的主要角色如下:

    • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。

    • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。

    • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

      image-20210811110902331
  • 使用备忘录模式解决游戏角色状态恢复问题:

    • 游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。

      image-20210811112015478
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      class Originator {
      private String state;//状态信息

      public String getState() {
      return state;
      }

      public void setState(String state) {
      this.state = state;
      }

      //编写一个方法,可以保存一个状态对象 Memento
      //因此编写一个方法,返回 Memento
      public Memento saveStateMemento() {
      return new Memento(state);
      }

      //通过备忘录对象,恢复状态
      public void getStateFromMemento(Memento memento) {
      state = memento.getState();
      }
      }

      class Memento {
      private String state;

      //构造器
      public Memento(String state) {
      super();
      this.state = state;
      }

      public String getState() {
      return state;
      }
      }

      class Caretaker {
      //在List 集合中会有很多的备忘录对象
      private List<Memento> mementoList = new ArrayList<Memento>();

      public void add(Memento memento) {
      mementoList.add(memento);
      }

      //获取到第index个Originator的备忘录对象(即保存状态)
      public Memento get(int index) {
      return mementoList.get(index);
      }
      }

      public class Client {
      public static void main(String[] args) {
      Originator originator = new Originator();
      Caretaker caretaker = new Caretaker();

      originator.setState(" 状态#1 攻击力 100 ");
      //保存了当前的状态
      caretaker.add(originator.saveStateMemento());

      originator.setState(" 状态#2 攻击力 80 ");
      caretaker.add(originator.saveStateMemento());

      originator.setState(" 状态#3 攻击力 50 ");
      caretaker.add(originator.saveStateMemento());

      System.out.println("当前的状态是 =" + originator.getState());

      //希望得到状态 1, 将 originator 恢复到状态1
      originator.getStateFromMemento(caretaker.get(0));
      System.out.println("恢复到状态1");
      System.out.println("当前的状态是 =" + originator.getState());
      }
      }

      执行结果:

3.3.8 状态模式

  • 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。

  • 状态模式是一种对象行为型模式,其主要优点如下:

    • 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
    • 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
    • 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
  • 状态模式的主要缺点如下:

    • 状态模式的使用必然会增加系统的类与对象的个数。
    • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
    • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。
  • 状态模式包含以下主要角色:

    • 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。

    • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。

    • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

      image-20210811113110559
  • 状态模式解决APP抽奖问题:

    • 假如每参加一次这个活动要扣除用户50积分,中奖概率 是10%。

    • 奖品数量固定,抽完就不能抽奖。

    • 活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完。

    • 活动的四个状态转换关系图如下:

      Snipaste_2021-08-11_11-34-00 image-20210811114347353
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      226
      227
      228
      229
      230
      231
      232
      233
      234
      235
      236
      237
      238
      239
      240
      241
      242
      243
      244
      245
      246
      247
      248
      249
      250
      251
      252
      253
      254
      255
      256
      257
      258
      259
      260
      261
      262
      263
      264
      265
      abstract class State {
      // 扣除积分 - 50
      public abstract void deductMoney();

      // 是否抽中奖品
      public abstract boolean raffle();

      // 发放奖品
      public abstract void dispensePrize();
      }

      /**
      * 可以抽奖的状态
      */
      class CanRaffleState extends State {
      RaffleActivity activity;

      public CanRaffleState(RaffleActivity activity) {
      this.activity = activity;
      }

      //已经扣除了积分,不能再扣
      @Override
      public void deductMoney() {
      System.out.println("已经扣取过了积分");
      }

      //可以抽奖, 抽完奖后,根据实际情况,改成新的状态
      @Override
      public boolean raffle() {
      System.out.println("正在抽奖,请稍等!");
      Random r = new Random();
      int num = r.nextInt(10);
      // 10%中奖机会
      if(num == 0){
      // 改变活动状态为发放奖品 context
      activity.setState(activity.getDispenseState());
      return true;
      }else{
      System.out.println("很遗憾没有抽中奖品!");
      // 改变状态为不能抽奖
      activity.setState(activity.getNoRafflleState());
      return false;
      }
      }

      // 不能发放奖品
      @Override
      public void dispensePrize() {
      System.out.println("没中奖,不能发放奖品");
      }
      }

      /**
      * 不能抽奖状态
      */
      class NoRaffleState extends State {
      // 初始化时传入活动引用,扣除积分后改变其状态
      RaffleActivity activity;

      public NoRaffleState(RaffleActivity activity) {
      this.activity = activity;
      }

      // 当前状态可以扣积分, 扣除后,将状态设置成可以抽奖状态
      @Override
      public void deductMoney() {
      System.out.println("扣除50积分成功,您可以抽奖了");
      activity.setState(activity.getCanRaffleState());
      }

      // 当前状态不能抽奖
      @Override
      public boolean raffle() {
      System.out.println("扣了积分才能抽奖喔!");
      return false;
      }

      // 当前状态不能发奖品
      @Override
      public void dispensePrize() {
      System.out.println("不能发放奖品");
      }
      }

      /**
      * 发放奖品的状态
      */
      class DispenseState extends State {

      // 初始化时传入活动引用,发放奖品后改变其状态
      RaffleActivity activity;

      public DispenseState(RaffleActivity activity) {
      this.activity = activity;
      }

      @Override
      public void deductMoney() {
      System.out.println("不能扣除积分");
      }

      @Override
      public boolean raffle() {
      System.out.println("不能抽奖");
      return false;
      }

      //发放奖品
      @Override
      public void dispensePrize() {
      if(activity.getCount() > 0){
      System.out.println("恭喜中奖了");
      // 改变状态为不能抽奖
      activity.setState(activity.getNoRafflleState());
      }else{
      System.out.println("很遗憾,奖品发送完了");
      // 改变状态为奖品发送完毕, 后面我们就不可以抽奖
      activity.setState(activity.getDispensOutState());
      //System.out.println("抽奖活动结束");
      //System.exit(0);
      }

      }
      }

      /**
      * 奖品发放完毕状态
      * 说明,当我们activity 改变成 DispenseOutState, 抽奖活动结束
      */
      class DispenseOutState extends State {

      // 初始化时传入活动引用
      RaffleActivity activity;

      public DispenseOutState(RaffleActivity activity) {
      this.activity = activity;
      }

      @Override
      public void deductMoney() {
      System.out.println("奖品发送完了,请下次再参加");
      }

      @Override
      public boolean raffle() {
      System.out.println("奖品发送完了,请下次再参加");
      return false;
      }

      @Override
      public void dispensePrize() {
      System.out.println("奖品发送完了,请下次再参加");
      }
      }

      /**
      * 抽奖活动
      */
      class RaffleActivity {

      // state 表示活动当前的状态,是变化
      State state = null;
      // 奖品数量
      int count = 0;

      // 四个属性,表示四种状态
      State noRafflleState = new NoRaffleState(this);
      State canRaffleState = new CanRaffleState(this);

      State dispenseState = new DispenseState(this);
      State dispensOutState = new DispenseOutState(this);

      //构造器
      //1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
      //2. 初始化奖品的数量
      public RaffleActivity(int count) {
      this.state = getNoRafflleState();
      this.count = count;
      }

      //扣分, 调用当前状态的 deductMoney
      public void debuctMoney(){
      state.deductMoney();
      }

      //抽奖
      public void raffle(){
      // 如果当前的状态是抽奖成功
      if(state.raffle()){
      //领取奖品
      state.dispensePrize();
      }
      }

      public State getState() {
      return state;
      }

      public void setState(State state) {
      this.state = state;
      }

      //这里请大家注意,每领取一次奖品,count--
      public int getCount() {
      int curCount = count;
      count--;
      return curCount;
      }

      public void setCount(int count) {
      this.count = count;
      }

      public State getNoRafflleState() {
      return noRafflleState;
      }

      public void setNoRafflleState(State noRafflleState) {
      this.noRafflleState = noRafflleState;
      }

      public State getCanRaffleState() {
      return canRaffleState;
      }

      public void setCanRaffleState(State canRaffleState) {
      this.canRaffleState = canRaffleState;
      }

      public State getDispenseState() {
      return dispenseState;
      }

      public void setDispenseState(State dispenseState) {
      this.dispenseState = dispenseState;
      }

      public State getDispensOutState() {
      return dispensOutState;
      }

      public void setDispensOutState(State dispensOutState) {
      this.dispensOutState = dispensOutState;
      }
      }

      /**
      * 状态模式测试类
      */
      public class Client {
      public static void main(String[] args) {
      // 创建活动对象,奖品有1个奖品
      RaffleActivity activity = new RaffleActivity(1);

      // 我们连续抽300次奖
      for (int i = 0; i < 30; i++) {
      System.out.println("--------第" + (i + 1) + "次抽奖----------");
      // 参加抽奖,第一步点击扣除积分
      activity.debuctMoney();
      // 第二步抽奖
      activity.raffle();
      }
      }
      }
  • 状态模式的注意事项和细节:

    • 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中。
    • 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错。
    • 符合“开闭原则”。容易增删状态。
    • 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度。
    • 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。

3.3.9 策略模式

  • 策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

  • 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。

  • 策略模式的主要优点如下:

    • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
    • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
    • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
    • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
    • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
  • 主要缺点如下:

    • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
    • 策略模式造成很多的策略类,增加维护难度。
  • 策略模式的主要角色如下:

    • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。

    • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。

    • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

  • 使用策略模式编写鸭子项目:

    • 有各种鸭子(比如野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如叫、飞行等)

    • 显示鸭子的信息。

      image-20210811151811824
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      interface FlyBehavior {
      void fly(); // 子类具体实现
      }

      class GoodFlyBehavior implements FlyBehavior {
      @Override
      public void fly() {
      System.out.println(" 飞翔技术高超 ~~~");
      }
      }

      class NoFlyBehavior implements FlyBehavior{
      @Override
      public void fly() {
      System.out.println(" 不会飞翔 ");
      }
      }

      class BadFlyBehavior implements FlyBehavior {
      @Override
      public void fly() {
      System.out.println(" 飞翔技术一般 ");
      }
      }

      abstract class Duck {
      //属性, 策略接口
      FlyBehavior flyBehavior;

      public Duck() {
      }

      public abstract void display();//显示鸭子信息

      public void quack() {
      System.out.println("鸭子嘎嘎叫~~");
      }

      public void swim() {
      System.out.println("鸭子会游泳~~");
      }

      public void fly() {
      //改进
      if(flyBehavior != null) {
      flyBehavior.fly();
      }
      }

      public void setFlyBehavior(FlyBehavior flyBehavior) {
      this.flyBehavior = flyBehavior;
      }
      }

      class WildDuck extends Duck {
      //构造器,传入FlyBehavor 的对象
      public WildDuck() {
      flyBehavior = new GoodFlyBehavior();
      }


      @Override
      public void display() {
      System.out.println(" 这是野鸭 ");
      }

      }

      class ToyDuck extends Duck{
      public ToyDuck() {
      flyBehavior = new NoFlyBehavior();
      }

      @Override
      public void display() {
      System.out.println("玩具鸭");
      }

      //需要重写父类的所有方法
      public void quack() {
      System.out.println("玩具鸭不能叫~~");
      }

      public void swim() {
      System.out.println("玩具鸭不会游泳~~");
      }
      }

      class PekingDuck extends Duck {
      //假如北京鸭可以飞翔,但是飞翔技术一般
      public PekingDuck() {
      flyBehavior = new BadFlyBehavior();
      }

      @Override
      public void display() {
      System.out.println("~~北京鸭~~~");
      }
      }

      public class Client {
      public static void main(String[] args) {
      WildDuck wildDuck = new WildDuck();
      wildDuck.fly();

      ToyDuck toyDuck = new ToyDuck();
      toyDuck.fly();

      PekingDuck pekingDuck = new PekingDuck();
      pekingDuck.fly();

      //动态改变某个对象的行为, 北京鸭 不能飞
      pekingDuck.setFlyBehavior(new NoFlyBehavior());
      System.out.println("北京鸭的实际飞翔能力");
      pekingDuck.fly();
      }
      }
  • 策略模式的注意事项和细节:

    • 策略模式的关键是:分析项目中变化部分与不变部分。
    • 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性。
    • 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else)。
    • 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
    • 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大。

3.3.10 职责链模式

  • 责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

  • 责任链模式是一种对象行为型模式,其主要优点如下:

    • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
    • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
    • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
    • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
    • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
  • 主要缺点如下:

    • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
    • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
    • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
  • 职责链模式主要包含以下角色:

    • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。

    • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。

    • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

      image-20210811152954265
  • 使用职责链模式解决OA系统采购审批:

    • 采购员采购教学器材。

    • 如果金额 小于等于5000, 由教学主任审批 (0<=x<=5000)。

    • 如果金额 小于等于10000, 由院长审批 (5000<x<=10000)。

    • 如果金额 小于等于30000, 由副校长审批 (10000<x<=30000)。

    • 如果金额 超过30000以上,有校长审批 ( 30000<x)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      //请求类
      class PurchaseRequest {
      private int type = 0; //请求类型
      private float price = 0.0f; //请求金额
      private int id = 0;
      //构造器
      public PurchaseRequest(int type, float price, int id) {
      this.type = type;
      this.price = price;
      this.id = id;
      }
      public int getType() {
      return type;
      }
      public float getPrice() {
      return price;
      }
      public int getId() {
      return id;
      }
      }

      abstract class Approver {
      Approver approver; //下一个处理者
      String name; // 名字

      public Approver(String name) {
      // TODO Auto-generated constructor stub
      this.name = name;
      }

      //下一个处理者
      public void setApprover(Approver approver) {
      this.approver = approver;
      }

      //处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
      public abstract void processRequest(PurchaseRequest purchaseRequest);
      }

      class DepartmentApprover extends Approver {
      public DepartmentApprover(String name) {
      super(name);
      }

      @Override
      public void processRequest(PurchaseRequest purchaseRequest) {
      if(purchaseRequest.getPrice() <= 5000) {
      System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
      }else {
      approver.processRequest(purchaseRequest);
      }
      }
      }

      class CollegeApprover extends Approver {
      public CollegeApprover(String name) {
      super(name);
      }

      @Override
      public void processRequest(PurchaseRequest purchaseRequest) {
      if(purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) {
      System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
      }else {
      approver.processRequest(purchaseRequest);
      }
      }
      }

      class SchoolMasterApprover extends Approver {
      public SchoolMasterApprover(String name) {
      super(name);
      }

      @Override
      public void processRequest(PurchaseRequest purchaseRequest) {
      if(purchaseRequest.getPrice() > 30000) {
      System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
      }else {
      approver.processRequest(purchaseRequest);
      }
      }
      }

      class ViceSchoolMasterApprover extends Approver {
      public ViceSchoolMasterApprover(String name) {
      super(name);
      }

      @Override
      public void processRequest(PurchaseRequest purchaseRequest) {
      if(purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {
      System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
      }else {
      approver.processRequest(purchaseRequest);
      }
      }
      }

      public class Client {
      public static void main(String[] args) {
      //创建一个请求
      PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);

      //创建相关的审批人
      DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
      CollegeApprover collegeApprover = new CollegeApprover("李院长");
      ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
      SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");

      //需要将各个审批级别的下一个设置好 (处理人构成环形: )
      departmentApprover.setApprover(collegeApprover);
      collegeApprover.setApprover(viceSchoolMasterApprover);
      viceSchoolMasterApprover.setApprover(schoolMasterApprover);
      schoolMasterApprover.setApprover(departmentApprover);

      departmentApprover.processRequest(purchaseRequest);
      viceSchoolMasterApprover.processRequest(purchaseRequest);
      }
      }
  • 职责链模式的注意事项和细节:

    • 将请求和处理分开,实现解耦,提高系统的灵活性。
    • 简化了对象,使对象不需要知道链的结构。
    • 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能。
    • 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂。
    • 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器。