[TOC]

1. 设计模式分类

  • 创建型模式

    单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。

    特点是“将对象的创建与使用分离”。

  • 结构型模式

    代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。

    用于描述如何将类或对象按某种布局组成更大的结构,类似与造房子,各种组件

  • 行为型模式

    模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。

    用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。

2. UML图

统一建模语言

特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。

UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。

2.1 类图

作用:

  • 类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解;
  • 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。

2.2类图表示方法

类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和address这3个属性,以及work()方法。

pk7QSY9.png

属性/方法名称前加的加号和减号表示了这个属性/方法的可见性,UML类图中表示可见性的符号有三种:

  • +:表示public

  • -:表示private

  • :表示protected

属性的完整表示方式是: 可见性 名称 :类型 [ = 缺省值]

方法的完整表示方式是: 可见性 名称(参数列表) [ : 返回类型]

注意:

​ 1,中括号中的内容表示是可选的

​ 2,也有将类型放在变量名前面,返回值类型放在方法名前面

2.2.2 类与类之间关系的表示方式

2.3.2.1 关联关系

关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,

分为一般关联关系、聚合关系和组合关系。

关联又可以分为单向关联,双向关联,自关联。

  • 单向关联

    pk7QCS1.png

在UML类图中单向关联用一个带箭头的实线表示。

就是Customer类里面有Address的一个成员变量(一个类有持有另一个类的属性)。

  • 双向关联

pk7QPQx.png

双向关联就是双方各自持有对方类型的成员变量。

双向关联用一个不带箭头的直线表示

上图中 Customer持有Product的属性(拥有多个产品),而Product也有Customer的属性(产品被谁购买)

  • 自关联

pk7QFOK.png

自己包含自己。

2.3.2.2 聚合关系

聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。

聚合关系可以用带空心菱形的实线来表示,菱形指向整体。

pk7QAeO.png

比如这个图,大学里面包含了老师,但大学停办了老师依然存在。

成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。

2.3.2.3 组合关系

组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。

组合关系用带实心菱形的实线来表示,菱形指向整体。

pk7QEwD.png

比如人和器官的关系就算组合关系,人包含着器官,但器官离不开人。

整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。

2.3.2.4 依赖关系

依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。

依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。

pk7NM1e.png

比如司机开车就是依赖关系,司机要用到车的一个功能(驾驶汽车)

2.3.2.5 继承关系

继承关系是对象之间耦合度最大的一种关系,父类与子类之间的关系,是一种继承关系。

pk7NQ6H.png

继承关系用带空心三角箭头的实线来表示,箭头从子类指向父类。

就是父亲与儿子的关系,儿子可以有父亲所有功能,还可以加一改造

2.3.2.6 实现关系

实现关系是接口与实现类之间的关系。

实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。

pk7N3nA.png

类实现一个接口就需要实现接口里面所有的抽象方法。

3 软件设计原则

3.1 开闭原则

对扩展开放,对修改关闭

意思就是,程序要扩展的可以开放,这样软件好增加功能,但对具体实现不能开放,因为这样会改变代码会造成紊乱。

如何实现:抽象类和接口实现

接口里面声明一些方法,然后让其有不同的实现,

比如把猫的颜色做一个抽象类,该类有一个颜色这个抽象方法

一个猫实现这个接口,变成白猫,另外一只猫也实现这个接口,变成黑猫。

我们扩展就只需要定义新的类实现这个颜色接口就可以了,而不需要修改原代码。

pk7Nttf.png

3.2 里氏代换原则

子类可以扩展父类的功能,但不能改变父类原有的功能。

就比如:正方形不是长方形,这个例子

pk7NwcQ.png

正方形继承了长方形,并且重写了设置长和宽的方法

我们的测试方法resize是针对长方形的可以通过,但正方形由于继承了长方形因此也可以调用这个方法,这样就出错了。

我们修改后:

pk7NrBn.png

我们让长方形和正方形都实现一个设置长和宽的接口,但长方形和正方形没有关系,这样resize这个方法正方形就无法调用。从而保证了里氏代换原则。

儿子可以在家里面加东西,但不能改东西。

3.3 依赖倒转原则

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

简单来说,就是一台电脑有很多组件,比如cpu+硬盘等等,我们可以用不同品牌的cpu和不同品牌的硬盘。但不能一台电脑只能用专门的cpu而不能换吧!

pk7NcNV.png

比如这个图,咱们的电脑只能组装专门的零件。

这就是高层模块(电脑)依赖与低层模块(具体的cpu),导致模块间的耦合。

改进一下:

pk7UqZn.png

我们的电脑依赖与一个(cpu框架)抽象,而不依赖与一个具体实现,这样咱们电脑组装的时候就可以选择不同品牌的cpu了。

3.4 接口隔离原则

客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。

通俗点就是咱们的微信有聊天,支付,点外卖这些功能,但咱们只需要聊天,支付这个功能。我们就需要把聊天,支付,点外卖这些功能抽成接口,我们微信只需要聊天和支付,咱们就实现聊天金额支付的接口,美团需要支付和点外卖,他就实现支付和点外卖这个接口。

改进前:

pk7NO3D.png

改进后:

pk7UjiV.png

3.5 迪米特法则

又称为最少知识原则。

只和你的直接朋友交谈,不跟“陌生人”说话。

你和她本无交集。但她和你朋友有交集,你就只能通过朋友来找她聊天。

“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

例子:明星与经纪人的关系实例

明星关注与唱歌这些,日常事务交给经纪人负责和公司交流和粉丝安排见面,而明星和粉丝和公司都不认识

pk7a2y4.png

3.6 合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

通常类的复用分为继承复用合成复用两种。

继承复用虽然有简单和易实现的优点,但它也存在以下缺点:

  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。

  • 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。

  • 限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:

  • 维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。

  • 对象间的耦合度低。可以在类的成员位置声明抽象。

  • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

pk7D18x.png

原本是这样,假如汽车多加一个颜色,那么又会产生很多子类导致复用性不高

改进:

pk7DJKO.png

把颜色抽陈一个接口,这样加一个颜色只需要实现Color这个接口。

4 创建者模式

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工程模式
  • 原型模式
  • 建造者模式

4.1 单例设计模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。

这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

4.1.2 单例模式的实现

  • 饿汉模式(类加载就创建实例)

    • 静态变量方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /**
      * 饿汉式
      * 静态变量创建类的对象
      */
      public class Singleton {
      //私有构造方法
      private Singleton() {}

      //在成员位置创建该类的对象
      private static Singleton instance = new Singleton();

      //对外提供静态方法获取该对象
      public static Singleton getInstance() {
      return instance;
      }
      }

      缺点:instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

    • 静态代码块方式

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

      //私有构造方法
      private Singleton() {}

      //在成员位置创建该类的对象
      private static Singleton instance;

      static {
      instance = new Singleton();
      }

      //对外提供静态方法获取该对象
      public static Singleton getInstance() {
      return instance;
      }
      }

      缺点:跟上面一种类似,会造成内存浪费

    • 枚举方式(最推荐)

      因为枚举类型是线程安全的,并且只会装载一次而且非常简单

      1
      2
      3
      4
      5
      6
      /**
      * 枚举方式
      */
      public enum Singleton {
      INSTANCE;
      }
  • 懒汉式(首次使用对象时候才创建实例)

    • 线程不安全

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      /**
      * 懒汉式
      * 线程不安全
      */
      public class Singleton {
      //私有构造方法
      private Singleton() {}

      //在成员位置创建该类的对象
      private static Singleton instance;

      //对外提供静态方法获取该对象
      public static Singleton getInstance() {

      if(instance == null) {
      instance = new Singleton();
      }
      return instance;
      }
      }

      缺点:多线程情况下出现线程安全问题。

    • 线程安全

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 懒汉式
    * 线程安全
    */
    public class Singleton {
    //私有构造方法
    private Singleton() {}

    //在成员位置创建该类的对象
    private static Singleton instance;

    //对外提供静态方法获取该对象
    public static synchronized Singleton getInstance() {

    if(instance == null) {
    instance = new Singleton();
    }
    return instance;
    }
    }

    缺点:锁的范围太大了,会导致执行效率过低

    • 双重检查锁(比较完美但是较复杂)
    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 Singleton {

    //私有构造方法
    private Singleton() {}

    private static Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
    if(instance == null) {
    synchronized (Singleton.class) {
    //抢到锁之后再次判断是否为null
    if(instance == null) {
    instance = new Singleton();
    }
    }
    }
    return instance;
    }
    }

    缺点:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

    改进:+volatile关键字可以保证可见性和有序性。

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

    //私有构造方法
    private Singleton() {}

    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
    if(instance == null) {
    synchronized (Singleton.class) {
    //抢到锁之后再次判断是否为空
    if(instance == null) {
    instance = new Singleton();
    }
    }
    }
    return instance;
    }
    }
    • 静态内部类方式(较常用)由JVM保证的

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      /**
      * 静态内部类方式
      */
      public class Singleton {

      //私有构造方法
      private Singleton() {}

      private static class SingletonHolder {
      private static final Singleton INSTANCE = new Singleton();
      }

      //对外提供静态方法获取该对象
      public static Singleton getInstance() {
      return SingletonHolder.INSTANCE;
      }
      }

      静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。


