0503-设计模式

前言

一、类结构关系

        类(对象)之间通常分为两种结构关系:①一般<—->特殊关系、②整体<—->部分关系;从这两种类的结构关系中又可以细分出来很多种具体的类(对象)关系。

  • 一般<—->特殊关系

    1. 继承关系(extends):在 Java 中通过关键字 extends 来表示继承关系;继承关系在UML图中用实线+实心箭头表示;子类是一种特殊的父类,父类比子类有更大的广泛性也即父类指代的是更泛、更一般的概念,而子类则代表的是一种更特殊、更具体的特指;小结:继承关系封装了这样一种逻辑:「XX(子类)是一种XX(父类)」,只要这种关系能说得通,就可以考虑用继承关系来封装它
    2. 实现关系(implements):在 Java 中通过关键字 implements 来表示实现关系,一个类实现一个接口,其实接口也是一种特殊的类;在这里,接口也即父类。在 UML 类图中用虚线 + 空心箭头 表示。
  • 整体<—->部分关系

    1. 依赖 (Dependency):通常指的是一种单向的关系,指的是调用关系。在 UML 中用虚线 + 箭头来表示。

      比如:具体到代码中往往表现为一个类的方法里面的参数是另一个类的某个具体的对象。换句话说,如果 A 依赖于 B,那么代码中 B 表现为 A 的方法参数、局部变量或静态方法调用等。

    2. 关联 (Association):在Java中指两个类直接的对应关系,在ORM框架中这种关系总结为三种:一对一、一对多、多对多;UML 中用实线 + 箭头来表示。

    3. 聚合 (Aggregation):聚合关系更加强调整体和部分的关系,通常,我们会说 xx 是 xx 的一部分,只要说得通,那么他们之间就是聚合关系。但是,有一点需要注意的是,聚合关系中的部分是相互独立的,并不是说部分离开了整体不能独立存在。在 UML 中用空心菱形+实线表示。

      比如:主板是电脑的一部分,显卡是电脑的一部分,电池是电脑的一部分,很显然这都是说得通的。

    4. 组合 (Composition):组合关系与聚合关系其实大同小异,最大的不同在于,组合关系中的整体和部分密不可分,在 UML 中用实心菱形和实线表示。

      比如:人体是由头、手、脚等组成的,一个人的脑袋不可能说既属于自己又属于别人,手和脚也是一样的道理。你的头和手、脚等离开了你的身体也没有什么存在的意义。

第一章 设计模式概述

1.1 设计模式的理念

        设计模式:这个术语最初并不是出现在软件设计中,而是被用于建筑领域的设计中。

        1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,在本教程中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的“四人组”(Gang of Four,GoF)匿名著称。

        直到今天,狭义的设计模式还是所介绍的 23 种经典设计模式

1.2 设计模式的作用

        在软件开发中,设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强

1.3 软件开发设计原则

  1. 单一职责原则:不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

  2. 里氏替换原则(Liskov Substitution Principle)

            里氏代换原则面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

            里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

            里氏代换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

  3. 依赖倒转原则(Dependence Inversion Principle):这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

  4. 接口隔离原则(Interface Segregation Principle):这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

  5. 迪米特法则(最少知道原则)(Demeter Principle)
            就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

            最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

  6. 合成复用原则(Composite Reuse Principle):原则是尽量首先使用关联/聚合的方式,而不是使用继承。

1.4 设计模型种类

82asII.png

第二章 创建型模式

创建型模式的主要功能的软件开发中对象的创建工作,主要作用是将对象的创建和对象的使用向分离;

2.1 简单工厂模式

1. 概述

        工厂模式的作用就是创建产品;在Java中就是new一个对象:Java是面向对象的开发语言,在业务开发中需要定义各种类型的对象;如果在业务代码中仅仅为了使用特定功能而负责这个对象的创建,这个对象的创建工作对于业务功能来说是多余的操作,而且代码耦合严重;工厂模式的解决了对象的创建和使用分离,只需要在业务功能中引入工厂角色就能获取到所需要的对象,而无需关系对象的创建过程

        简单工厂模式是通过方法设计实现对象的创建,所以根据方法的签名(修饰符、方法名称、方法参数)不同,简单工厂模式分为静态工厂(工厂类中提供静态方法创建对象,生产对象要直接使用工厂类调用)和实例工厂(工厂类中定义实例方法创建对象,生成对象需要用工厂对象调用);在Java中的方法设计灵活,对应的简单工厂的方式也有很多种;

2. 代码演示-参数式工厂

  • 静态工厂:业务场景描述:工厂类要创建Car类型的对象,该类型有两款产品实现宝马BMCarQQCar,需要使用简单工厂模式创建该类型的对象;简单工厂的设计思路:在工厂类中定义创建Car类型的静态方法,根据不同的参数创建不同的Car对象;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class SimpleFactory01 {

    private SimpleFactory01() {
    }

    public static Car createCar(String type) {
    if ("BM".equals(type)) {
    return new BMCar(type);
    } else if ("QQ".equals(type)) {
    return new QQCar(type);
    }
    return null;
    }
    }
  • 实例工厂:与静态工厂的区别是创建对象的方法是实例方法,所以在使用实例工厂创建对象之前首先需要实例化工厂对象;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class SimpleFactory02 {
    public Car createCar(String type) {
    if ("BM".equals(type)) {
    return new BMCar(type);
    } else if ("QQ".equals(type)) {
    return new QQCar(type);
    }
    return null;
    }
    }

3. 代码演示-方法式工厂

该方式的工厂模式是对上一种参数类型的工厂的改进,在上一种创建方式中类型标识需要调用者传递,有参数异常的风险,改进方法是在类中定义不同的方法名称,根据方法名称返回指定的对象;

  • 静态工厂:是因为工厂类中创建对象的方法是静态方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class SimpleFactory03 {

    private SimpleFactory03() {
    }

    public static Car createQQCar() {
    return new QQCar("QQ");
    }

    public static Car createBMCar() {
    return new QQCar("BM");
    }
    }
  • 实例工厂:工厂中的实例方法完成对象的创建;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class SimpleFactory04 {
    public Car createQQCar() {
    return new QQCar("QQ");
    }

    public Car createBMCar() {
    return new QQCar("BM");
    }
    }

4. 代码演示-反射