4.1.3 存在的问题

除了用枚举类的方式,其它方式在遇到序列化反射的时候都会破坏单例模式

4.1.3.2 问题的解决

  • 序列化、反序列方式破坏单例模式的解决方法

    在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。

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

    //私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
    }

    /**
    * 下面是为了解决序列化反序列化破解单例模式
    */
    private Object readResolve() {
    return SingletonHolder.INSTANCE;
    }
    }

    源码解析

    • ObjectInputStream类

      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 final Object readObject() throws IOException, ClassNotFoundException{
      ...
      // if nested read, passHandle contains handle of enclosing object
      int outerHandle = passHandle;
      try {
      Object obj = readObject0(false);//重点查看readObject0方法
      .....
      }

      private Object readObject0(boolean unshared) throws IOException {
      ...
      try {
      switch (tc) {
      ...
      case TC_OBJECT:
      return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
      ...
      }
      } finally {
      depth--;
      bin.setBlockDataMode(oldMode);
      }
      }

      private Object readOrdinaryObject(boolean unshared) throws IOException {
      ...
      //isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
      obj = desc.isInstantiable() ? desc.newInstance() : null;
      ...
      // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
      if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
      // 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
      // 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
      Object rep = desc.invokeReadResolve(obj);
      ...
      }
      return obj;
      }
  • 反射方式破解单例的解决方法

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

    //私有构造方法
    private Singleton() {
    /*
    反射破解单例模式需要添加的代码
    */
    if(instance != null) {
    throw new RuntimeException();
    }
    }

    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

    if(instance != null) {
    return instance;
    }

    synchronized (Singleton.class) {
    if(instance != null) {
    return instance;
    }
    instance = new Singleton();
    return instance;
    }
    }
    }

4.1.4 JDK源码解析-Runtime类

Runtime类就是使用的单例设计模式。

Runtime类使用的是恶汉式(静态属性)方式来实现单例模式的。


4.2 工厂模式

4.2.1 概述

原本的实现:

pkHtuu9.png

比如设计一个咖啡点餐系统,咱们用传统的方法,每一个东西都需要自己创建对象,比如自己要弄各种咖啡,然后拿来卖,但咱们使用工厂方法就是我们只需要点哪种咖啡,咖啡怎么弄是他的事。

如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦

  • 简单工厂模式

    结构:

    • 抽象产品: 就是这个产品的规范,比如:咖啡抽象类,里面就定义了咖啡的属性和功能。
    • 具体产品:具体实现或继承抽象产品的子类。就比如各种各样不同的咖啡,拿铁咖啡啊,美式咖啡等等。
    • 具体工厂:提供创建产品的方法。

    实现:

    用简单工厂改进

    pkHtMH1.png

    改进之后咱们客户端只需要创建工厂类,然后选那种咖啡传进去就可以了,咱们不需要自己手动创建咖啡

    实现了咖啡店和咖啡的耦合,但又产生了新的耦合。

    优点:

    装了创建对象的过程,可以通过参数直接获取对象。更加容易扩展。

    缺点:

    增加产品的时候还是需要修改工厂类代码,违背“开闭原则”。

    在开发中一般把工厂定义为静态的。

  • 工厂方法模式

    概念:

    定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。

    结构:

    • 抽象工厂:提供创建产品的接口,咱们用户通过这个接口调用创建
    • 具体工厂:创建产品的具体实现。
    • 抽象产品:产品自身的规范
    • 具体产品:具体实现的产品

    实现:

    pkHt0Et.png

    改进后定义了一个咖啡工厂接口,然后不同的工厂实现不同种类咖啡工厂的创建,美式咖啡工厂创建美式咖啡。

    假如增加一种拿铁咖啡,只需要添加拿铁咖啡工厂和拿铁咖啡类就可以了无需对之前的代码做修改。

    优点:

    • 只需知道工厂名称就可以创建产品,无需知道创建细节。
    • 增加产品时候,只需添加代码,无需修改之前代码。

    缺点:

    • 每增加一个产品就需要加一个具体产品类和产品工厂类,会让系统的复杂度增加。
  • 抽象工厂模式

    pkHt4U0.png

抽象工厂可以实现同一品牌的产品的生产,比如:这个图电脑有不同品牌,

手机也有不同品牌,但电脑和手机是不同种类的。

而咱们抽象工厂就可以实现,同一种品牌的产品的创建。

概念:

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

结构:

  • 抽象工厂:提供了产品的创建,里面包含着多个创建产品的方法,可以创建不同种的产品
  • 具体工厂:实现抽象工厂种不同产品的创建
  • 抽象产品:定义产品规范,描述产品主要特性和功能。
  • 具体产品:实现抽查产品接口,由具体工厂创建。它和具体工厂是多对一的关系。

实现:

pkbpTH0.png

比如这个类图:

我们想要生产咖啡还要生产甜点,我们就把甜点和咖啡都定义为抽象产品,

它们各自有不同的实现,比如美式咖啡和提拉米苏甜点。

然后我们定义一个抽象工厂,里面有创建咖啡和甜点的方法。

美式甜点工厂实现这个抽象工厂创建,美式咖啡和提拉米苏。

然后其它风味的选择不同组合。

抽象工厂:

1
2
3
4
5
6
public interface DessertFactory {

Coffee createCoffee();

Dessert createDessert();
}

具体工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {

public Coffee createCoffee() {
return new AmericanCoffee();
}

public Dessert createDessert() {
return new MatchaMousse();
}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {

public Coffee createCoffee() {
return new LatteCoffee();
}

public Dessert createDessert() {
return new Tiramisu();
}
}

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

优缺点

  • 优点:同种品牌中多个产品一起被创建时,保证这个工厂只会给你生产这个品牌的产品。
  • 缺点:新增一个产品时,所有工厂都需要进行修改。

使用场景

  • 创建的产品需要互相依赖关联的。比如,格力工厂里面生产空调和配套的洗衣机电冰箱。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

输入法换皮肤,一整套一起换。生成不同操作系统的程序。

模式扩展

简单工厂+配置文件解除耦合

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

第一步:定义配置文件

为了演示方便,我们使用properties文件作为配置文件,名称为bean.properties

1
2
american=com.itheima.pattern.factory.config_factory.AmericanCoffee
latte=com.itheima.pattern.factory.config_factory.LatteCoffee

第二步:改进工厂类

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

private static Map<String,Coffee> map = new HashMap();

static {
Properties p = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//遍历Properties集合对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//根据键获取值(全类名)
String className = p.getProperty((String) key);
//获取字节码对象
Class clazz = Class.forName(className);
Coffee obj = (Coffee) clazz.newInstance();
map.put((String)key,obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}

public static Coffee createCoffee(String name) {

return map.get(name);
}
}

静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。

JDK源码解析-Collection.iterator方法

pkb9A8e.png

Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。

4.3 原型模式

我的理解这个模式就是克隆,把一个类当作原型,复制创建信对象。

结构

  • 抽象原型类: 规定了clone()方法
  • 具体原型类:作为可以被复制的对象,实现了clone()方法
  • 访问类:使用原型类进行克隆对象

类图:

pkb9mDI.png

用代码理解:

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。

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
//奖状类
public class Citation implements Cloneable {
private String name;

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

public String getName() {
return (this.name);
}

public void show() {
System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
}

@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}

//测试访问类
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("张三");

//复制奖状
Citation c2 = c1.clone();
//将奖状的名字修改李四
c2.setName("李四");

c1.show();
c2.show();
}
}

浅克隆:创建新对象,新对象的属性和原来对象完全相同,对于非基本类型属性(比如聚合了其它的类),仍指向原有属性所指向的对象的内存地址。

深克隆:创建一个新对象,被聚合的对象也会被克隆。

使用场景:

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。

4.4 建造者模式

4.4.1 概述

将复杂对象的创建和组合分离,比如有一个电脑对象,很复杂有CPU,内存条,硬盘等,我们专门拿个类做创建CPU这些部件,另外一个类(人)来组装这些部件。

  • 分离了部件的构造和装配。
  • 由于实现了构建和装配的解耦。->不同的构建器,相同的装配,也可以做出不同的对象。 相同的构建器,不同的装配顺序也可以做出不同的对象。
  • 用户只需要指定复杂对象类型就可以得到对象,无需知道内部细节

4.4.2 结构

  • 抽象建造者类(Build):规定要实现复杂对象哪些部分的创建。
  • 具体建造者类:实现Build,完成创建各个部件的创建。
  • 产品类:需要创建的复杂对象
  • 指挥者类:调用建造者来创建产品,就是组装的人。

类图:

pkb9Nbq.png

4.4.3 实例

创建共享单车

pkb9aV0.png

Bike是产品,Build是抽象建造者,有摩拜单车的建造者,还有of单车的建造者,最后还有一个组装者

具体代码:

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
//自行车类
public class Bike {
private String frame;
private String seat;

public String getFrame() {
return frame;
}

public void setFrame(String frame) {
this.frame = frame;
}

public String getSeat() {
return seat;
}

public void setSeat(String seat) {
this.seat = seat;
}
}

// 抽象 builder 类
public abstract class Builder {

protected Bike mBike = new Bike();

public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}

//摩拜单车Builder类
public class MobikeBuilder extends Builder {

@Override
public void buildFrame() {
mBike.setFrame("铝合金车架");
}

@Override
public void buildSeat() {
mBike.setSeat("真皮车座");
}

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

//ofo单车Builder类
public class OfoBuilder extends Builder {

@Override
public void buildFrame() {
mBike.setFrame("碳纤维车架");
}

@Override
public void buildSeat() {
mBike.setSeat("橡胶车座");
}

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

//指挥者类
public class Director {
private Builder mBuilder;

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

public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}

//测试类
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.getFrame());
System.out.println(bike.getSeat());
}
}

优点:

  • 建造者模式的封装性很好。
  • 在建造者模式中,客户端不必知道产品内部组成的细节就能创建产品,更好的解耦,使得相同的创建过程创建不同的产品。
  • 可以更加精细地控制产品的创建过程 。有指挥者可以将创建过程分解。
  • 建造者模式很容易进行扩展。如果有新需求,只需要增加新的建造者,不需要修改之前代码。

缺点:

  • 建造者的产品一般具有共同点。如果差异较大,则不适合。

4.4.5 使用场景

  • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
  • 创建复杂过程的算法比较独立。

4.4.6 模式扩展

我们平常使用build的链式调用就是这么实现的,如果用构造方法,代码可读性很差。

重构后代码:

使用单例模式+建造者模式可实现链式调用

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

private String cpu;
private String screen;
private String memory;
private String mainboard;

//私有的不允许访问
private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}

//使用静态内部类的方式创建
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;

public Builder() {}

public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
//提供对外访问的方法
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}

public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("华硕")
.memory("金士顿")
.screen("三星")
.build();
System.out.println(phone);
}
}

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。

4.5 创建者模式对比

4.5.1 工厂方法模式VS建造者模式

  • 工厂方法:注重的是整体对象的创建方式
  • 建造者模式:注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

工厂就是直接创建出来一个完整的,而建造者就是一步一步构建的。

4.5.2 抽象工厂模式VS建造者模式

  • 抽象工厂模式:对产品家族的创建,一个产品家族是这样的一系列产品。

  • 建造者模式:要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。


5 结构型模式

就像建房子的工程一样,建房子有着各自各样的建造结构。

分为:

  • 类结构型模式:采用继承机制组织,耦合度较高。
  • 对象结构型模式:采用组合或者聚合来组织,耦合度较低,满足合成复用原则。

结构模式分为:

  • 代理模式
  • 适配者模式
  • 装饰着模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

5.1 代理模式

就像一个中间人,你通过中间人来做一些事情。

访问对象(你)和目标对象(房东)没有直接关系(你不认识他),这个时候就需要代理(中介)来操作,代理可以做一些增强操作。

代理分为

  • 静态代理:编译时生成
  • 动态代理:Java运行时动态生成
    • JDK代理
    • CGLib代理

结构:

  • 抽象主题:通过接口或者抽象类声明代理对象和真实业务实现的方法。
  • 真实主题:实现抽象主题的具体方法,是最终要引用的类。
  • 代理:提供与真实主题一样的接口,内部含有真实主题的引用,并且可以做扩展和增强

静态代理

pkbBRg0.png

这是一个火车购票系统,我们到火车站买票太麻烦,就需要线上购买,这个线上系统就是一个代理。火车站就是目标对象。

代码:

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 interface SellTickets {
void sell();
}

//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

public void sell() {
System.out.println("火车站卖票");
}
}

//代售点
public class ProxyPoint implements SellTickets {

private TrainStation station = new TrainStation();

public void sell() {
System.out.println("代理点收取一些服务费用");
station.sell();
}
}

//测试类
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint();
pp.sell();
}
}

JDK动态代理

使用动态代理实现代码

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
//卖票接口
public interface SellTickets {
void sell();
}

//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {

public void sell() {
System.out.println("火车站卖票");
}
}

public class ProxyFactory {

//声明目标对象
private TrainStation station = new TrainStation();

//获取代理对象的方法
public SellTickets getProxyObject() {
//返回代理对象
/*
ClassLoader loader : 类加载器,用于加载代理类。可以通过目标对象获取类加载器
Class<?>[] interfaces : 代理类实现的接口的字节码对象
InvocationHandler h : 代理对象的调用处理程序
*/
/*
Object proxy : 代理对象。和proxyObject对象是同一个对象,在invoke方法中基本不用
Method method : 对接口中的方法进行封装的method对象
Object[] args : 调用方法的实际参数

返回值: 方法的返回值。
*/
SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
(proxy, method, args) -> {
//System.out.println("invoke方法执行了");
System.out.println("代售点收取一定的服务费用(jdk动态代理)");
//执行目标对象的方法
Object obj = method.invoke(station, args);
return obj;
}
);
return proxyObject;
}
}

//测试类
public class Client {
public static void main(String[] args) {
//获取代理对象
ProxyFactory factory = new ProxyFactory();

SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}

我们的代理工厂类通过反射获取目标对象的属性,然后通过

InvocationHandler中的Invoke方法在运行时实现Proxy接口和反射获取的目标接口,然后动态创建代理类。

代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

运行时的代码:

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
//程序运行过程中动态生成的代理类
public final class $Proxy0 extends Proxy implements SellTickets {
//目标对象
private static Method m3;


public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

//获取目标对象并赋值
static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}

public final void sell() {
this.h.invoke(this, m3, null);
}
}

//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
this.h = h;
}
}

//代理工厂类
public class ProxyFactory {

private TrainStation station = new TrainStation();

public SellTickets getProxyObject() {
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}


//测试访问类
public class Client {
public static void main(String[] args) {
//获取代理对象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}

执行流程如下:

1. 在测试类中通过代理对象调用sell()方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

CGLIB动态代理

跟JDK不同的是CGLIB动态代理不需要定义抽象接口类进行代理。

代码:

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
//火车站
public class TrainStation {

public void sell() {
System.out.println("火车站卖票");
}
}

//代理工厂
public class ProxyFactory implements MethodInterceptor {

private TrainStation target = new TrainStation();

public TrainStation getProxyObject() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer =new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation obj = (TrainStation) enhancer.create();
return obj;
}

/*
intercept方法参数说明:
o : 代理对象
method : 真实对象中的方法的Method实例
args : 实际参数
methodProxy :代理对象中的方法的method实例
*/
public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
return result;
}
}

//测试类
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();

proxyObject.sell();
}
}

三种代理的对比

  • jdk代理和CGLIB代理
    • 用CGLIB代理在JDK1,6之前比jdk代理效率高,但不同对final的类进行代理。
    • 在之后JDK优化之后是JDK代理效率高一些。
    • 有接口时使用JDK代理,没有接口时使用CGLIB代理。
  • 动态代理VS静态代理
    • 最大优势:将接口声明的所有方法都被转移到一个集中方法处理。不需要像静态代理一样每一个方法进行中转。
    • 接口增加方法,所有子类都需要实现这个方法,增加了代码的耦合度。

优缺点

优点:

  • 两个不相干的对象交流,中介起到了一个保护作用
  • 代理可以扩展原本的功能,AOP就是使用代理模式实现的。
  • 将客户端和目标对象解耦。

缺点:

增加系统复杂度

使用场景

  • 远程代理
  • 防火墙代理
  • 保护代理

5.2 适配器模式

就像充电器一样,我们的手机接口和插板不兼容,这就需要用充电器转换。

定义

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