以上两种的工厂模式对创建对象的类型和数量都有限制,如果使用类的全限定名称作为对象的表示,使用反射创建对象,理论上可以解决指定类型的扩展问题;

  • 静态工厂

    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 SimpleFactory05 {

    private SimpleFactory05() {
    }

    public static Car createCar(String productBeanName) {
    try {
    return (Car) Class.forName(productBeanName).newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }

    public static Car createCar(Class<?> clz) {
    try {
    return (Car) clz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
  • 实例工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class SimpleFactory06 {
    public Car createCar(String productBeanName) {
    try {
    return (Car) Class.forName(productBeanName).newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }

    public Car createCar(Class<?> clz) {
    try {
    return (Car) clz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }

5. 代码演示-泛型加反射

仅仅使用反射创建对象时候,对创建对象的类型仍然是无法扩展的,可以是引用泛型对创建的对象的类型进行扩展

  • 静态工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class SimpleFactory07 {

    private SimpleFactory07() {
    }

    public static <T> T create(Class<T> t) {
    try {
    return t.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
  • 实例工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class SimpleFactory08 {
    public <T> T create(Class<T> t) {
    try {
    return (T) t.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }

2.2 工厂方法模式

1. 概述

        简单工厂模式出现的问题是:在没用反射和泛型的前提下,如过需要添加新的产品,则需要修改原来的代码,对扩展和修改不友好;为解决简单工厂模式出现的问题,可以使用工厂方法模式:借鉴模板方法模式的思路,定义创建对象的抽象方法,在工厂类中具体对象的创建延迟到工厂的子类实现,即由子类来决定应该实例化(创建)哪一个类。

  • 工厂方法中相关对象说明

    名称 作用说明
    抽象工厂 对创建对象的方式进行抽象,并不负责具体对象的创建
    工厂方法 是对抽象方法的实现,主要作用的是创建该工厂对应的对象
    抽象产品 规定工厂创建对象的产品类型
    具体产品 是抽象产品的一个实现类,是工厂方法创建出的具体对象

2. 案例演示

  • 抽象产品:假设工厂类要创建的产品的类型是IProduct产品

    1
    2
    3
    public interface IProduct {
    void show();
    }
  • 具体产品:IProduct类型的产品有多中不同的实现,比如ProductA、ProductB。。。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ProductA implements IProduct{
    public void show() {
    System.out.println(" 创建产品A成功 ");
    }
    }
    public class ProductB implements IProduct {
    public void show() {
    System.out.println(" 创建产品B成功 ");
    }
    }
    public class ProductC implements IProduct {
    public void show() {
    System.out.println(" 创建产品C成功 ");
    }
    }
  • 抽象工厂:需要定义个工厂,对IProduct对象的创建进行抽象,但是不会创建具体的产品

    1
    2
    3
    public interface Factory {
    IProduct create();
    }
  • 在工厂类的子类中实现抽象方法,在子类工厂中实现对具体产品的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class ProductAFactory implements Factory {
    @Override
    public IProduct create() {
    return new ProductA();
    }
    }
    public class ProductBFactory implements Factory {
    @Override
    public IProduct create() {
    return new ProductB();
    }
    }
    public class ProductCFactory implements Factory {
    @Override
    public IProduct create() {
    return new ProductC();
    }
    }
  • 使用工厂类创建对象

    1
    2
    3
    4
    // 创建ProductA类型的对象
    Factory factory = new ProductAFactory();
    IProduct productA. = factory.create();
    productA.show();

2.3 抽象工厂模式

1. 概述

        在工厂方法模式中,我们使用一个工厂创建一种类型的产品。但是有时候我们需要一个工厂能够提供多个类型的产品(产品族),而且每一种类型的产品可以有多个具体产品(产品等级),可以使用抽象工厂模式:抽象工厂模式我理解的是对工厂方法的扩展,抽象工厂用于创建多种类型的多种产品的一中对象创建方式;

  • 在讲解抽象工厂模式之前,我们需要理清两个概念:

    • 产品等级结构。产品的等级结构也就是产品的继承结构。例如一个为空调的抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个抽象类空调和他的子类就构成了一个产品等级结构。

    • 产品族。产品族是在抽象工厂模式中的。在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调。海尔冰箱,那么海尔空调则位于空调产品族中。

  • 抽象工厂中相关对象说明

名称 作用说明
产品族 定义工厂类需要创建的产品类型:比如手机类型和电脑类型
产品等级 定义每种类型的具体产品:比如手机又小米手机和华为手机,电脑有小米电脑和华为电脑
工厂 在工厂类中抽象出创建多中类型的产品的方法:比如要创建手机类型的产品和电脑类型的产品
抽象工厂 抽象工厂是对工厂的进一步抽象:
- 比如可以抽象为小米工厂(生产小米手机和小米电脑)和华为工厂(生产华为手机和华为电脑)
- 或者抽象为高端工厂(生产华为手机和小米电脑)和低端工厂(生产小米手机和华为电脑)
工厂类 类似于工厂方法中创建对象的具体工厂的实现,没一个工厂类只能创建特定类型的产品

2. 案例演示

  • 需要创建两种类型的产品:比如有两个产品族:手机和电脑

    1
    2
    3
    4
    5
    6
    public interface NetBook {
    }

    public interface NetPhone {

    }
    • 电脑类型的产品有两个产品等级:小米笔记本电脑和华为笔记本电脑

      1
      2
      3
      4
      5
      public class HWNetBook implements NetBook{
      }

      public class XMNetBook implements NetBook{
      }
    • 手机类型的产品有两个产品等级:小米手机和华为手机

      1
      2
      3
      4
      5
      6
      public class HWNetPhone implements NetPhone{
      }

      public class XMNetPhone implements NetPhone{

      }
  • 此时我们需要使用到抽象工厂创建上面的四个产品:而四个产品属于两个产品族,在抽象工厂中定义的不是具体的产品族的创建方式;如首先定义一个工厂规定创建手机和电脑的方法

    1
    2
    3
    4
    5
    public interface Factory {
    NetBook creatBook();

    NetPhone createPhone();
    }
  • 根据产品族和产品等级对工厂进一步抽象:说明这些工厂是为了创建什么样的产品

    • 抽象方案一:根据产品类型不同抽象为小米工厂(生产小米手机和小米电脑)和华为工厂(生产华为手机和华为电脑)

      1
      2
      3
      4
      5
      public interface HWAbsFactory extends Factory {
      }

      public interface XMAbsFactory extends Factory {
      }
    • 抽象方案二:根据产品价格不同抽象为高端工厂(小米电脑和华为手机)和低端工厂(小米手机和华为电脑)

      1
      2
      3
      4
      5
      public interface HightAbsFactory extends Factory {
      }

      public interface LowAbsFactory extends Factory {
      }
  • 创建具体的工厂实现抽象出来的工厂用于创建具体的产品对象

    • 根据方案一:实现抽象工厂创建华为工厂的实现创建华为手机和华为电脑,创建小米工厂的实现创建小米手机和小米电脑

      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 HWAbsFactoryImpl implements HWAbsFactory {
      @Override
      public NetBook creatBook() {
      return new HWNetBook();
      }

      @Override
      public NetPhone createPhone() {
      return new HWNetPhone();
      }
      }
      /**
      * 小米工厂的实现
      */
      public class XMAbsFactoryImpl implements XMAbsFactory {
      @Override
      public NetBook creatBook() {
      return new XMNetBook();
      }

      @Override
      public NetPhone createPhone() {
      return new XMNetPhone();
      }
      }
    • 根据方案二:实现抽象工厂创建高端工厂的实现创建华为手机和小米电脑,创建低端工厂的实现创建小米手机和华为电脑

      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 HightAbsFactoryImpl implements HightAbsFactory {

      @Override
      public NetBook creatBook() {
      return new HWNetBook();
      }

      @Override
      public NetPhone createPhone() {
      return new XMNetPhone();
      }
      }
      /**
      * 低端工厂的实现
      */
      public class LowAbsFactoryImpl implements LowAbsFactory {
      @Override
      public NetBook creatBook() {
      return new HWNetBook();
      }

      @Override
      public NetPhone createPhone() {
      return new XMNetPhone();
      }
      }

2.4 单例模式

1、单例模式学习要求

  • 使用CountDownLatch和Semaphore模拟并发

    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
    public class TestSingle {

    private static final Integer TOTAL_COUNT = 10000;
    private static final Integer THREAD_COUNT = 500;

    private final Set<Integer> hash = new HashSet<>();

    /**
    * 并发模拟测试单例效率与安全性
    */
    @Test
    public void test() throws InterruptedException {
    // 创建线程池
    ExecutorService service = Executors.newCachedThreadPool();
    // 定义信号量
    final Semaphore semaphore = new Semaphore(THREAD_COUNT);
    //定义计数器闭锁
    final CountDownLatch countDownLatch = new CountDownLatch(TOTAL_COUNT);
    long start = System.currentTimeMillis();
    for (int i = 0; i < TOTAL_COUNT; i++) {
    //将需要测试的业务全部放入线程池
    service.execute(() -> {
    try {
    //当线程允许被执行时才执行
    semaphore.acquire();
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    hash.add(LazySyncBlock.getInstance().hashCode());
    //线程执行完后释放
    semaphore.release();
    //每次线程执行完之后,countdown一次
    countDownLatch.countDown();
    });
    }
    try {
    //该方法可以保证clientTotal减为0.既可以保证所有的线程都执行完毕了
    countDownLatch.await();
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    //所有的线程执行完了后,关闭线程池
    service.shutdown();
    System.out.println("创建实例数=" + hash.size());
    System.out.println("创建实例耗时=" + (System.currentTimeMillis() - start));
    }
    }
  • 使用反射创建对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class TestSingle {
    @Test
    public void staticFinal() throws Exception {
    Class<StaticFinal> finalClass = StaticFinal.class;
    Constructor<StaticFinal> ct = finalClass.getDeclaredConstructor();
    ct.setAccessible(true);

    StaticFinal single1 = ct.newInstance();
    StaticFinal single2 = ct.newInstance();
    Assert.assertEquals(single1, single2);
    }
    }
  • 序列化创建对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class TestSingle {    
    @Test
    public void testXuliehua() {
    String path = "StaticFinal";
    try (
    FileOutputStream objStream = new FileOutputStream(path);
    ObjectOutputStream outputObjStream = new ObjectOutputStream(objStream);
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
    ) {
    StaticFinal instance1 = StaticFinal.getInstance();
    StaticFinal instance2 = null;
    outputObjStream.writeObject(instance1);
    outputObjStream.flush();

    instance2 = (StaticFinal) objectInputStream.readObject();
    Assert.assertEquals(instance1, instance2);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
  • synchronized关键字

  • volatile关键字

2、单例的九种创建方式

① 静态常量单例:在加载类的时候完成实例的创建

1
2
3
4
5
6
7
8
9
10
public class StaticFinal {
private static final StaticFinal INSTANCE = new StaticFinal();

private StaticFinal() {
}

public static StaticFinal getInstance() {
return INSTANCE;
}
}

静态代码块:在加载类的时候完成实例的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticBlock {
private StaticBlock() {
}

private static final StaticBlock INSTANCE;

static {
INSTANCE = new StaticBlock();
}

public static StaticBlock getInstance() {
return INSTANCE;
}
}

静态内部类:在加载类的时候完成实例的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazyInnerClass {
private LazyInnerClass() {

}

private static class Inner {
private static final LazyInnerClass INSTANCE = new LazyInnerClass();
}

public static LazyInnerClass getInstance() {
return Inner.INSTANCE;
}
}

④ 懒加载单例-饿汉式:在单线程环境中是安全的,在并发环境下存在严重的风险

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LazyGenerator {

private static LazyGenerator instance;

private LazyGenerator() {

}

public static LazyGenerator getInstance() {
if (null == instance) {
instance = new LazyGenerator();
}
return instance;
}
}

⑤ 懒加载单例-同步方法:上述单例在并发环境下有严重分析,所以在创建实例的方法上添加同步锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazySyncMethod {
private LazySyncMethod() {

}

private static LazySyncMethod instance;

public static synchronized LazySyncMethod getInstance() {
if (null == instance) {
instance = new LazySyncMethod();
}
return instance;
}
}

⑥ 懒加载单例-同步代码块:由于同步锁在并发环境消耗比较大,所以改为同步方法,只在创建对象时候添加同步锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LazySyncBlock {
private LazySyncBlock() {

}

private static LazySyncBlock instance;

public static LazySyncBlock getInstance() {
if (null == instance) {
synchronized (LazySyncBlock.class) {
instance = new LazySyncBlock();
}
}
return instance;
}
}

⑦ 懒加载单例-双重检查:单例对象是成员变量,由于指令重拍的原因,还是会导致创建出多个单例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LazyDoubleCheck {
static TestSingle l = new TestSingle();
private static LazyDoubleCheck instance;

private LazyDoubleCheck() {

}

public static LazyDoubleCheck getInstance() {
//先判断是否存在,不存在再加锁处理
if (instance == null) {
//在同一个时刻加了锁的那部分程序只有一个线程可以进入
synchronized (l) {
if (instance == null) {
instance = new LazyDoubleCheck();
}
}
}
return instance;
}
}

⑧ 懒加载单例-volatile:使用volatile关键字配合同步锁保证消除指令重排的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LazyVolitile {
private static volatile LazyVolitile instance;

private LazyVolitile() {

}

public static LazyVolitile getInstance() {
if (instance == null) {
synchronized (LazyVolitile.class) {
if (instance == null) {
instance = new LazyVolitile();
}
}
}
return instance;
}
}

关键字 volatile : 保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存

⑨ 单例- 枚举

1
2
3
4
5
6
7
public enum EnumSingle {
INSTANCE;

public EnumSingle getInstance() {
return INSTANCE;
}
}

3、破坏单例

  • 反射破坏单例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void staticFinal() throws Exception {
    Class<StaticFinal> finalClass = StaticFinal.class;
    Constructor<StaticFinal> ct = finalClass.getDeclaredConstructor();
    ct.setAccessible(true);

    StaticFinal single1 = ct.newInstance();
    StaticFinal single2 = ct.newInstance();
    Assert.assertEquals(single1, single2);
    }
    • 解决方案:反射创建对象是会调用对象的构造方法,需要为单例订单构造方法中添加显示:方式构造方法重复调用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class StaticFinal implements Serializable {
      private static final StaticFinal INSTANCE = new StaticFinal();

      private static boolean flag = false;

      private StaticFinal() {
      synchronized (StaticFinal.class) {
      if (flag) {
      throw new RuntimeException("单例对象不可以重复创建");
      }
      flag = true;
      }
      }

      public static StaticFinal getInstance() {
      return INSTANCE;
      }
      }


  • 序列化破坏单例:单例对象序列化然后写入到磁盘,再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,重新创建破坏了单例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void testXuliehua() {
    String path = "single";
    try (
    FileOutputStream objStream = new FileOutputStream(path);
    ObjectOutputStream outputObjStream = new ObjectOutputStream(objStream);
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
    ) {
    StaticFinal instance1 = StaticFinal.getInstance();
    StaticFinal instance2 = null;
    outputObjStream.writeObject(instance1);
    outputObjStream.flush();

    instance2 = (StaticFinal) objectInputStream.readObject();
    Assert.assertEquals(instance1, instance2);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    • 序列化创建对象源码

      • readObject()

        1
        2
        3
        4
        5
        public final Object readObject() throws IOException, ClassNotFoundException {
        //...
        Object obj = readObject0(false);
        // ...
        }
      • readObject0(false)

        1
        2
        3
        4
        5
        6
        7
        8
        /**
        * new Object.
        */
        final static byte TC_OBJECT = (byte)0x73;

        // 定位到这里:序列化创建对象
        case TC_OBJECT:
        return checkResolve(readOrdinaryObject(unshared));
      • readOrdinaryObject(unshared)

        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
        private Object readOrdinaryObject(boolean unshared) throws IOException {
        // ...

        // new了一个对象
        obj = desc.isInstantiable() ? desc.newInstance() : null;
        // ...
        // 如果序列化对象中有hasReadResolveMethod()方法
        if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
        {
        // 重点1:获取这个方法返回的对象
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
        rep = cloneArray(rep);
        }
        if (rep != obj) {
        // Filter the replacement object
        if (rep != null) {
        if (rep.getClass().isArray()) {
        filterCheck(rep.getClass(), Array.getLength(rep));
        } else {
        filterCheck(rep.getClass(), -1);
        }
        }

        // 把方法返回的对象的引用赋值给序列化的对象
        handles.setObject(passHandle, obj = rep);
        }
        }

        return obj;
        }
    • 序列化破坏单例解决方案:在readOrdinaryObject()源码中,如果类实现了readResolveMethod()方法,那就invoke,同时之前newInstance的obj引用readOrdinaryObject()返回的对象,在源码中找到这个方法的名称是readResolve()

      1
      2
      /** class-defined readResolve method, or null if none */
      private Method readResolveMethod;
      • 需要被序列化的类实现了Serializable接口,在Serializable接口的文档注释中也有说明

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        /**
        * This writeReplace method is invoked by serialization if the method
        * exists and it would be accessible from a method defined within the
        * class of the object being serialized. Thus, the method can have private,
        * protected and package-private access. Subclass access to this method
        * follows java accessibility rules. <p>
        *
        * Classes that need to designate a replacement when an instance of it
        * is read from the stream should implement this special method with the
        * exact signature.
        *
        * <PRE>
        * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
        * </PRE><p>
        */
        public interface Serializable {
        }


  • 标准单例

    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 StaticFinal implements Serializable {
    private static final StaticFinal INSTANCE = new StaticFinal();

    private static boolean flag = false;

    private StaticFinal() {
    synchronized (StaticFinal.class) {
    if (flag) {
    throw new RuntimeException("单例对象不可以重复创建");
    }
    flag = true;
    }
    }

    public static StaticFinal getInstance() {
    return INSTANCE;
    }

    /**
    * 防止单例被序列化破坏单例
    *
    * @return 序列化对象
    */
    public Object readResolve() {
    return INSTANCE;
    }
    }

4、单例的应用

  • 在Spring中的应用
  • 在JDK中的应用

5、单例模式的知识点

  1. 多线程的的使用 : 用于验证单例的线程安全

  2. synchronized, volatile 关键字的理解 : java基础

  3. 反射的基本使用 : 创建实例与方法调用

  4. 反序列化的原理的;理解 : readResolve()方法

    1
    2
    new ObjectInputStream(InputStream in);
    Object o = ObjectInputStream.readObject();
  5. TreadLoca线程单例 : 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全

  6. 反射破坏单例的解决方案

  7. 序列化破坏单例的解决方案

  8. ThreadLocal线程单例

2.5 建造者模式

1、概述

        当一个类的内部数据过于复杂的时候(通常是负责持有数据的类,比如Config、VO、PO、Entity…),要创建的话可能就需要了解这个类的内部结构,还有这些东西是怎么组织装配等一大坨乱七八糟的东西,这个时候就会增加学习成本而且会很混乱,这就是Builder模式的应用场景,Builder模式可以将一个类的构建和表示进行分离。

        创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使
得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。建造者有两种实现方式,根据使用 案例描述:

  1. 需要建造笔记本这种产品(笔记本中有各种CPU、主板、硬盘等等),但是这种类型的特点的没中品牌的笔记本其每部属性都是固定的,所以这种类型的对象的创建可以定义在建造者对象中,创建对象时候只需要说明需要的创建者;
  2. 第二种产品比如煎饼果子(内部属性有鸡蛋、火腿肠等等),每个对象的内部属性是不一定的,需要在创建时候单独制定,此时的建造者对象所创建的对象属性需要在创建的时候就指定;

2、代码演示

  1. 具体完整功能的创建者模式

    • 需要建造一个自行车对象,自信车对象有三个对象组成(框架、座椅、轮胎)

      1
      2
      3
      4
      5
      6
      7
      8
      9

      public class Bike {

      private IFrame frame;
      private ISeat seat;
      private ITire tire;

      // getter setter
      }
    • 因为需要不同的建造者对象建造不同的自行车,所以需要对建造者对象进行抽象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public interface Builder {
      void buildFrame();

      void buildSeat();

      void buildTire();

      Bike createBike();
      }
    • 此时定义两个自行车建造者:摩拜自行车和ofo自行车

      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
      // 具体 builder 类 
      public class MobikeBuilder implements Builder {
      private Bike mBike = new Bike();

      @Override
      public void buildFrame() {
      mBike.setFrame("AlloyFrame");
      }

      @Override
      public void buildSeat() {
      mBike.setSeat("DermisSeat");
      }

      @Override
      public void buildTire() {
      mBike.setTire("SolidTire");
      }

      @Override
      public Bike createBike() {
      return mBike;
      }
      }

      public class OfoBuilder implements Builder {
      private Bike mBike = new Bike();

      @Override
      public void buildFrame() {
      mBike.setFrame("CarbonFrame");
      }

      @Override
      public void buildSeat() {
      mBike.setSeat("RubberSeat");
      }

      @Override
      public void buildTire() {
      mBike.setTire("InflateTire");
      }

      @Override
      public Bike createBike() {
      return mBike;
      }
      }
    • 封装用建造者创建对象的指挥者

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class Director {
      private Builder mBuilder = null;

      public Director(Builder builder) {
      mBuilder = builder;
      }

      public Bike construct() {
      mBuilder.buildFrame();
      mBuilder.buildSeat();
      mBuilder.buildTire();
      return mBuilder.createBike();
      }
      }
    • 测试创建自行车对象:创建对象的时候只需要告诉指挥者需要用哪个建造者创建对象;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Client {
      public static void main(String[] args) {
      showBike(new OfoBuilder());
      showBike(new MobikeBuilder());
      }

      private static void showBike(Builder builder) {
      Director director = new Director(builder);
      Bike bike = director.construct();
      System.out.println("bike = " + bike);
      }
      }
  2. 根据对象使用者的特殊要求创建对象:是当一个类构造参数有多个,而且参数都是可选的时候

    • 举例需要创建一个电脑对象,电脑有很多属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class Computer {
      private String cpu;//必须
      private String ram;//必须
      private int usbCount;//可选
      private String keyboard;//可选
      private String display;//可选

      public Computer(Builder builder) {
      this.cpu = builder.getCpu();
      this.ram = builder.getRam();
      this.usbCount = builder.getUsbCount();
      this.keyboard = builder.getKeyboard();
      this.display = builder.getDisplay();
      }
      }
    • 定义一个建造者对象:建造者对象也拥有和对象相同的属性,不然无法给对象传递参数

      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
      public class Builder {
      private String cpu;//可选
      private String ram;//可选
      private int usbCount;//可选
      private String keyboard;//可选
      private String display;//可选

      public static Builder builder() {
      return new Builder();
      }

      public Builder ram(String ram) {
      this.ram = ram;
      return this;
      }

      public Builder cup(String cup) {
      this.cpu = cup;
      return this;
      }

      public Builder usbCount(int usbCount) {
      this.usbCount = usbCount;
      return this;
      }

      public Builder keyboard(String keyboard) {
      this.keyboard = keyboard;
      return this;
      }

      public Builder display(String display) {
      this.display = display;
      return this;
      }

      public Computer build() {
      return new Computer(this);
      }

      // getter setter
      }
    • 测试创建对象

      1
      2
      3
      4
      5
      6
      7
      8
      public class Client {
      public static void main(String[] args) {
      Computer build = Builder.builder()
      .cup("cup")
      .build();
      System.out.println(build);
      }
      }

2.6 原型设计模式

1、概述

        原型模式的思路:Java中Object是所有类的根类,Object提供了一个clone方法,该方法可以将Java对象复制一份,但是需要实现克隆方法的Java类必须实现一个接口Cloneable:该接口表示实现类能够复制且具有复制的能力,这种复制方式也称为原型模式。

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

        在Spring中指定scope=prototype(多例)的Bean的创建,就是使用的原型模式

2、代码演示

  • 浅克隆:如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Resume implements Cloneable {
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    private List<String> lists;

    @Override
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }

    lists是引用类型,克隆后修改原修改引用类型的值,会影响到原对象

  • 深克隆:复制对象的所有基本数据类型的成员变量值;为所有引用数据类型的成员变量申请存储空间,并复制每个引用类型成员变量所引用的对象,直到该对象的所有成员;(总结一句话:深拷贝是对整个对象进行拷贝

    1. 深拷贝方式一:重新clone方法来实现深拷贝

      1
      2
      3
      4
      5
      6
      7
      8
      @Override
      protected Resume clone() throws CloneNotSupportedException {
      Resume clone = (Resume) super.clone();
      List<String> target = new ArrayList<>();
      target.addAll(this.lists);
      clone.lists = target;
      return clone;
      }
    2. 深拷贝方式二:通过对象序列化实现深拷贝

      1
           

第三章 结构型模式

3.1 适配器模式

1. 概述

  • 适配器模式的定义:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
  • 适配器实现类型:①类适配器模式、②对象适配器模式、③接口适配器模式。
  • 适配器模式的优缺点
    • 优点:①客户端通过适配器可以透明地调用目标接口。②复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。③将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
    • 缺点:对类适配器来说,更换适配器的实现过程比较复杂。

2. 结构

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

3. 类适配器

  • 类适配器通过继承适配者类,然后实现目标接口,当执行接口中目标方法方法时在配置器类中调用父类的实现

  • 案例说明:目前接口的功能是播放美剧,适配者类中有个播放韩剧的方法,要求调用目标接口播放美剧的时候可以通过适配器实现播放韩剧的效果;

  • 定义播放韩剧的目标接口:接口的实现不重要了,使用适配器就是为了替换接口原有的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * @Description 美剧电影目标接口
    */
    public interface AmericanMovieTarget {

    /**
    * 播放美剧
    */
    public void playAmericanMovie();

    }
  • 是配置者类是一个播放美剧的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * @Description 韩剧适配者接口
    */
    public interface KoreanDramasAdaptee {

    /**
    * 播放韩剧
    */
    public void playKoreanDramas();
    }

    // 面向接口编程
    public class KoreanDramasAdapteeImpl implements KoreanDramasAdaptee {
    @Override
    public void playKoreanDramas() {
    System.out.println(" 播放韩剧 ");
    }
    }
  • 定义适配器类:实现调用播放美剧的接口播放韩剧的功能

    1
    2
    3
    4
    5
    6
    public class ClassAdatper extends KoreanDramasAdapteeImpl implements AmericanMovieTarget {
    @Override
    public void playAmericanMovie() {
    super.playKoreanDramas();
    }
    }
  • 测试适配器的效果

    1
    2
    3
    4
    5
    6
    7
    public class Client {
    public static void main(String[] args) {
    AmericanMovieTarget target = new ClassAdatper();
    target.playAmericanMovie();
    }
    }
    // 播放韩剧

4. 对象适配器

  • 对象适配器:是适配器类包含了对适配者的引用,对目标接口的实现是通过适配者完成

  • 创建对象是适配器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ObjectAdapter implements AmericanMovieTarget {
    private final KoreanDramasAdaptee koreanDramasAdaptee;

    public ObjectAdapter(KoreanDramasAdaptee koreanDramasAdaptee) {
    this.koreanDramasAdaptee = koreanDramasAdaptee;
    }

    @Override
    public void playAmericanMovie() {
    koreanDramasAdaptee.playKoreanDramas();
    }
    }
  • 测试对象适配器

    1
    2
    3
    4
    5
    6
    7
    public class Client {
    public static void main(String[] args) {
    AmericanMovieTarget target = new ObjectAdapter(new KoreanDramasAdapteeImpl());
    target.playAmericanMovie();
    }
    }
    // 播放韩剧

5. 接口适配器

  • 也称为缺省适配器模式,主要作用是:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现,那么该抽象类的子类可有选择的覆盖父类的某些方法来实现需求

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

  • 案例说明:目标接口定义了很多方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface InterfaceTarget {
    void m1();

    void m2();

    void m3();

    void m4();

    void m5();
    }
  • 但是当前要用到接口的m3方法,但是不想实现其他方法,可以定义一个适配器类将接口的方法进行空实现,当前是配置可以实现多个接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public abstract class InterfaceAdatper implements InterfaceTarget{
    @Override
    public void m1() {
    }
    @Override
    public void m2() {
    }
    @Override
    public void m3() {
    }
    @Override
    public void m4() {
    }
    @Override
    public void m5() {
    }
    }
  • 希望是接口的实现类,但是不想实现全部方法,此时只需要继承适配器抽象类,覆盖父类的方法

    1
    2
    3
    4
    5
    6
    public class InterfaceAdatperImpl extends InterfaceAdatper{
    @Override
    public void m3() {
    System.out.println("只是实现了m3方法");
    }
    }
  • 测试接口适配器

    1
    2
    3
    4
    5
    6
    public class Client {
    public static void main(String[] args) {
    InterfaceTarget target = new InterfaceAdatperImpl();
    target.m3();
    }
    }

3.2 装饰器模式

1. 概述

        按照单一职责原则,某一个对象只专注于干一件事,而如果要扩展其职能的话,不如想办法分离出一个类来“包装”这个对象,而这个扩展出的类则专注于实现扩展功能。装饰器模式就可以将新功能动态地附加于现有对象而不改变现有对象的功能;装饰器模式(Decorator Pattern),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更灵活。

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
    /**
    * 咖啡
    */
    interface Coffee {
    /** 获取价格 */
    double getCost();
    /** 获取配料 */
    String getIngredients();
    }

    /**
    * 原味咖啡
    */
    class SimpleCoffee implements Coffee {

    @Override
    public double getCost() {
    return 1;
    }

    @Override
    public String getIngredients() {
    return "Coffee";
    }
    }
  • 定义咖啡的装饰器类:添加牛奶的装饰器,添加糖的装饰器

    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
    /**
    * 此装饰类混合"牛奶"到咖啡中
    */
    class WithMilk implements Coffee {

    private final Coffee coffee;

    public WithMilk(Coffee coffee) {
    this.coffee = coffee;
    }

    @Override
    public double getCost() {
    return coffee.getCost() + 0.5;
    }

    @Override
    public String getIngredients() {
    return coffee.getIngredients() + ",milk";
    }
    }
    /**
    * 此装饰类混合"r糖"到咖啡中
    */
    class WithSugar implements Coffee {

    private final Coffee coffee;

    public WithSugar(Coffee coffee) {
    this.coffee = coffee;
    }

    @Override
    public double getCost() {
    return coffee.getCost() + 1;
    }

    @Override
    public String getIngredients() {
    return coffee.getIngredients() + ", Sugar";
    }
    }
  • 测试装饰器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Client {
    public static void main(String[] args) {
    Coffee c = new SimpleCoffee();
    System.out.println("c = " + c.getCost() + c.getIngredients());
    Coffee m = new WithMilk(c);
    System.out.println("m = " + m.getCost() + m.getIngredients());
    Coffee s = new WithSugar(m);
    System.out.println("s = " + s.getCost() + s.getIngredients());
    }
    }
    // c = 1.0Coffee
    // m = 1.5Coffee,milk
    // s = 2.5Coffee,milk, Sugar

3.3 代理模式

1. 概述

        代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

        按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。

2. 代码演示-静态代理

  • 抽象一组服务:被代理对象需要买个房子

    1
    2
    3
    public interface BuyHouse {
    void buyHosue();
    }
  • 被代理对象实现了买房的功能

    1
    2
    3
    4
    5
    6
    7
    public class BuyHouseImpl implements BuyHouse {

    @Override
    public void buyHosue() {
    System.out.println("我要买房");
    }
    }
  • 但是买房要准备很对东西,只能交给代理对象完成准备工作,买房的具体实际动作还得代理对象完成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class BuyHouseProxy implements BuyHouse {

    private BuyHouse buyHouse;

    public BuyHouseProxy(final BuyHouse buyHouse) {
    this.buyHouse = buyHouse;
    }

    @Override
    public void buyHosue() {
    System.out.println("买房前准备");
    buyHouse.buyHosue();
    System.out.println("买房后装修");
    }
    }
  • 测试静态代理

    1
    2
    3
    4
    5
    6
    7
    8
    public class ProxyTest {
    public static void main(String[] args) {
    BuyHouse buyHouse = new BuyHouseImpl();
    buyHouse.buyHosue();
    BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
    buyHouseProxy.buyHosue();
    }
    }

3. 动态代理-jdk

  • jdk动态代理处理器需要实现InvocationHandler接口:实现原理是通过Proxy.newProxyInstance()方法通过反射创建出被代理对象的代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class DynamicProxyHandler implements InvocationHandler {

    private Object object;

    public DynamicProxyHandler(final Object object) {
    this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("买房前准备");
    Object result = method.invoke(object, args);
    System.out.println("买房后装修");
    return result;
    }
    }
  • 根据代理处理器创建出指定对象的代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class DynamicProxyTest {
    public static void main(String[] args) {
    BuyHouse buyHouse = new BuyHouseImpl();
    BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(
    BuyHouse.class.getClassLoader(),
    new Class[]{BuyHouse.class},
    new DynamicProxyHandler(buyHouse));
    proxyBuyHouse.buyHosue();
    }
    }

    Proxy.newProxyInstance()方法接受三个参数:

    • ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
    • Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
    • InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

3. 代码演示-CGLIB

  • 是Spring中使用的一种代理方式,原理是通过动态的创建被代理对象的子类完成对真实对象的代理工作;CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

  • 添加CGLIB环境

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    </dependency>
    <dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    </dependency>
    <dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    </dependency>
  • 第一步:创建CGLIB代理类:Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,可以用来为无接口的类创建代理。它的功能与java自带的Proxy类挺相似的。它会根据某个给定的类创建子类,并且所有非final的方法都带有回调钩子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class CglibProxy implements MethodInterceptor {
    private Object target;
    public Object getInstance(final Object target) {
    this.target = target;
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(this.target.getClass());
    enhancer.setCallback(this);
    return enhancer.create();
    }

    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    System.out.println("买房前准备");
    Object result = methodProxy.invoke(object, args);
    System.out.println("买房后装修");
    return result;
    }
    }
  • 第二步:创建测试类

    1
    2
    3
    4
    5
    6
    7
    8
    public class CglibProxyTest {
    public static void main(String[] args){
    BuyHouse buyHouse = new BuyHouseImpl();
    CglibProxy cglibProxy = new CglibProxy();
    BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);
    buyHouseCglibProxy.buyHosue();
    }
    }

3.4 外观模式

1. 概述

        外观模式的标准定义是:要求一个子系统的外部与其内部的通信必须通过统一的对象进行,统一的对象称为外观:提供一个搞成次的接口,是的子系统更容易使用;简单描述就是:有个功能需要由一系列子一起执行,如果没有外观对象,需要一个个调用子系统,定义了外观对象,用一个接口封装了各个子系统的功能,只对外提供一个接口实现子系统的全部功能;再简单的一句话就是:外观模式就是封装;

2. 代码演示

  • 有多个子系统:每个系统有各自的功能实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class SubSystem01 {
    public void method1() {
    System.out.println("子系统01的method1()被调用!");
    }
    }

    class SubSystem02 {
    public void method2() {
    System.out.println("子系统02的method2()被调用!");
    }
    }

    class SubSystem03 {
    public void method3() {
    System.out.println("子系统03的method3()被调用!");
    }
    }
  • 定义外观对象:封装子系统的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Facade {
    private SubSystem01 obj1 = new SubSystem01();
    private SubSystem02 obj2 = new SubSystem02();
    private SubSystem03 obj3 = new SubSystem03();

    public void method() {
    obj1.method1();
    obj2.method2();
    obj3.method3();
    }
    }
  • 测试外观模式

    1
    2
    3
    4
    5
    6
    public class Client {
    public static void main(String[] args) {
    Facade facade = new Facade();
    facade.method();
    }
    }

3.5 桥接模式

1. 概述

  • 抽象化:其概念是将复杂物体的一个或几个特性抽出去而只注意其他特性的行动或过程。在面向对象就是将对象共同的性质抽取出去而形成类的过程。
  • 实现化:针对抽象化给出的具体实现。它和抽象化是一个互逆的过程,实现化是对抽象化事物的进一步具体化。
  • 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系
  • 桥接模式即将抽象部分与它的实现部分分离开来(并不是将抽象类与他的派生类分离,而是抽象类和它的派生类用来实现自己的对象),使他们都可以独立变化。桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。

2. 案例演示

  • 案例场景:有一个画笔画一个形状,可以画正方形、长方形、圆形。但是现在我们需要给这些形状进行上色,这里有三种颜色:白色、灰色、黑色。所以要抽象出两类产品:形状和颜色,画笔的实现有三种,颜色的实现有三种

    • 抽象方案一:为形状提供颜色的实现
    • 抽象方案二:为颜色提供形状的选择
  • 案例演示抽象方案一:为形状提供颜色,首先抽象出形状父类,可以设置颜色

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public abstract class Shape {
    Color color;

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

    public abstract void draw();
    }
    • 实现三个形状

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class Circle extends Shape{

      public void draw() {
      color.bepaint("正方形");
      }
      }

      public class Rectangle extends Shape{

      public void draw() {
      color.bepaint("长方形");
      }
      }

      public class Square extends Shape{

      public void draw() {
      color.bepaint("正方形");
      }
      }
    • 抽象的颜色也有三种

      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 interface Color {
      public void bepaint(String shape);
      }

      public class White implements Color{

      public void bepaint(String shape) {
      System.out.println("白色的" + shape);
      }
      }

      public class Gray implements Color{

      public void bepaint(String shape) {
      System.out.println("灰色的" + shape);
      }
      }

      public class Black implements Color{

      public void bepaint(String shape) {
      System.out.println("黑色的" + shape);
      }
      }
    • 测试桥接模式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public class Client {
      public static void main(String[] args) {
      //白色
      Color white = new White();
      //正方形
      Shape square = new Square();
      //白色的正方形
      square.setColor(white);
      square.draw();

      //长方形
      Shape rectange = new Rectangle();
      rectange.setColor(white);
      rectange.draw();
      }
      }

3.6 组合模式

1. 概述

        组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

  • 意图:将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
  • 主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
  • 何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

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
    import java.util.ArrayList;
    import java.util.List;

    public class Employee {
    private String name;
    private String dept;
    private int salary;
    private List<Employee> subordinates;

    //构造函数
    public Employee(String name,String dept, int sal) {
    this.name = name;
    this.dept = dept;
    this.salary = sal;
    subordinates = new ArrayList<Employee>();
    }
    // 新增
    public void add(Employee e) {
    subordinates.add(e);
    }
    // 删除
    public void remove(Employee e) {
    subordinates.remove(e);
    }
    // 列表
    public List<Employee> getSubordinates(){
    return subordinates;
    }

    public String toString(){
    return ("Employee :[ Name : "+ name
    +", dept : "+ dept + ", salary :"
    + salary+" ]");
    }
    }

3.7 享元模式

1.概述

        享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

第四章 行为型模式

4.1 策略模式

1. 概述

  • 策略模式的使用场景:先理解策略模式的使用场景:Java是面向对象的,如果要为对象添加某项功能通常会在类中定义一个方法,如果完成改功能的实现方式有多种选择的话就可以使用到策略设计模式了:即将不同的实现方式封装在不同的对象中,需要那种方式实现就让改对象去完成;比如我们希望实现一个出门旅游的功能,出行方式有坐飞机、坐火车、开私家车,将不同出行方式定义为不同的对象,在出行的时候指定所需要的出现方式即可;注意和状态模式的区别:①策略模式的选择是客户端进行选择的②策略模式专注于特定的算法或功能(比如使用状态模式:坐过车不舒服了可以自己转换为开私家车)
  • 使用策略模式的目的:程序调用者为程序指定执行环境(即程序执行的策略),根据策略的不同程序执行相同的函数会有不同的执行结果;
  • 策略模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
  • 策略模式的优点
    • 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
    • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
    • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
    • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
    • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
  • 策略模式的缺点
    • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
    • 策略模式造成很多的策略类,增加维护难度。

2. 结构

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

3. 代码演示

  • 抽象一种算法:封装算法方案

    1
    2
    3
    4
    5
    6
    public interface Algorithm {
    /**
    * 算法方案
    */
    void scheme();
    }
  • 这个算法有不同的算法实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class AlgorithmJwt implements Algorithm {
    @Override
    public void scheme() {
    System.out.println("使用Jwt验证的算法方案");
    }
    }

    public class AlgorithmSession implements Algorithm {
    @Override
    public void scheme() {
    System.out.println("获取Session的算法方案");
    }
    }
  • 最后将算法进行封装:用户只需要提供算法。算法的执行统一交给算法执行器执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class AlgorithmContext {
    private final Algorithm algorithm;

    public AlgorithmContext(Algorithm algorithm) {
    this.algorithm = algorithm;
    }

    public void execute() {
    algorithm.scheme();
    }
    }
  • 测试策略模式

    1
    2
    3
    4
    5
    6
    7
    8
    public class Client {
    public static void main(String[] args) {
    AlgorithmContext c1 = new AlgorithmContext(new AlgorithmJwt());
    c1.execute();
    AlgorithmContext c2 = new AlgorithmContext(new AlgorithmSession());
    c2.execute();
    }
    }

4.2 模板方法模式

1. 概述

  • 模板方法定义:在父类中定义了某种操作,操作中定义好了算法的整体步骤和框架,而将一些特殊步骤没有实现,需要延迟到子类进行个性化的实现;模板方法的作用:使得子类可以不改变一个算法的结构,即可重新定义该算法的某个特定的步骤;
  • 模板方法优点
    • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
    • 它在父类中提取了公共的部分代码,便于代码复用。
    • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  • 模板方法缺点
    • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
    • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
    • 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

2. 结构

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

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

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

3. 代码演示

  • 抽象出一种算法框架,其中一部分的步骤是没有实现的抽象方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public abstract class AbstractTemplate {
    public void execute() {
    System.out.println("公共是算法:开始");
    template();
    System.out.println("公共是算法:结束");
    }

    /**
    * 改方法没有实现,需要子类个性化定义
    */
    abstract void template();
    }
  • 定义子类:个性化定义算法中的特点步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class TemplateAImpl extends AbstractTemplate {
    @Override
    void template() {
    System.out.println("子类个性化的算法A步骤");
    }
    }
    public class TemplateBImpl extends AbstractTemplate {
    @Override
    void template() {
    System.out.println("子类个性化的算法B步骤");
    }
    }
  • 测试模板方法

    1
    2
    3
    4
    5
    6
    7
    8
    public class TemplateClient {
    public static void main(String[] args) {
    AbstractTemplate templateA = new TemplateAImpl();
    templateA.execute();
    AbstractTemplate templateB = new TemplateBImpl();
    templateB.execute();
    }
    }

4.3 观察者模式

1. 概述

  • 观察者模式定义:又叫发布-订阅模式(Publish/Subscribe):简单描述就是使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新;根据描述可以分析得出:被观察的对象和观察者之间是一对多的依赖关系;对描述进行抽象:

    • 有一个状态会改变的对象我们定义为Subject:这个对象知道其依赖的所有对象,表示这个对象里面有个集合包含着这些对象,所有还有有个方法,可以让其他对象进行依赖这个对象;
    • 这个对象状态改变时候会通知所依赖的对象:从这句话可以抽象出两个关键点,①Subject有个状态改变的方法,②依赖的对象有个接受通知的方法
    • 根据抽象结果,面向接口编程
  • 观察者模式优点

    • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
    • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
  • 观察者模式缺点

    • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
    • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

2. 结构

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

3. 代码演示

  • 观察者对象(Observer):这个比较简单先定义这个,这个对象只有一个接受通知的方法,也可以返回一个接受到通知的响应

    1
    2
    3
    4
    5
    6
    7
    8
    public interface Observer {
    /**
    * 被观察者对象接收通知
    *
    * @return true 表示接收到通知
    */
    boolean updateNotify();
    }
  • 被观察者抽象为Subject(主题):有两个方法①其他对象依赖这个对象的方法②状态改变的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface Subject {
    /**
    * 表示被依赖了
    *
    * @param observer 被依赖的对象
    */
    void depended(Observer observer);

    /**
    * 被更新的方法
    */
    void update();
    }
  • 先定义一个被观察者对象一个公司的大Boss:①管理者所依赖的对象②更新时候通知所有依赖的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class SubjectBoss implements Subject {

    private final List<Observer> list = new ArrayList<>();

    /**
    * 被依赖后将依赖的对象管理起来
    *
    * @param observer 被依赖的对象
    */
    public void depended(Observer observer) {
    list.add(observer);
    }

    /**
    * 更新后通知被依赖着对象
    */
    public void update() {
    list.forEach(Observer::updateNotify);
    }
    }
  • 再定义一批观察者对象:员工小王和员工小刘

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ObserverEmpXL implements Observer {
    @Override
    public boolean updateNotify() {
    System.out.println("员工小刘收到通知");
    return true;
    }
    }

    public class ObserverEmpXW implements Observer {
    @Override
    public boolean updateNotify() {
    System.out.println("员工小王收到通知");
    return true;
    }
    }
  • 测试观察者模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Client {
    public static void main(String[] args) {
    Subject subject = new SubjectBoss();

    Observer sl = new ObserverEmpXL();
    Observer sw = new ObserverEmpXW();

    // 小王和小刘观察了大boss
    subject.depended(sl);
    subject.depended(sw);

    // 大boss被更新了
    subject.update();
    }
    }

4. JDK内置观察者

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

  • Observable类:是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

    • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
    • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
    • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
  • Observer 接口:Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。

  • 【案例代码】警察抓小偷:警察是观察者,小偷是被观察者

    • 小偷是一个被观察者,所以需要继承Observable类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      public class Thief extends Observable {

      private String name;

      public Thief(String name) {
      this.name = name;
      }

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

      public String getName() {
      return name;
      }

      public void steal() {
      System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
      super.setChanged(); //changed = true
      super.notifyObservers();
      }
      }
    • 警察是一个观察者,所以需要让其实现Observer接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class Policemen implements Observer {

      private String name;

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

      public String getName() {
      return name;
      }

      @Override
      public void update(Observable o, Object arg) {
      System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
      }
      }
    • 测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class Client {
      public static void main(String[] args) {
      //创建小偷对象
      Thief t = new Thief("隔壁老王");
      //创建警察对象
      Policemen p = new Policemen("小李");
      //让警察盯着小偷
      t.addObserver(p);
      //小偷偷东西
      t.steal();
      }
      }

4.4 迭代子模式

1. 定义

        提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

2. 结构

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

3. 代码实现

  • 定义迭代器接口,声明hasNext、next方法

    1
    2
    3
    4
    public interface StudentIterator {
    boolean hasNext();
    Student next();
    }
  • 定义具体的迭代器类,重写所有的抽象方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class StudentIteratorImpl implements StudentIterator {
    private List<Student> list;
    private int position = 0;

    public StudentIteratorImpl(List<Student> list) {
    this.list = list;
    }

    @Override
    public boolean hasNext() {
    return position < list.size();
    }

    @Override
    public Student next() {
    Student currentStudent = list.get(position);
    position ++;
    return currentStudent;
    }
    }
  • 定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法

    1
    2
    3
    4
    5
    6
    7
    public interface StudentAggregate {
    void addStudent(Student student);

    void removeStudent(Student student);

    StudentIterator getStudentIterator();
    }
  • 定义具体的容器类,重写所有的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class StudentAggregateImpl implements StudentAggregate {

    private List<Student> list = new ArrayList<>(); // 学生列表

    @Override
    public void addStudent(Student student) {
    this.list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
    this.list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
    return new StudentIteratorImpl(list);
    }
    }

4. 优缺点

  • 优点:
    • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
    • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
    • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。
  • 缺点:
    • 增加了类的个数,这在一定程度上增加了系统的复杂性。

4.5 责任链模式

4.6 命令模式

4.7 备忘录模式

4.8 状态模式

1. 概述

        允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类;状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

        状态模式最主要的好处就是把状态的判断与控制放到了其服务端的内部,使得客户端不需要去写很多代码判断,来控制自己的节点跳转,而且这样实现的话,我们可以把每个节点都分开来处理,当流程流转到某个节点的时候,可以去写自己的节点流转方法。当然状态模式的缺点也很多,比如类的耦合度比较高,基本上三个类要同时去写,而且会创建很多的节点类。

2. 代码演示

  • 抽象一个状态的行为

    1
    2
    3
    public interface Status {
    void handle();
    }
  • 该状态有多种实现:如果状态需要传递,则当前状态只需要判断传递过来的状态结果而执行自己的状态行为,并传递给下一个状态即可,不必关系之后状态的行为;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class StatusA implements Status{
    @Override
    public void handle() {
    System.out.println("状态A的行为");
    }
    }

    public class StatusB implements Status{
    @Override
    public void handle() {
    System.out.println("状态B的行为");
    }
    }
  • 此时外部会使用到状态,并且还需要修改状态,所以需要一个状态的上下文对象封装状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Context {
    private Status status;

    public Context(Status status) {
    this.status = status;
    }

    public void doWork() {
    status.handle();
    }

    public Status getStatus() {
    return status;
    }

    public void setStatus(Status status) {
    this.status = status;
    }
    }
  • 测试状态行为,修改状态并获对象的执行结果

    1
    2
    3
    4
    5
    6
    7
    8
    public class Client {
    public static void main(String[] args) {
    Context context = new Context(new StatusA());
    context.doWork();
    context.setStatus(new StatusB());
    context.doWork();
    }
    }

3. 优缺点

  • 优点:
    • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
    • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 缺点:
    • 状态模式的使用必然会增加系统类和对象的个数。
    • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
    • 状态模式对”开闭原则”的支持并不太好。

4.9 访问者模式

访问者模式的定义:封装一下用于对某种数据结构中的各元素的操作(在不改变数据结构的前提下定义与这些元素的新的操作);案例代码看完后再回头看看下面场景对访问者模式定义的解释:

学校里有教师和学生,访问者最终的目的是希望获取到教师和学生的一些信息,那么数据结构就是固定的(教师和学生),将教师和学生抽象为User,并且提供一个供访问者可以访问的方法,如下的抽象方法:

1
public abstract void accept(Visitor visitor);

那么访问者(Visitor)呢!可以是学生家长(关心的是学生成绩)或者是学校校长(关心的是学校教师)或者其他访问者,你是任何一个访问者只能固定访问学生老师,在进行访问核心信息的时候需要拿到这些数据的,那么将访问者进行抽象就是如下代码(因为数据结构固定只有学生和老师):

1
2
3
4
5
6
public interface Visitor {
// 访问学生信息
void visit(Student student);
// 访问老师信息
void visit(Teacher teacher);
}

这样不同访问者有不同的访问逻辑,增加了扩展性,这样的代码涉及是为了将这两部分的业务解耦的一种设计模式。

案例

访问者模式的类结构相对其他设计模式来说比较复杂,但这样的设计模式在我看来更加烧气有魅力,它能阔开你对代码结构的新认知,用这样思维不断的建设出更好的代码架构。

访问者模式的角色划分:

  • 抽象元素角色(Element):该角色声明一个接受操作,接受一个访问者对象,,其意义是指,每一个元素都要可以被访问者访问。。
  • 具体元素角色(Concrete Element):提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
  • 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

场景说明:

  • 抽象元素角色(Element):User
  • 具体元素角色(Concrete Element):Teacher、Student
  • 抽象访问者(Visitor)角色:Visitor
  • 具体访问者(ConcreteVisitor)角色:Principal、Parent

代码实现:

  1. 定义抽象元素角色:用户抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public abstract class User {
    // 基础信息包括: 姓名、身份、班级,也可以是一个业务用户属性类。
    public String name; // 姓名
    public String identity; // 身份;重点班、普通班 | 特级教师、普通教师、实习教师
    public String clazz; // 班级
    public User(String name, String identity, String clazz) {
    this.name = name;
    this.identity = identity;
    this.clazz = clazz;
    }
    // 核心访问方法: 这个方法是为了让后续的用户具体实现者都能提供出一个访问方法,共外部使用。
    public abstract void accept(Visitor visitor);
    }
  2. 具体元素角色(Concrete Element):①这里实现了老师和学生类,都提供了父类的构造函数。②在accept方法中,提供了本地对象的访问;visitor.visit(this),这块需要加深理解。③老师和学生类又都单独提供了各自的特性方法;升本率(entranceRatio)、排名(ranking),类似这样的方法可以按照业务需求进行扩展。

    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 Teacher extends User {
    public Teacher(String name, String identity, String clazz) {
    super(name, identity, clazz);

    public void accept(Visitor visitor) {
    visitor.visit(this);
    }
    // 升本率
    public double entranceRatio() {
    return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
    }
    // 学生类
    public class Student extends User {

    public Student(String name, String identity, String clazz) {
    super(name, identity, clazz);
    }
    public void accept(Visitor visitor) {
    visitor.visit(this);
    }
    public int ranking() {
    return (int) (Math.random() * 100);
    }
    }
  3. 定义抽象访问者(Visitor)角色:相同的方法名称,不同的入参用户类型。

    1
    2
    3
    4
    5
    6
    7
    public interface Visitor {
    // 访问学生信息
    void visit(Student student);

    // 访问老师信息
    void visit(Teacher teacher);
    }
  4. 具体访问者(ConcreteVisitor)角色:

    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 Principal implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Principal.class);

    public void visit(Student student) {
    logger.info("学生信息 姓名:{} 班级:{}", student.name, student.clazz);
    }

    public void visit(Teacher teacher) {
    logger.info("学生信息 姓名:{} 班级:{} 升学率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }

    }

    // 访问者:家长
    public class Parent implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Parent.class);

    public void visit(Student student) {
    logger.info("学生信息:姓名:{} 班级:{} 排名:{}", student.name, student.clazz, student.ranking());
    }

    public void visit(Teacher teacher) {
    logger.info("老师信息 姓名:{} 班级:{} 级别:{}", teacher.name, teacher.clazz, teacher.identity);
    }
    }
  5. 数据准备:初始化了基本的数据,学生和老师的信息,通过传入不同的观察者(校长、家长)而差异化的打印信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class DataView {

    List<User> userList = new ArrayList<User>();

    public DataView() {
    userList.add(new Student("谢飞机", "重点班", "一年一班"));
    userList.add(new Student("windy", "重点班", "一年一班"));
    userList.add(new Student("大毛", "普通班", "二年三班"));
    userList.add(new Student("Shing", "普通班", "三年四班"));
    userList.add(new Teacher("BK", "特级教师", "一年一班"));
    userList.add(new Teacher("娜娜Goddess", "特级教师", "一年一班"));
    userList.add(new Teacher("dangdang", "普通教师", "二年三班"));
    userList.add(new Teacher("泽东", "实习教师", "三年四班"));
    }

    // 展示
    public void show(Visitor visitor) {
    for (User user : userList) {
    user.accept(visitor);
    }
    }
    }
  6. 测试验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void test(){
    DataView dataView = new DataView();

    logger.info("\r\n家长视角访问:");
    dataView.show(new Parent()); // 家长

    logger.info("\r\n校长视角访问:");
    dataView.show(new Principal()); // 校长
    }

    测试结果:通过测试结果可以看到,家长和校长的访问视角同步,数据也是差异化的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23:00:39.726 [main] INFO  org.itstack.demo.design.test.ApiTest - 
    家长视角访问:
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:谢飞机 班级:一年一班 排名:62
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:windy 班级:一年一班 排名:51
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:大毛 班级:二年三班 排名:16
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 学生信息 姓名:Shing 班级:三年四班 排名:98
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:BK 班级:一年一班 级别:特级教师
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:娜娜Goddess 班级:一年一班 级别:特级教师
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:dangdang 班级:二年三班 级别:普通教师
    23:00:39.730 [main] INFO o.i.demo.design.visitor.impl.Parent - 老师信息 姓名:泽东 班级:三年四班 级别:实习教师
    23:00:39.730 [main] INFO org.itstack.demo.design.test.ApiTest -
    校长视角访问:
    23:00:39.731 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:谢飞机 班级:一年一班
    23:00:39.731 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:windy 班级:一年一班
    23:00:39.731 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:大毛 班级:二年三班
    23:00:39.731 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:Shing 班级:三年四班
    23:00:39.733 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:BK 班级:一年一班 升学率:70.62
    23:00:39.733 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:娜娜Goddess 班级:一年一班 升学率:23.15
    23:00:39.734 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:dangdang 班级:二年三班 升学率:70.98
    23:00:39.734 [main] INFO o.i.d.design.visitor.impl.Principal - 学生信息 姓名:泽东 班级:三年四班 升学率:90.14

    Process finished with exit code 0

总结

特点

  1. 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
  2. 访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。
  3. 访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。

优点

  1. 添加新的操作或者说访问者会非常容易。
  2. 使得类层次结构不改变的情况下,可以针对各个层次做出不同的操作,而不影响类层次结构的完整性。
  3. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
  4. 使得给结构稳定的对象增加新算法变得容易,提搞了代码的可维护性,可扩展性。
  5. 将对各个元素的一组操作集中在一个访问者类当中。
  6. 可以跨越类层次结构,访问不同层次的元素类,做出相应的操作。

缺点

  1. 增加新的元素会非常困难。
  2. 实现起来比较复杂,会增加系统的复杂性。
  3. 破坏封装,如果将访问行为放在各个元素中,则可以不暴露元素的内部结构和状态,但使用访问者模式的时候,为了让访问者能获取到所关心的信息,元素类不得不暴露出一些内部的状态和结构,就像收入和支出类必须提供访问金额和单子的项目的方法一样。

开源代码

4.10 中介模式

案例

总结

开源代码

1. 概述

        又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

2. 结构

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

3. 代码演示

  • 现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

  • 抽象中介者:申明一个联络方法

    1
    2
    3
    public abstract class Mediator {
    public abstract void constact(String message,Person person);
    }
  • 抽象同事类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public abstract class Person {
    protected String name;
    protected Mediator mediator;

    public Person(String name,Mediator mediator){
    this.name = name;
    this.mediator = mediator;
    }
    }
  • 具体同事类-房屋拥有者:属于中介要管理的Person

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class HouseOwner extends Person {

    public HouseOwner(String name, Mediator mediator) {
    super(name, mediator);
    }

    //与中介者联系
    public void constact(String message){
    mediator.constact(message, this);
    }

    //获取信息
    public void getMessage(String message){
    System.out.println("房主" + name +"获取到的信息:" + message);
    }
    }
  • 具体同事类-承租人:也属于中介要管理的Person

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Tenant extends Person {
    public Tenant(String name, Mediator mediator) {
    super(name, mediator);
    }

    //与中介者联系
    public void constact(String message){
    mediator.constact(message, this);
    }

    //获取信息
    public void getMessage(String message){
    System.out.println("租房者" + name +"获取到的信息:" + message);
    }
    }
  • 中介机构

    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
    public class MediatorStructure extends Mediator {
    //首先中介结构必须知道所有房主和租房者的信息
    private HouseOwner houseOwner;
    private Tenant tenant;

    public HouseOwner getHouseOwner() {
    return houseOwner;
    }

    public void setHouseOwner(HouseOwner houseOwner) {
    this.houseOwner = houseOwner;
    }

    public Tenant getTenant() {
    return tenant;
    }

    public void setTenant(Tenant tenant) {
    this.tenant = tenant;
    }

    public void constact(String message, Person person) {
    //如果是房主,则租房者获得信息
    if (person == houseOwner) {
    tenant.getMessage(message);
    } else {
    //反之则是房主获得信息
    houseOwner.getMessage(message);
    }
    }
    }
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Client {
    public static void main(String[] args) {
    //一个房主、一个租房者、一个中介机构
    MediatorStructure mediator = new MediatorStructure();

    //房主和租房者只需要知道中介机构即可
    HouseOwner houseOwner = new HouseOwner("张三", mediator);
    Tenant tenant = new Tenant("李四", mediator);

    //中介结构要知道房主和租房者
    mediator.setHouseOwner(houseOwner);
    mediator.setTenant(tenant);

    tenant.constact("需要租三室的房子");
    houseOwner.constact("我这有三室的房子,你需要租吗?");
    }
    }

4. 优缺点

  • 优点:

    • 松散耦合:中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了。

    • 集中控制交互:多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。

    • 一对多关联转变为一对一的关联:没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。

  • 缺点:

    • 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

4.11 解释器模式

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2020-2022 xiaoliuxuesheng
  • PV: UV:

老板,来一杯Java

支付宝
微信