可以分为:

  • 类适配器:耦合度高,需要了解内部
  • 对象适配器:耦合度低。

结构

  • 目标接口:就是类似与插板,可以是抽象类或者接口
  • 适配者类:就是类似与你的手机接口,
  • 适配者类:转换器,过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式

实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

例如:电脑只能读取SD卡,但现在要求你读取TF卡的数据。

pkq5HQU.png

定义SD卡,TF卡接口,让并将其实现,然后定义一个适配器类继承SD卡接口实现转换。

但类适配器模式违背了合成复用原则。

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
//SD卡的接口
public interface SDCard {
//读取SD卡方法
String readSD();
//写入SD卡功能
void writeSD(String msg);
}

//SD卡实现类
public class SDCardImpl implements SDCard {
public String readSD() {
String msg = "sd card read a msg :hello word SD";
return msg;
}

public void writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
}
}

//电脑类
public class Computer {

public String readSD(SDCard sdCard) {
if(sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}

//TF卡接口
public interface TFCard {
//读取TF卡方法
String readTF();
//写入TF卡功能
void writeTF(String msg);
}

//TF卡实现类
public class TFCardImpl implements TFCard {

public String readTF() {
String msg ="tf card read msg : hello word tf card";
return msg;
}

public void writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
}
}

//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {

public String readSD() {
System.out.println("adapter read tf card ");
return readTF();
}

public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}

//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));

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

SDAdapterTF adapter = new SDAdapterTF();
System.out.println(computer.readSD(adapter));
}
}

对象适配器模式

实现方式:适配器类要聚合转换的接口,其余跟类适配器模式相同

pkq5joR.png

区别就是TFCardImpl没有继承转换器,而是转换器聚合了TFCard接口。

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
/创建适配器对象(SD兼容TF)
public class SDAdapterTF implements SDCard {

private TFCard tfCard;

public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}

public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}

public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}

//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));

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

TFCard tfCard = new TFCardImpl();
SDAdapterTF adapter = new SDAdapterTF(tfCard);
System.out.println(computer.readSD(adapter));
}
}

应用场景

  • 旧系统存在可以执行的类,但新系统与其不兼容。
  • 第三方提供的组件,但组件接口的定义和自己的要求不相同。

JDK源码解析

Reader(字符流)、InputStream(字节流)的适配使用的是InputStreamReader。

这里采用了适配器模式

5.3 装饰者模式

想象一下,你走进一家咖啡店,点了一杯基础的美式咖啡。但是,你可能还想加点糖、牛奶或者奶油来让咖啡更加美味。装饰者设计模式就是让你能够自由地给咖啡添加这些额外的“装饰”。

就是在原有的基础上加东西让它变的好看。

结构

  • 抽象构建角色:定义了一个对象接口,可以给这些对象动态地添加职责。比如一个咖啡类,它定义了咖啡的基本属性和方法。
  • 具体构建角色:实现抽象构件。
  • 抽象装饰角色:持有一个组件对象的引用,并定义装饰物品的方法。
  • 具体装饰角色:实现抽象装饰角色。

pkqIETA.png

比如这个快餐店案例,FastFood就是抽象构建角色,它有两个子实现。

Garish就是抽象装饰角色,它聚合了快餐类,并定义了子实现类的规范。

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
//快餐接口
public abstract class FastFood {
private float price;
private String desc;

public FastFood() {
}

public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}

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

public float getPrice() {
return price;
}

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

public abstract float cost(); //获取价格
}

//炒饭
public class FriedRice extends FastFood {

public FriedRice() {
super(10, "炒饭");
}

public float cost() {
return getPrice();
}
}

//炒面
public class FriedNoodles extends FastFood {

public FriedNoodles() {
super(12, "炒面");
}

public float cost() {
return getPrice();
}
}

//配料类
public abstract class Garnish extends FastFood {

private FastFood fastFood;

public FastFood getFastFood() {
return fastFood;
}

public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}

public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}

//鸡蛋配料
public class Egg extends Garnish {

public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}

public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//培根配料
public class Bacon extends Garnish {

public Bacon(FastFood fastFood) {

super(fastFood,2,"培根");
}

@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//测试类
public class Client {
public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");

System.out.println("========");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();

food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");

System.out.println("========");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}

好处:

  • 提供更比继承加灵活的扩展。
  • 实现了装饰类和被装饰类的解耦。可动态扩展

    使用场景

  • 不使用继承进行系统维护时

    不能采用继承的情况

    • 系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长(类爆炸)
    • 类不能定义为继承。
  • 不影响其它对象,并为其动态,透明的给单个对象添加职责。


JDK源码解析

IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

pkqI8Ts.png

代理和装饰者的区别

静态代理和装饰者模式的区别:

  • 相同点
    • 都要实现与目标类相同的业务接口
    • 都要声明目标对象
    • 都可以增强目标方法
  • 不同点:
    • 静态代理是为了保护和隐藏目标对象
    • 装饰着是为了增强目标对象
    • 装饰者是由外界传递进来,可以通过构造方法传递
      静态代理是在代理类内部创建,以此来隐藏目标对象

5.4 桥接模式

想象一下,你正在设计一个软件,这个软件可以打印不同的文档。这些文档有不同的格式,比如PDF、Word、PPT,同时,打印的方式也可以不同,比如黑白打印、彩色打印、双面打印等。如果使用传统的继承方式,你可能会得到很多组合,这会导致类的数量急剧增加,而且难以管理。

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

结构:

  • 抽象化角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化角色:给出实现化角色接口的具体实现。

需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。

类图如下:

pkqITAA.png

OperatingSystem就是抽象化角色,里面包含了实现化角色(视频文件)的引用,Windows和Linux就是扩展抽象化角色,两个实现类AVI格式就是具体实现化角色

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
//视频文件
public interface VideoFile {
void decode(String fileName);
}

//avi文件
public class AVIFile implements VideoFile {
public void decode(String fileName) {
System.out.println("avi视频文件:"+ fileName);
}
}

//rmvb文件
public class REVBBFile implements VideoFile {

public void decode(String fileName) {
System.out.println("rmvb文件:" + fileName);
}
}

//操作系统版本
public abstract class OperatingSystemVersion {

protected VideoFile videoFile;

public OperatingSystemVersion(VideoFile videoFile) {
this.videoFile = videoFile;
}

public abstract void play(String fileName);
}

//Windows版本
public class Windows extends OperatingSystem {

public Windows(VideoFile videoFile) {
super(videoFile);
}

public void play(String fileName) {
videoFile.decode(fileName);
}
}

//mac版本
public class Mac extends OperatingSystemVersion {

public Mac(VideoFile videoFile) {
super(videoFile);
}

public void play(String fileName) {
videoFile.decode(fileName);
}
}

//测试类
public class Client {
public static void main(String[] args) {
OperatingSystem os = new Windows(new AVIFile());
os.play("战狼3");
}
}

好处:

  • 提高了系统的可扩充性
  • 实现细节对客户透明

使用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。

桥接模式和装饰者模式有什么区别

桥接模式

桥接模式就像是你有一个多功能的遥控器,它可以控制不同的电子设备,比如电视、音响、灯光等。每种设备都有自己的功能和特性,遥控器并不关心这些设备的具体实现,它只关心如何发送指令。

  • 目的:桥接模式的目的是将一个类的抽象部分和它的实现部分分离,使它们可以独立地变化。
  • 场景:当你有多个类层次结构,并且想要避免它们之间的强耦合时,使用桥接模式。

装饰者模式

装饰者模式就像是你走进一家咖啡店,点了一杯基础的咖啡,然后可以选择添加不同的调料,比如糖、牛奶、奶油等,来定制你的咖啡。每添加一种调料,都会给咖啡增加一些新的属性或行为。

  • 目的:装饰者模式的目的是动态地给一个对象添加额外的职责或行为。
  • 场景:当你想要在不修改原有对象的情况下,给对象添加新功能时,使用装饰者模式。

区别

  1. 目的不同
    • 桥接模式关注的是将抽象和实现分离,让它们可以独立地扩展。
    • 装饰者模式关注的是动态地给对象添加额外的职责。
  2. 使用场景不同
    • 桥接模式适用于处理多个类层次结构,避免它们之间的耦合。
    • 装饰者模式适用于在运行时动态地扩展对象的功能。
  3. 实现方式不同
    • 桥接模式通过定义两个独立的类层次结构来实现,一个是抽象部分,另一个是实现部分。
    • 装饰者模式通过定义一个抽象装饰者和多个具体装饰者来实现,它们持有并装饰一个组件对象。
  4. 灵活性
    • 桥接模式提供了在两个维度上的灵活性,你可以独立地扩展抽象部分和实现部分。
    • 装饰者模式提供了在单个维度上的灵活性,你可以逐层地添加装饰者来扩展功能。

用一个简单的比喻来总结:桥接模式就像是你有一个可以更换镜头的相机,你可以根据不同的拍摄需求更换镜头;而装饰者模式就像是你给相机添加不同的滤镜,每次添加都会改变照片的效果。

5.5 外观模式

外观模式就像是你使用一个遥控器来控制家庭影院系统。遥控器上有简单的按钮,比如“播放”、“暂停”、“音量+”、“音量-”等。你不需要知道家庭影院的内部是如何工作的,也不需要分别操作DVD播放器、音响系统和电视。你只需要按遥控器上的按钮,就可以享受电影。

pkqIHht.jpg

外观(Facade)模式是“迪米特法则”的典型应用

结构:

  • 外观角色:为多个子系统对外提供一个共同的接口。
  • 子系统角色:实现系统的部分功能,客户可以通过外观角色访问它。

案例

pkqIOc8.png

小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。

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
//灯类
public class Light {
public void on() {
System.out.println("打开了灯....");
}

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

//电视类
public class TV {
public void on() {
System.out.println("打开了电视....");
}

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

//控制类
public class AirCondition {
public void on() {
System.out.println("打开了空调....");
}

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

//智能音箱
public class SmartAppliancesFacade {

private Light light;
private TV tv;
private AirCondition airCondition;

public SmartAppliancesFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}

public void say(String message) {
if(message.contains("打开")) {
on();
} else if(message.contains("关闭")) {
off();
} else {
System.out.println("我还听不懂你说的!!!");
}
}

//起床后一键开电器
private void on() {
System.out.println("起床了");
light.on();
tv.on();
airCondition.on();
}

//睡觉一键关电器
private void off() {
System.out.println("睡觉了");
light.off();
tv.off();
airCondition.off();
}
}

//测试类
public class Client {
public static void main(String[] args) {
//创建外观对象
SmartAppliancesFacade facade = new SmartAppliancesFacade();
//客户端直接与外观对象进行交互
facade.say("打开家电");
facade.say("关闭家电");
}
}

好处:

  • 降低子系统和客户端的耦合
  • 对客户端屏蔽子系统组件

缺点:

  • 修改很麻烦,违背了开闭原则。

使用场景:

  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

5.6 组合模式

想象一下,你有一个由多个文件夹和文件组成的文件系统。在这个系统中,每个文件夹可以包含文件和其他文件夹,形成一个层次结构。当你需要对文件系统中的某个部分进行操作时,不管是单个文件还是包含多个文件和子文件夹的文件夹,操作的方式都是相同的。

定义:

把一组相似的对象当作一个单一的对象,组合模式依据树形结构来组合对象。

结构

  • 抽象根节点:定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
  • 树形节点:定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
  • 叶子节点:叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

实现

软件菜单

pkLgWWD.png

pkLghSe.png

实现这种菜单中:

MenuCompent:是抽象根节点定义了节点的基本实现

Menu:树形节点,实现抽象根节点又聚合抽象根节点

MenuItem:叶子节点

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
//菜单组件  不管是菜单还是菜单项,都应该继承该类
public abstract class MenuComponent {

protected String name;
protected int level;

//添加菜单
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}

//移除菜单
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}

//获取指定的子菜单
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}

//获取菜单名称
public String getName(){
return name;
}

public void print(){
throw new UnsupportedOperationException();
}
}
//Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。
public class Menu extends MenuComponent {

private List<MenuComponent> menuComponentList;

public Menu(String name,int level){
this.level = level;
this.name = name;
menuComponentList = new ArrayList<MenuComponent>();
}

@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}

@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}

@Override
public MenuComponent getChild(int i) {
return menuComponentList.get(i);
}

@Override
public void print() {

for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
//MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。
public class MenuItem extends MenuComponent {

public MenuItem(String name,int level) {
this.name = name;
this.level = level;
}

@Override
public void print() {
for (int i = 1; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}

组合模式的分类

  • 透明组合模式

    透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

    缺点就是不够安全,叶子节点不实现某些方法,但继承又要实现这些方法,如果没写好就可能会有错误

  • 安全组合模式

    在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。

    pkL2Z6J.png

    缺点就是不够透明。

优点

  • 灵活性:组合模式使得你可以在运行时动态地添加或删除对象,而不需要修改现有代码。

  • 一致性:用户可以以相同的方式处理单个对象和组合对象,这简化了客户端代码。

  • 可扩展性:通过增加新的叶节点或容器节点,可以轻松扩展系统。
  • 简化设计:它简化了对象的组织和管理,使得设计更加清晰。

使用场景

  • 出现树形结构的地方

5.7 享元模式

享元模式想象成一个资源节约的策略,它通过共享不变的部分来减少资源消耗,同时允许每个对象保持其独特的状态或行为。

结构:

  • 内部状态:不会随着环境而改变的状态,可共享部分
  • 外部状态:随环境改变而改变的状态,不可共享部分

角色:

  • 抽象享元角色:声明具体享元类公共方法,通常是接口或抽象类
  • 具体享元角色:实现抽象享元类,在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元角色:享元对象的某些状态不能共享,这些状态被称为外部状态。非享元角色通常用来存储这些外部状态。
  • 享元工厂:负责创建和管理享元角色。

案例

俄罗斯方块:

每种方块出现都需要创建,太占用内存,这时候我们把不同方块都当成一个实例对象。

pkL2ltK.png

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 abstract class AbstractBox {
public abstract String getShape();

public void display(String color) {
System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
}
}
//定义不同的形状了,IBox类、LBox类、OBox类等。
public class IBox extends AbstractBox {

@Override
public String getShape() {
return "I";
}
}

public class LBox extends AbstractBox {

@Override
public String getShape() {
return "L";
}
}

public class OBox extends AbstractBox {

@Override
public String getShape() {
return "O";
}
}
//提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
public class BoxFactory {

private static HashMap<String, AbstractBox> map;

private BoxFactory() {
map = new HashMap<String, AbstractBox>();
AbstractBox iBox = new IBox();
AbstractBox lBox = new LBox();
AbstractBox oBox = new OBox();
map.put("I", iBox);
map.put("L", lBox);
map.put("O", oBox);
}

public static final BoxFactory getInstance() {
return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
private static final BoxFactory INSTANCE = new BoxFactory();
}

public AbstractBox getBox(String key) {
return map.get(key);
}
}

优缺点和使用场景

优点

  • 极大节省相似对象的内存
  • 外部状态相对独立,且不影响内部状态

缺点:

  • 让程序复杂

使用场景

  • 场景中有大量相似或者相同的对象,造成内存大量消耗。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

JDK源码解析

Integer类使用了享元模式。

Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。

6 行为型模式

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。

  • 模板方法模式
  • 策略模式
  • 命令模式
  • 职责链模式
  • 状态模式
  • 观察者模式
  • 中介者模式
  • 迭代器模式
  • 访问者模式
  • 备忘录模式
  • 解释器模式

除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。


6.1 模板方法模式

概述

想象一下你是一家餐厅的厨师长,你负责制定一道菜的烹饪流程。这个流程包括几个基本步骤:准备食材、烹饪、装盘。但是,不同的菜可能在这些步骤中有不同的做法。比如,做鱼和做鸡的步骤虽然大体相似,但具体细节却大不相同。

模板方法模式就像是你制定的这个烹饪流程,它规定了基本的步骤顺序,但允许不同的菜肴(子类)在这些步骤中有自己的特色做法。

结构

  • 抽象类:负责给出一个算法的轮廓和骨架。由一个模板方法和诺干其余方法组成

    • 模板方法:定义算法骨架

    • 基本方法:实现算法个步骤的方法

      可分为:

      • 抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。
      • 具体方法:个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承
      • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  • 具体子类:实现抽象类。

案例

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

pkL2s1g.png

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 abstract class AbstractClass {
//模板方法
public final void cookProcess() {
//第一步:倒油
this.pourOil();
//第二步:热油
this.heatOil();
//第三步:倒蔬菜
this.pourVegetable();
//第四步:倒调味料
this.pourSauce();
//第五步:翻炒
this.fry();
}

public void pourOil() {
System.out.println("倒油");
}
//其余都是具体方法

//第二步:热油是一样的,所以直接实现
public void heatOil() {
System.out.println("热油");
}

//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
public abstract void pourVegetable();

//第四步:倒调味料是不一样
public abstract void pourSauce();


//第五步:翻炒是一样的,所以直接实现
public void fry(){
System.out.println("炒啊炒啊炒到熟啊");
}
}

public class ConcreteClass_BaoCai extends AbstractClass {

@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是包菜");
}

@Override
public void pourSauce() {
System.out.println("下锅的酱料是辣椒");
}
}

public class ConcreteClass_CaiXin extends AbstractClass {
@Override
public void pourVegetable() {
System.out.println("下锅的蔬菜是菜心");
}

@Override
public void pourSauce() {
System.out.println("下锅的酱料是蒜蓉");
}
}

public class Client {
public static void main(String[] args) {
//炒手撕包菜
ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
baoCai.cookProcess();

//炒蒜蓉菜心
ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
caiXin.cookProcess();
}
}

一般模板方法都需要加上final,防止子类重写修改算法。


优缺点

优点:

  • 提高代码复用性
  • 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时。
  • 需要通过子类来决定父类算法中某个步骤是否执行

JDK源码解析

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法

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
public abstract class InputStream implements Closeable {
//抽象方法,要求子类必须重写
public abstract int read() throws IOException;

public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}

public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}

int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
if (c == -1) {
return -1;
}
b[off] = (byte)c;

int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}

在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。


6.2 策略模式

pkL2R7q.png

作为程序员,可以选择多种编译器开发软件。

想象一下,你是一个旅行者,计划去不同的城市旅行。每个城市都有不同的交通方式:有些地方适合开车,有些地方适合骑自行车,还有些地方适合步行。

策略模式就像是你旅行时的交通方式选择方案。你可以根据目的地和个人喜好,灵活地选择最合适的交通方式。

结构:

  • 抽象策略:这就像是你的旅行计划,定义了基本的交通方式(开车、骑自行车、步行)。每种交通方式都有一些基本的操作,比如“前进”、“停止”。
  • 具体策略:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境类:持有一个策略类的引用,最终给客户端调用。

实现:

pkL2jN6.png

商店买东西提供了不同从促销活动

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 interface Strategy {
void show();
}

//具体策略
//为春节准备的促销活动A
public class StrategyA implements Strategy {

public void show() {
System.out.println("买一送一");
}
}

//为中秋准备的促销活动B
public class StrategyB implements Strategy {

public void show() {
System.out.println("满200元减50元");
}
}

//为圣诞准备的促销活动C
public class StrategyC implements Strategy {

public void show() {
System.out.println("满1000元加一元换购任意200元以下商品");
}
}

//定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
public class SalesMan {
//持有抽象策略角色的引用
private Strategy strategy;

public SalesMan(Strategy strategy) {
this.strategy = strategy;
}

//向客户展示促销活动
public void salesManShow(){
strategy.show();
}
}

优缺点

优点:

  • 策略类之间可以自由切换
  • 易于扩展
  • 避免使用多重条件选择语句(if else)

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类。

使用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

JDK源码解析

Comparator 中的策略模式。

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
public class Arrays{
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
}
//测试类
public class demo {
public static void main(String[] args) {

Integer[] data = {12, 2, 3, 2, 4, 5, 1};
// 实现降序排序
Arrays.sort(data, new Comparator<Integer>() {
//有就按你传的策略来进行排序
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1]
}
}

Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。

6.3 命令模式

想象一下,你是一个餐厅的顾客,你想要点菜。在命令模式中,你的点菜请求就像是发出一个命令。

定义

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。

结构

  • 抽象命令类:这就像是菜单上的菜品列表,它定义了所有可能的请求的格式。每个菜品都对应一个命令。
  • 具体命令类:实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者:接收者,真正执行命令的对象。
  • 调用者/请求者:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。

实现

pkLRFHI.png

代码:

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
//抽象命令类
public interface Command {
void execute();//只需要定义一个统一的执行方法
}

//具体命令类
public class OrderCommand implements Command {

//持有接受者对象-厨师
private SeniorChef receiver;
private Order order;

public OrderCommand(SeniorChef receiver, Order order){
this.receiver = receiver;
this.order = order;
}

public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Set<String> keys = order.getFoodDic().keySet();
for (String key : keys) {
receiver.makeFood(order.getFoodDic().get(key),key);
}

try {
Thread.sleep(100);//停顿一下 模拟做饭的过程
} catch (InterruptedException e) {
e.printStackTrace();
}


System.out.println(order.getDiningTable() + "桌的饭弄好了");
}
}

//订单
public class Order {
// 餐桌号码
private int diningTable;

// 用来存储餐名并记录份数
private Map<String, Integer> foodDic = new HashMap<String, Integer>();

public int getDiningTable() {
return diningTable;
}

public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}

public Map<String, Integer> getFoodDic() {
return foodDic;
}

public void setFoodDic(String name, int num) {
foodDic.put(name,num);
}
}

// 资深大厨类 是命令的Receiver
public class SeniorChef {

public void makeFood(int num,String foodName) {
System.out.println(num + "份" + foodName);
}
}

//调用者-服务员
public class Waitor {

private ArrayList<Command> commands;//可以持有很多的命令对象

public Waitor() {
commands = new ArrayList();
}

public void setCommand(Command cmd){
commands.add(cmd);
}

// 发出命令 喊 订单来了,厨师开始执行
public void orderUp() {
System.out.println("美女服务员:叮咚,大厨,新订单来了.......");
for (int i = 0; i < commands.size(); i++) {
Command cmd = commands.get(i);
if (cmd != null) {
cmd.execute();
}
}
}
}

public class Client {
public static void main(String[] args) {
//创建2个order
Order order1 = new Order();
order1.setDiningTable(1);
order1.getFoodDic().put("西红柿鸡蛋面",1);
order1.getFoodDic().put("小杯可乐",2);

Order order2 = new Order();
order2.setDiningTable(3);
order2.getFoodDic().put("尖椒肉丝盖饭",1);
order2.getFoodDic().put("小杯雪碧",1);

//创建接收者
SeniorChef receiver=new SeniorChef();
//将订单和接收者封装成命令对象
OrderCommand cmd1 = new OrderCommand(receiver, order1);
OrderCommand cmd2 = new OrderCommand(receiver, order2);
//创建调用者 waitor
Waitor invoker = new Waitor();
invoker.setCommand(cmd1);
invoker.setCommand(cmd2);

//将订单带到柜台 并向厨师喊 订单来了
invoker.orderUp();
}
}

优缺点

优点:

  • 降低系统耦合度,将调用操作的对象与实现该操作的对象解耦。
  • 增加和删除命令十分方便
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点:

  • 是系统变复杂
  • 会有太多具体命令类

使用场景

  • 系统需要将请求调用者和请求接收者解耦,且双方不直接交互。
  • 系统需要在不同的时间指定请求、
  • 将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

JDK源码解析

Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

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
//命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}

//调用者
public class Thread implements Runnable {
private Runnable target;

public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();

group.add(this);

boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}

private native void start0();
}
//会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体
*/
public class TurnOffThread implements Runnable{
private Receiver receiver;

public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
receiver.turnOFF();
}
}
/**
* 测试类
*/
public class Demo {
public static void main(String[] args) {
Receiver receiver = new Receiver();
TurnOffThread turnOffThread = new TurnOffThread(receiver);
Thread thread = new Thread(turnOffThread);
thread.start();
}
}

6.4 责任链模式

想象一下,你在一个公司工作,当你遇到一个问题时,你首先会向你的直接上司求助。如果上司解决不了,他可能会把问题交给他的上司,也就是你的大老板。这个过程就像是一条链,每个环节都有机会处理这个问题,直到问题被解决为止。

定义:

又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

结构

  • 抽象处理者:定义处理请求接口,包含抽象处理方法和后继连接。
  • 具体处理者:实现抽象处理者的处理方法,如果不能处理请求,转发给后继者
  • 客户类角色:创建处理类,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

实现:

pkOE2rT.png

上面的案例的实现

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
//请假条
public class LeaveRequest {
private String name;//姓名
private int num;//请假天数
private String content;//请假内容

public LeaveRequest(String name, int num, String content) {
this.name = name;
this.num = num;
this.content = content;
}

public String getName() {
return name;
}

public int getNum() {
return num;
}

public String getContent() {
return content;
}
}

//处理者抽象类
public abstract class Handler {
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;

//该领导处理的请假天数区间
private int numStart;
private int numEnd;

//领导上面还有领导-后继者
private Handler nextHandler;

//设置请假天数范围 上不封顶
public Handler(int numStart) {
this.numStart = numStart;
}

//设置请假天数范围
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}

//设置上级领导
public void setNextHandler(Handler nextHandler){
this.nextHandler = nextHandler;
}

//提交请假条
public final void submit(LeaveRequest leave){
if(0 == this.numStart){
return;
}

//如果请假天数达到该领导者的处理要求
if(leave.getNum() >= this.numStart){
this.handleLeave(leave);

//如果还有上级 并且请假天数超过了当前领导的处理范围
if(null != this.nextHandler && leave.getNum() > numEnd){
this.nextHandler.submit(leave);//继续提交
} else {
System.out.println("流程结束");
}
}
}

//各级领导处理请假条方法
protected abstract void handleLeave(LeaveRequest leave);
}

//小组长
public class GroupLeader extends Handler {
public GroupLeader() {
//小组长处理1-3天的请假
super(Handler.NUM_ONE, Handler.NUM_THREE);
}

@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("小组长审批:同意。");
}
}

//部门经理
public class Manager extends Handler {
public Manager() {
//部门经理处理3-7天的请假
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}

@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("部门经理审批:同意。");
}
}

//总经理
public class GeneralManager extends Handler {
public GeneralManager() {
//部门经理处理7天以上的请假
super(Handler.NUM_SEVEN);
}

@Override
protected void handleLeave(LeaveRequest leave) {
System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
System.out.println("总经理审批:同意。");
}
}
//测试类
public class Client {
public static void main(String[] args) {
//请假条来一张
LeaveRequest leave = new LeaveRequest("小花",5,"身体不适");

//各位领导
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();

groupLeader.setNextHandler(manager);//小组长的领导是部门经理
manager.setNextHandler(generalManager);//部门经理的领导是总经理
//之所以在这里设置上级领导,是因为可以根据实际需求来更改设置,如果实战中上级领导人都是固定的,则可以移到领导实现类中。

//提交申请
groupLeader.submit(leave);
}
}

优缺点

优点

  • 降低对象之间的耦合度-降低请求发送者和接收者的耦合度
  • 增强系统的可扩展性
  • 责任链简化了对象之间的连接
  • 责任分担

缺点

  • 不能保证每个请求一定被处理。

  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。

  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性

源码解析

在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用


6.5 状态模式

想象一下,你有一个智能助手,它可以根据不同的情况(状态)来调整它的行为。比如,当你在工作时,智能助手会保持安静,只在你提问时才回答;当你在休息时,它可能会播放轻松的音乐;当你在运动时,它可能会播放动感的音乐并提醒你注意呼吸。

状态模式的核心思想是将行为和状态封装在不同的类中,这样当状态改变时,只需要改变对象的状态类即可,而不需要修改对象本身的代码。

结构

  • 环境角色:这是包含状态的对象,它将不同的状态作为其行为的一部分。
  • 抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态角色:实现抽象状态所对应的行为

实现

通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。

pkOVwy6.png

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
//抽象状态类
public abstract class LiftState {
//定义一个环境角色,也就是封装状态的变化引起的功能变化
protected Context context;

public void setContext(Context context) {
this.context = context;
}

//电梯开门动作
public abstract void open();

//电梯关门动作
public abstract void close();

//电梯运行动作
public abstract void run();

//电梯停止动作
public abstract void stop();
}


//环境角色
public class Context {
//定义出所有的电梯状态
public final static OpenningState openningState = new OpenningState();//开门状态,这时候电梯只能关闭
public final static ClosingState closeingState = new ClosingState();//关闭状态,这时候电梯可以运行、停止和开门
public final static RunningState runningState = new RunningState();//运行状态,这时候电梯只能停止
public final static StoppingState stoppingState = new StoppingState();//停止状态,这时候电梯可以开门、运行


//定义一个当前电梯状态
private LiftState liftState;

public LiftState getLiftState() {
return this.liftState;
}

public void setLiftState(LiftState liftState) {
//当前环境改变
this.liftState = liftState;
//把当前的环境通知到各个实现类中
this.liftState.setContext(this);
}

public void open() {
this.liftState.open();
}

public void close() {
this.liftState.close();
}

public void run() {
this.liftState.run();
}

public void stop() {
this.liftState.stop();
}
}
//开启状态
public class OpenningState extends LiftState {

//开启当然可以关闭了,我就想测试一下电梯门开关功能
@Override
public void open() {
System.out.println("电梯门开启...");
}

@Override
public void close() {
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}

//电梯门不能开着就跑,这里什么也不做
@Override
public void run() {
//do nothing
}

//开门状态已经是停止的了
@Override
public void stop() {
//do nothing
}
}

//运行状态
public class RunningState extends LiftState {

//运行的时候开电梯门?你疯了!电梯不会给你开的
@Override
public void open() {
//do nothing
}

//电梯门关闭?这是肯定了
@Override
public void close() {//虽然可以关门,但这个动作不归我执行
//do nothing
}

//这是在运行状态下要实现的方法
@Override
public void run() {
System.out.println("电梯正在运行...");
}

//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.stop();
}
}

//停止状态
public class StoppingState extends LiftState {

//停止状态,开门,那是要的!
@Override
public void open() {
//状态修改
super.context.setLiftState(Context.openningState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().open();
}

@Override
public void close() {//虽然可以关门,但这个动作不归我执行
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}

//停止状态再跑起来,正常的很
@Override
public void run() {
//状态修改
super.context.setLiftState(Context.runningState);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().run();
}

//停止状态是怎么发生的呢?当然是停止方法执行了
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}

//关闭状态
public class ClosingState extends LiftState {

@Override
//电梯门关闭,这是关闭状态要实现的动作
public void close() {
System.out.println("电梯门关闭...");
}

//电梯门关了再打开,逗你玩呢,那这个允许呀
@Override
public void open() {
super.context.setLiftState(Context.openningState);
super.context.open();
}


//电梯门关了就跑,这是再正常不过了
@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.run();
}

//电梯门关着,我就不按楼层
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState);
super.context.stop();
}
}


优缺点

优点

  • 封装性:状态模式将与特定状态相关的行为封装在状态对象中,使得状态转换不会影响到其他状态。
  • 灵活性:新增状态时,只需增加相应的状态类,无需修改现有代码,符合开闭原则。
  • 可维护性:状态模式使得状态转换逻辑集中管理,易于追踪和维护状态之间的转换。
  • 可读性:状态模式通过类和接口的实现,使得代码结构清晰,易于理解。

缺点

  • 资源消耗:如果状态非常多,可能会导致系统中存在大量的状态类,增加系统的复杂性。
  • 状态管理:状态模式可能会使得状态转换的管理变得复杂,特别是当状态转换依赖于多个条件时。
  • 状态依赖:状态模式可能导致状态类之间存在依赖关系,这可能会使得状态转换逻辑难以理解和维护。
  • 状态一致性:在某些情况下,需要确保所有状态对象都能保持一致的行为,这可能需要额外的工作来实现。

使用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

6.6 观察者模式

概述

类似与发布订阅模型,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

想象一下,你订阅了一个天气预报服务。每当天气发生变化时,比如从晴天变成雨天,这个服务就会给你发送通知,告诉你天气变了。在这个例子中,天气预报服务就是”被观察者”,而你就是”观察者”。

结构

  • 主题:被观察者,维护一组观察者,并提供增加和删除观察者对象。
  • 观察者:定义更新接口,使得在得到主题更改通知时更新自己。
  • 具体主题:实现主题接口,维护当前状态,当状态改变时,通知所有观察者。
  • 具体观察者:实现观察者接口,以便在得到主题更改通知时更新自身的状态。

实现

微信公众号

我们关注了一个公众号以后,这个公众号有什么事就需要像所有用户发通知。

pkXpRvF.png

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
//观察者
public interface Observer {
//更新方法
void update(String message);
}
//具体观察者
public class WeixinUser implements Observer {
// 微信用户名
private String name;

public WeixinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "-" + message);
}
}
//主题
public interface Subject {
//增加订阅者
public void attach(Observer observer);

//删除订阅者
public void detach(Observer observer);

//通知订阅者更新消息


public void notify(String message);
}

//具体主题
public class SubscriptionSubject implements Subject {
//用来维护存储的观察者对象
//储存订阅公众号的微信用户
private List<Observer> weixinUserlist = new ArrayList<Observer>();

@Override
public void attach(Observer observer) {
weixinUserlist.add(observer);
}

@Override
public void detach(Observer observer) {
weixinUserlist.remove(observer);
}

@Override
public void notify(String message) {
for (Observer observer : weixinUserlist) {
observer.update(message);
}
}
}

//客户端程序
public class Client {
public static void main(String[] args) {
SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
//创建微信用户
WeixinUser user1=new WeixinUser("孙悟空");
WeixinUser user2=new WeixinUser("猪悟能");
WeixinUser user3=new WeixinUser("沙悟净");
//订阅公众号
mSubscriptionSubject.attach(user1);
mSubscriptionSubject.attach(user2);
mSubscriptionSubject.attach(user3);
//公众号更新发出消息给订阅的微信用户
mSubscriptionSubject.notify("传智黑马的专栏更新了");
}
}


优缺点

优点

  • 降低了目标与观察者之间的耦合关系
  • 可以实现广播机制

缺点

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

使用场景

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。

6.7 中介者模式

pkX9p5t.png

想象一下,你在一个大型聚会上,有很多人在交流。但是,如果每个人都要直接找到其他人进行交流,那么这个聚会就会变得非常混乱,人们需要花费很多时间来寻找交流对象。为了避免这种情况,聚会上有一个主持人(中介者),他会协调所有人的交流。如果你想和某个人交流,你只需要告诉主持人,主持人会帮你找到那个人并安排你们交流。

结构

  • 中介者:中介者接口,提供同时对象注册与转发同时对象信息的抽象方法。
  • 具体中介者:实现中介者接口,定义List集合管理同事对象,协调各个同事。协调与各个同事之间的交互关系,它依赖于同事对象。
  • 抽象同事类:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类:抽象同事类的实现类。

案例实现

租房

租房子要有一个中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

pkX9wRK.png

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
//抽象中介者
public abstract class Mediator {
//申明一个联络方法
public abstract void constact(String message,Person person);
}

//抽象同事类
public abstract class Person {
protected String name;
//保存中介者对象
protected Mediator mediator;

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

//具体同事类 承租人
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);
}
}

//中介机构
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);
}
}
}

//测试类
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("我这有三室的房子,你需要租吗?");
}
}

6.8 迭代器模式

就是遍历集合对象数据的一种方式。

结构

  • 迭代器:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
  • 具体迭代器:实现迭代器。
  • 集合:定义了创建迭代器的方法
  • 具体集合:实现了集合接口,提供一个方法来创建具体迭代器实例。

案例:

pkxGc5j.png

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
//StudentIterator:迭代器接口
public interface StudentIterator {
boolean hasNext();
Student next();
}
//具体迭代器
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;
}
}

//集合容器类包含添加元素,删除元素,获取迭代器对象的方法
public interface StudentAggregate {
void addStudent(Student student);

void removeStudent(Student student);

//获取迭代器
StudentIterator getStudentIterator();
}
//具体集合类
public class StudentAggregateImpl implements StudentAggregate {

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

@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);
}
}

优缺点

优点:

  • 迭代器模式提供了一种抽象的遍历方式,允许用户以统一的方式访问集合中的元素,而不需要了解集合的具体实现细节。
  • 迭代器简化了集合类。在原有的集合对象中不需要再自行提供数据遍历等方法,这样可以简化集合类的设计。
  • 易于扩展

缺点

  • 增加了类的个数,提升了系统的复杂性。

6.9 访问者模式

想象一下,你有一个图书馆,里面有各种类型的书籍。现在你想要对这些书籍进行不同的操作,比如计算价格、检查是否需要维修、或者更新书籍的分类信息。每种操作都可能需要访问书籍的不同属性。访问者模式就是用来解决这种问题的。

它允许你将算法与其所作用的对象结构分离,从而可以在不修改对象结构的情况下,增加新的操作。

结构

  • 访问者:定义了对每一个元素(Element)访问的行为。
  • 具体访问者:给出对每一个元素类访问时所产生的具体行为
  • 元素:定义了一个接受访问者的方法,通常称为accept,它接收一个访问者对象作为参数。
  • 具体元素:实现了元素接口,具体元素类将为访问者提供访问其内部元素的方法。
  • 对象结构:定义当中所提到的对象结构

案例实现

给宠物喂食

  • 访问者:给宠物喂食的人
  • 具体访问者:主人、其他人
  • 元素:动物抽象类
  • 具体元素:宠物狗、宠物猫
  • 结构对象:主人家

pkxJ9de.png

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
//访问者
public interface Person {
void feed(Cat cat);

void feed(Dog dog);
}
//具体访问者- 主人
public class Owner implements Person {

@Override
public void feed(Cat cat) {
System.out.println("主人喂食猫");
}

@Override
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
//具体访问者-其它人
public class Someone implements Person {
@Override
public void feed(Cat cat) {
System.out.println("其他人喂食猫");
}

@Override
public void feed(Dog dog) {
System.out.println("其他人喂食狗");
}
}
//元素-宠物
public interface Animal {
void accept(Person person);
}
//具体元素-狗
public class Dog implements Animal {

@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,汪汪汪!!!");
}
}
//具体public class Cat implements Animal {

@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,喵喵喵!!!");
}
}
//元素-猫
public class Cat implements Animal {

@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,喵喵喵!!!");
}
}
//对象结构
public class Home {
private List<Animal> nodeList = new ArrayList<Animal>();

public void action(Person person) {
for (Animal node : nodeList) {
node.accept(person);
}
}

//添加操作
public void add(Animal animal) {
nodeList.add(animal);
}
}

优缺点

优点:

  • 扩展性好

    在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好

    通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为

    通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

2,缺点:

  • 对象结构变化很困难

    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则

    访问者模式依赖了具体类,而没有依赖抽象类。


6.10 备忘录模式

想象一下,你在玩一个视频游戏,游戏中有保存进度的功能。当你达到某个关键点或者想要尝试不同的策略时,你可以保存当前的游戏状态。如果后来你想要回到之前的状态,只需要加载之前保存的状态即可。备忘录模式就是实现这种功能的模式。

又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。

结构

  • 发起人:负责创建一个备忘录,用以记录当前时刻的内部状态,并可以恢复状态。
  • 备忘录:存储发起人的当前状态,供以后恢复使用。
  • 管理者:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录有两个等效的接口:

  • 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
  • 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

案例实现

游戏挑战BOSS

  • 白箱”备忘录模式

    pkxJJyV.png

    备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。

    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
    //游戏角色类-发起人
    public class GameRole {
    private int vit; //生命力
    private int atk; //攻击力
    private int def; //防御力

    //初始化状态
    public void initState() {
    this.vit = 100;
    this.atk = 100;
    this.def = 100;
    }

    //战斗
    public void fight() {
    this.vit = 0;
    this.atk = 0;
    this.def = 0;
    }

    //保存角色状态
    public RoleStateMemento saveState() {
    return new RoleStateMemento(vit, atk, def);
    }

    //回复角色状态
    public void recoverState(RoleStateMemento roleStateMemento) {
    this.vit = roleStateMemento.getVit();
    this.atk = roleStateMemento.getAtk();
    this.def = roleStateMemento.getDef();
    }

    public void stateDisplay() {
    System.out.println("角色生命力:" + vit);
    System.out.println("角色攻击力:" + atk);
    System.out.println("角色防御力:" + def);
    }

    public int getVit() {
    return vit;
    }

    public void setVit(int vit) {
    this.vit = vit;
    }

    public int getAtk() {
    return atk;
    }

    public void setAtk(int atk) {
    this.atk = atk;
    }

    public int getDef() {
    return def;
    }

    public void setDef(int def) {
    this.def = def;
    }
    }

    //游戏状态存储类(备忘录类)
    public class RoleStateMemento {
    private int vit;
    private int atk;
    private int def;

    public RoleStateMemento(int vit, int atk, int def) {
    this.vit = vit;
    this.atk = atk;
    this.def = def;
    }

    public int getVit() {
    return vit;
    }

    public void setVit(int vit) {
    this.vit = vit;
    }

    public int getAtk() {
    return atk;
    }

    public void setAtk(int atk) {
    this.atk = atk;
    }

    public int getDef() {
    return def;
    }

    public void setDef(int def) {
    this.def = def;
    }
    }

    //角色状态管理者类
    public class RoleStateCaretaker {
    private RoleStateMemento roleStateMemento;

    public RoleStateMemento getRoleStateMemento() {
    return roleStateMemento;
    }

    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
    this.roleStateMemento = roleStateMemento;
    }
    }

    //测试类
    public class Client {
    public static void main(String[] args) {
    System.out.println("------------大战Boss前------------");
    //大战Boss前
    GameRole gameRole = new GameRole();
    gameRole.initState();
    gameRole.stateDisplay();

    //保存进度
    RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
    roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

    System.out.println("------------大战Boss后------------");
    //大战Boss时,损耗严重
    gameRole.fight();
    gameRole.stateDisplay();
    System.out.println("------------恢复之前状态------------");
    //恢复之前状态
    gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
    gameRole.stateDisplay();

    }
    }

    白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。

  • 黑箱备忘录模式

    备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。

pkxJ0Y9.png

1
2
3
//窄接口`Memento`,这是一个标识接口,因此没有定义出任何的方法
public interface Memento {
}

文章结束!