【23种设计模式】

23种设计模式

一、设计模式

1、设计模式的诞生

最早将模式这个概念引入到软件工程的是由GoF(Gang of Four)四人组(Erich Gamma,、Richard Helm,、Ralph Johnson、John Vlissides)于1994年发表了 《Design Patterns Elements of Reusable Object-Oriented Software》又称《设计模式-可复用面向对象软件的基础》该书详细讲解了可用于软件工程领域的23种常用设计模式。

1.1、软件设计模式的概念

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

1.2、学习设计模式的意义

(1)设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。

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

(2)当然,软件设计模式只是一个引导。在具体的软件幵发中,必须根据设计的应用系统的特点和要求来恰当选择。对于简单的程序开发,可能写一个简单的算法要比引入某种设计模式更加容易。但对大项目的开发或者框架设计,用设计模式来组织代码显然更好。

2、设计模式分类

2.1 创建型模式:

用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

2.2 结构型模式:

用于描述如何将类或对象按某种布局组成更大的结构,共七种:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

2.3 行为型模式:

用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

3、UML

3.1 概念

UML (Unified Modeling Language)为面向对象软件设计提供统一的、标准的、可视化的建模语言。适用于描述以用例为驱动,以体系结构为中心的软件设计的全过程。

3.2 可见性

在这里插入图片描述
在这里插入图片描述

4、类与类之间关系表示方法

4.1 关联关系

关联关系,Association。关联关系是对象之间的一种引用关系。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 聚合关系

聚合关系,Aggregation,又称聚集关系。聚合关系是关联关系的一种,表示 has-a 的关系,一般用来表示整体与个体的关系。与关联关系一样,聚合关系也是通过实例变量来实现。聚合关系和关联关系在语法上没法区分,但是从语义上可以很好的区分两者的区别。例如,雁群与大雁就是整体与个体的关系。
转载请联系作者获得授权,非商业转载请注明出处。
在这里插入图片描述

4.3 组合关系

在这里插入图片描述

4.4 依赖关系

在这里插入图片描述

4.5 继承关系

在这里插入图片描述

4.6 实现关系

在这里插入图片描述

二、软件设计原则

软件设计原则是设计模式的基石。目的只有一个,降低对象之间的耦合,增加程序的可复用性、可扩展性、可维护性。

1、开闭原则:软件实体应当对扩展开放,对修改关闭

可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
在这里插入图片描述

2、里氏代换原则

也正因为这个原则, 使得继承复用成为了可能, 只有当子类可以替换掉父类, 软件单位的功能不受到影响时,父类才能真正被复用, 而子类也能够在父类的基础上增加新的行为。
在这里插入图片描述
典型案例 案例(正方形不是长方形)

3、依赖倒转原则

3.1 概念

(1)高层模块不应该依赖低层模块,二者都应该依赖其抽象。
(2)抽象不应该依赖细节,细节应该依赖抽象。
(3)依赖倒转的中心思想是面向接口编程。
(4)依赖倒转的设计理念为:相对于细节的多变性,抽象的东西要稳定的多。以抽象的基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
(5)使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

3.2 程序实现步骤,以组装计算机为例

(1)声明CPU、HardDisk、Memory接口。
(2)创建上述三个接口的具体实现类,InterlCPU、XiJieHardDisk、KingstonMemory。
(3)创建高层模块计算机,将三个接口作为其成员变量,其成员变量的Get/Set方法。
在这里插入图片描述
(4)编写测试类即组装计算机
在这里插入图片描述

4、接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中的方法分组,然后用多个接口替代它,每个接口服务于一个子模块。简单地说,就是使用多个专门的接口比使用单个接口要好很多。

4.1接口隔离

(1)一个类对另外一个类的依赖性应当是建立在最小的接口上的。
(2)客户端程序不应该依赖它不需要的接口方法(功能)。

4.2 对接口的污染

(1)过于臃肿的接口设计是对接口的污染。所谓的接口污染就是为接口添加不必要的职责,如果开发人员在接口中增加一个新功能的目的只是减少接口实现类的数目,则此设计将导致接口被不断地“污染”并“变胖”。
(2)“接口隔离”其实就是定制化服务设计的原则。使用接口的多重继承实现对不同的接口的组合,从而对外提供组合功能—达到“按需提供服务”。
(3)接口即要拆,但也不能拆得太细,这就得有个标准,这就是高内聚。接口应该具备一些基本的功能,能独一完成一个基本的任务。

4.3 如何避免接口污染

(1)利用委托分离接口:委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理,如策略模式、代理模式等中都应用到了委托的概念。
(2)利用多继承分离接口。

5、迪米特法则

一个软件实体应当尽可能少的与其他实体发生相互作用。

5.1 迪米特法则描述

(1)如果一个系统满足迪米特法则,那么当其中一个软件实体发生变化时,就会尽量少的影响其他软件实体,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可以降低系统的耦合度,使类与类之间保持松耦合状态。
(2)不要和"陌生人"说话、只与你的直接朋友通信等
(3)在迪米特法则中,对于一个对象,其朋友包括以下几类:

当前对象本身(this)
以参数形式传入到当前对象方法中的对象
当前对象的成员对象
如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友。
当前对象创建的对象

5.2 案例

在这里插入图片描述

6、合成复用原则

合成复用原则(Composite Reuse Principle,CRP)又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)。它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

6.1 聚合关系与组合关系

(1)聚合关系:是整体与部分的关系,且部分可以离开整体而单独存在,是一种不稳定的包含关系,它们有各自独立的生命周期;例如:公司和员工的关系,公司包含员工,但如果公司倒闭,员工依然可以换公司上班。

代码体现:通常将一个类的对象作为另一个类的成员变量来实现聚合关系

(2)组合关系:也是整体与部分的关系,但部分不能离开整体而单独存在,这种关系比聚合更强,是一种稳定的包含关系。整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。

代码体现:通常将一个类的对象作为另一个类的成员变量来实现组合关系,与聚合相同,只能从语义级别来区分

6.2 继承复用与合成复用对比

(1)继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
(2)限制了系统的灵活性,使类与类之间的耦合度增加,父类的变化可能会影响到所有的子类。
(3)采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
(4)可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少。

6.3 汽车例子

(1)继承方式
在这里插入图片描述
(2)合成复用方式
在这里插入图片描述

三、创建型模式

共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

1、单例模式

1.1 概念

(1)单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
(2)在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。
(3)每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。
(4)总之,选择单例模式就是为了避免不一致状态,避免政出多头。

1.2 单例模式的特点

(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。
(4)单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。

1.3 饿汉式

该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了

1.3.1 饿汉式+静态成员变量

(1)私有化构造方法,使外界无法直接调用该构造方法
(2)在类定义中创建该类对象
(3)提供一个公共访问方式,让外界获取该对象
(4)写测试类调用。
在这里插入图片描述
在这里插入图片描述

1.3.2 饿汉式+静态代码块

(1)私有化构造方法,使外界无法直接调用
(2)声明一个私有静态成员变量,(与静态成员变量的区别是这里只定义,未赋值即构建对象)
(3)在静态代码模块中赋值。
(4)对外提供获取该对象的方法。public static XXX对象(函数的返回类型) getInstance()
在这里插入图片描述

1.4 懒汉式

该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。

1.4.1 懒汉式+线程不安全

(1)私有化构造方法,使外界无法直接调用
(2)声明一个私有静态成员变量,(与静态成员变量的区别是这里只定义,未赋值即构建对象)
(3)对外提供获取该对象的方法。public static XXX对象(函数的返回类型) getInstance(),判断一下该对象实例是否存在,存在直接返回,不存在创建后返回。
在这里插入图片描述

1.4.2 懒汉式+线程安全

与1.4.1的区别在于声明公共静态方法是添加synchronized关键字。
在这里插入图片描述
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。

1.4.3 懒汉式+双重检查锁

在这里插入图片描述
(1)私有化构造方法,使外界无法直接调用
(2)声明一个私有静态成员变量,(与静态成员变量的区别是这里只定义,未赋值即构建对象)
(3)对外提供获取该对象的方法。该方法用public static volatile声明,方法内先判断实例是否==null,不为空则直接返回,为空在加synchronized (Singleton.class){}锁,被锁住的代码块{}中,再判断一下实例是否==null,为空则生成创建实例,不为空略过。
(4)volatile关键字可以解决可见性问题和指令重排序问题,所以instance加上volatile修饰后即可阻止指令重排序。
在这里插入图片描述
在这里插入图片描述

1.4.3.1 双层检测的理论解释

问题1:单例模式在多线程环境下的lazy模式为什么要加两个if(instance == null)?

回答1:第一层 if (instance == null)是为了减少线程对同步锁锁的竞争,第二层if(instance==nul)是保证单例。即
(1)if (instance == null) 的懒汉式多线程下是不安全的;
(2)synchronized/lock + if (instance == null) 的懒汉式多线程下是安全的;
(3)if (instance == null) + synchronized + if (instance == null) 的懒汉式在(2)的情况下减少对同步锁的竞争,提高效率。

问题2:如回答1,既然第二种方式已经看保证线程安全了,为什么需要从第二种方式变为第三种方式?或者为什么要使用双层同步锁?或者第三种方式 if (instance == null) + synchronized + if (instance == null) 的懒汉式是如何减少对同步锁的竞争?

回答2:如果使用第二种方式,即第三种方式没有第一层if (instance == null) ,每次调用newInstance()方法都会先synchronized/lock 然后判断 if (instance == null) ,对于一共 n 次调用newInstance静态方法,对于第二层的 if (instance == null),只有第一次创建在为true,后面都是为false,不需要再次新建了,因为是单例,所以对于后面的 n-1 次调用newInstance,都是先获取到同步锁,然后 if(instance==null)为false,这样白白的消耗性能,后面的 n-1 次,每个线程辛辛苦苦的获取到的同步锁,发现没用,还不如不要获取同步锁,尝试的解决方法有两个:

(1) synchronized/lock + if (instance == null) 变为 if (instance == null) + synchronized/lock,但是这样修改后,第一批进入的线程破坏单例模式。
(2)synchronized/lock + if (instance == null) 变为 if (instance == null) + synchronized/lock + if (instance == null),在保证线程安全的第二种方式前面加一层 if (instance == null)判断,变为双层检测,这样在保证单例的情况下,提高效率,减少性能浪费。

问题3:为什么说刚才的第一种方式 synchronized/lock + if (instance == null) 变为 if (instance == null) + synchronized/lock ,无法保证第一批进入的线程仅创建一个对象?

回答3:虽然 if (instance == null) 实现后面调用阻止进入,提高了效率,同时 synchronized/lock 保证内部新建单例的原子性,但是由于没有内层 if (instance == null) ,第一批进入的每一个线程都会创建一个对象,破坏单例模式 。

1.4.4 懒汉式+内部静态类

在这里插入图片描述

1.4.4.1 实现步骤

(1)私有外部类的构造方法
(2)定义一个私有静态类,该类中定义一个外部类的静态常量,用 private static final修饰。
(3)定义一个公共访问方法,返回内部类的常量对象。
(4)这种写法由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,保证了线程安全问题,没有性能缺陷;

1.4.4.2 代码实现
public class Singleton {  
     
    //私有的构造函数:
    private Singleton (){}  
 
    //静态内部类:
    private static class SingletonHolder {  
        //供外界访问的静态常量:
        private static final Singleton INSTANCE = new Singleton();  
    }  

    //供外界访问获取类对象的方法:
    public static Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
 
}

(1) 这种写法由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,又因为外部类加载时、并不需要立即加载内部类,因此该写法是懒汉式的、懒加载的;

(2)同时访问实例对象的时候,不需要进行同步措施,还能保证线程安全问题,没有性能缺陷。

1.4.4.3 静态内部类的单例模式如何保证线程安全

1)关于类加载过程中的最后一个阶段:即类的初始化
PS:JVM类加载机制的过程:加载、验证、准备、解析、初始化;

 首先要了解JVM的类加载过程中的最后一个阶段:即类的初始化,
 类的初始化阶段的本质就是执行类构造器中的<clinit>方法。

2)什么时候会进行类的初始化?
有以下5种情况:

T 是一个类,而且一个 T 类型的实例被创建;
T 是一个类,且 T 中声明的一个静态方法被调用;
T 中声明的一个静态字段被赋值;
T 中声明的一个静态字段被调用;
T 是一个顶级类(top level class,见 java 语言规范的§7.6),而且一个断言语句嵌套在 T 内部被执行。
  由于加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员
(静态域、构造器、静态方法等)被调用时发生。

所以,我们前面的代码对应了第四种情况,一个静态字段被调用,此时才会进行静态内部类的初始化。

3)什么是<clinit>方法:
刚才我们讲到:类的初始化阶段的本质就是执行类构造器中的方法。

    那这个 <clinit>方法 到底是什么呢?
    <clinit>方法:不是由程序员写的方法,而是根据由JVM中的javac编译器生成的。
    <clinit>方法它是由类里面 所有的静态变量/常量的赋值动作 和 静态代码块 组成的。
    <clinit>() 方法对于类来说不是必须的,如果一个类中既没有 静态语句块、也没有静态变量/常量的赋值动作, 那么编译器不会为该类生成<clinit>()方法。

JVM内部会保证一个类的<clinit>方法在多线程环境下被正确的加锁同步:

    也就是说如果多个线程同时去进行“类的初始化”,那么只有一个线程会去执行类的<clinit>方法,其他的线程都要阻塞等待,直到这个线程执行完<clinit>方法。
    然后执行完<clinit>方法后,其他线程唤醒,但是不会再进入<clinit>()方法。也就是说同一个加载器下,加载一个类的时候,只会执行一次<clinit>方法。
    因此,JVM保证了 一个类的初始化环节在多线程下会被同步加锁。

4)保证线程安全的原理是:利用了类加载机制的初始化阶段的<clinit>方法;
那么回到代码中,静态内部类里边的静态变量/常量的赋值操作,实际上就是一个<clinit>方法中的代码,

    当我们执行getInstance方法的时候,才会导致虚拟机加载SingletonHolder静态内部类,
    然后在加载这个静态内部类的过程中,因为该内部类有静态变量/常量,所以JVM会为该内部类生成<clinit>()方法,然后在初始化环节执行<clinit>()方法——即执行静态变量/常量的赋值动作。

private static final Singleton INSTANCE = new Singleton();
因为虚拟机会保证()方法在多线程环境中被正确地加锁同步,在同一个加载器下,加载一个类的时候,只会执行一次方法——所以只会对该静态内部类里边的 静态变量/常量 赋值一次,

    再加上 INSTANCE 是常量,只会被赋值一次;
    所以,这样便保证了只会创建一次Singleton单例类的实例对象,而且在多线程环境下保证了线程安全。

总结:

    在类加载的初始化的步骤中,JVM会去获取一个用于同步多个线程对同一个类进行初始化的锁,这样就不需要额外的同步。
    这种方式不仅能够保证线程安全,也能保证单例对象的唯一性,同时也延迟实例化,是一种非常推荐的方式。

5)补充:<clinit>方法如何保证多线程环境下被正确的加锁同步?
加载SingleTonHolder类时,是调用ClassLoader的loadClass方法,最终保证方法在多线程环境下被正确的加锁同步的原理,其实还是采用synchronize加锁的方式。

相关源码:

 protected Class<?> loadClassOrNull(String cn, boolean resolve) {
    synchronized (getClassLoadingLock(cn)) {
        // check if already loaded
        Class<?> c = findLoadedClass(cn);

        return c;
     }
}

1.5 枚举模式

在这里插入图片描述
(1)这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
(2)这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
(3)不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
(4)不能通过 reflection attack 来调用私有构造方法。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

枚举方式属于饿汉式方式,详情见单例模式 | 菜鸟教程

1.6 破坏单例模式(枚举模式除外)

1.6.1 序列化与反序列化破坏单例模式

(1)将对象序列化生成文件
在这里插入图片描述
(2)通过反序列化从同一文件生成两个对象,对象地址不一样,即破坏单例模式
在这里插入图片描述
在这里插入图片描述

1.6.2 反射破坏单例模式

在这里插入图片描述
java获取字节码的三种方式

方式一,使用类的class属性:
Class clazz1 = java.util.Date.class;

方式二,通过Class类中的静态方法forName(String className),传入类的全限定名(必须添加完整包名)
Class> clazz2 = Class.forName(“java.util.Date”);

方式三,通过对象的getClass方法来实现,其中,getClass()是Object类中的方法,所有的对象都可以调用该方法
java.util.Date str = new java.util.Date();
Class> clazz3 = str.getClass();
1.6.3 如何避免反序列化破坏单例模式
class Singleton implements Serializable {

    private volatile static Singleton singleton;

    private Singleton() {

    }

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

   //增加一个readResolve函数即可
    public Object readResolve() {
        return getInstance();
    }
}
1.6.4 如何避免反射破坏单例模式

在定义类的私有构造函数时,添加一下代码
在这里插入图片描述

2、工厂模式

(1)工厂模式(Factory Pattern)是Java中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

(2)工厂模式大体分为简单工厂、工厂方法、抽象工厂等三种模式。工厂方法模式也可称为工厂模式,与抽象模式都是属于GOF23种设计模式中的一员。可以大概理解为:简单工厂进阶变成了工厂方法,然后再进阶成了抽象工厂。难度逐步增加,也越来越抽象。

(3)核心本质实例化对象不适用new,用工厂方法代替,将选择实现类,创建对象统一管理和控制。从而使调用者跟我们的实现类解耦

2.1 简单工厂模式

2.1.1 简单工厂模式结构

(1)抽象产品:定义产品规范,描述产品的主要特性和功能。
(2)具体产品:实现或继承抽象产品的子类
(3)具体工厂:提供创建产品的方法,调用者通过该方法来获取产品;简单厂模式的核心,它负责实现创建所有实例的内部逻辑。

2.1.2 简单工厂模式类图

在这里插入图片描述

2.1.3 优缺点

(1)优点

将对象的创建和对象本身业务处理分离可以降低系统的 耦合度,使得两者修改起来都相对容易.

(2)缺点

厂类的职责相对过重,增加新的产品需要修改厂类 的判断逻辑,这一点与开闭原则是相违背
将会增加系统中类的个数,在定程度上增加了系统的 复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不用模式

2.2 静态工厂模式

与简单工厂模式唯一区别是,工厂类中的方法是静态的,调用工程类的方法时不用new 工厂对象,直接用工厂类名.方法调用。

2.3 工厂方法模式

工厂方法模式又称为工厂模式,也叫虚拟构造器模式或者多态工厂模式,它属于类创建型模式。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

2.3.1 工厂方法模式结构

(1) 抽象工厂(Factory)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。

(2)具体工厂(ConcreteCreator)角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。

(3)抽象产品(Product)角色:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。

(4)具体产品(ConcreteProduct)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

2.3.2 工厂方法模式类图

在这里插入图片描述

2.3.3 具体实现过程

(1)定义产品类的抽象类,实现其所有产品的共性方法;私有的方法定义为抽象方法,在具体产品类中重写。
(2)定义具体产品的实现类,继承抽象类及共性方法,重写抽象方法。
(3)定义工厂类接口及其抽象方法。
(4)定义生产具体产品的工厂类,继承工厂类接口,重写抽象方法,引入并返回依赖的具体产品类,就实现该类产品的生产。
(5)使用具体产品时,只需调用生产具体产品的工厂类即可得到想要的产品。
(6)调用原则:一般采取父引用指向子对象

2.3.4 优缺点

(1)优点
用户只需要知道具体工厂的名称即可得到所要的产品,无需知道产品的具体创建过程。
系统增加新的产品时只需增加具体产品类和一个相应的具体工厂(分别实现产品及工厂接口),无需对原工厂进行任何修改,满足开闭原则。

(2)缺点
每增加一个产品就要增加一个具体产品类和对应的具体工厂类,增加了系统的复杂性。

2.4 抽象工厂模式

抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,图 1 所示的是海尔工厂和 TCL 工厂所生产的电视机与空调对应的关系图。
在这里插入图片描述

2.4.1 抽象工厂模式结构

(1)抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。

(2)具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。

(3)抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。

(4)具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

2.4.2 抽象工厂模式类图

在这里插入图片描述

2.4.3 以甜品店为例,抽象工厂模式实现步骤

(1)定义两个同一层次商品的抽象类,分别为Coffee和Dessert;
(2)分别定义属于Coffee的两个实体类AmericanCoffee和LatteCoffee,属于Dessert的两个实体类抹茶慕斯和提拉米苏。
(3)定义一个工厂类接口,有两个抽象方法,createCoffee()和createDessert();
(4)分别定义两个工厂实现工厂接口的意大利风味工厂和美式风味工厂。
(5)定义一个测试类,分别调用意大利风味工厂或美式风味工厂,就可以得到该工厂生产的对应产品。

2.4.4 优缺点

(1)优点
当一个产品族中的多个对象被设计成一起工作时,他能保证客户端始终只使用同一个产品族中的产品

(2)缺点
当产品族需要增加一个新产品时,所有的工厂类都需要进行修改。

(3)适应场景
在这里插入图片描述

2.5 工厂模式扩展

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

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

2.5.2 实现步骤

(1)定义配置文件
为了演示方便,使用properties文件作为配置文件,名称为bean.properties
在这里插入图片描述
(2)改进工厂

public class CoffeeFactory{
	//加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
	//1、定义容器对象并存储咖啡对象
	private static HashMap<String,Coffee> map =new HashMap<String,Coffee>();
	//2、加载配置文件,只需加载一次,用静态代码块
	static {
		//2.1  创建Properties对象
		Properties p =new Properties();
		//2.2 调用P对象中的Load方法进行配置文件的加载
		InputStream is = CoffeeFactory.class.getClassLoder().getResourceAsStream("bean.properties");
	try {
		p.load(is);
		//从p集合中获取全类名并创建对象
		Set<Object> keys=p.keySet();
		for (Object key:keys){
			String className=p.getPoperty((String) key);
			//通过反射技术创建对象
			Class clazz=Class.forName(className);
			Coffee coffee=(Coffee) clazz.newInstance();
			//将名称和对象存储到容器中
			map.put((String) key,coffee);
		}
    }	catch (Exception e){
    	e.printStackTree();
    }
  }
  //根据名称获得对象
  public static Cofffee createCoffee(String name) {
    //HaspMap中通过key得到vaule,key是传入的字符串,value是对应的对象。
  	return map.get(name);
  }	
}

(3)编写测试类

public class client{
	public static void main (String[] args) {
		//调用Coffee工厂,并传入具体coffee名即可
		Coffee coffee=CoffeeFactory.createCoffee("latte");
		System.out.println(coffee.getName());
	}
}

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

2.6 JDK中用到的工厂模式

2.6.1 Collection.iterator分析

(1)Collection.iterator方法,代码例子
在这里插入图片描述
(2)分析:单列集合获取迭代器的方法就使用到工厂方法,类图如下
在这里插入图片描述
(3)Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Itr内部类是具体的商品类,具体工厂类的iterator()方法创建具体商品类的对象。

2.6.2 其他工厂类

(1)DataFormat类中的getInstance()方法使用的是工厂模式;
(2)Calendar类中的getInstance()方法使用的是工厂模式。

3、原型模式

3.1 定义

原型模式(Prototype Pattern)是一种创建型模式,他采取克隆原型对象的方法来创建对象的实例,所以也可称作克隆模式。使用原型模式创建的实例,具有与原型一样的数据。

3.2 原型模式结构

(1)抽象原型类:规定具体原型对象必须实现的clone()方法;
(2)具体原型类:实现抽象原型类中的clone()方法,它是可被复制的对象;
(3)访问类:使用原型类中的clone ()方法来复制新的对象。

3.3 原型模型的类图

在这里插入图片描述

3.4 浅克隆和深克隆

浅克隆:只复制指向某个对象的指针,而不是复制对象的本身,新旧对象还是共享同一块内存,修改一个对象的引用数据,会同时改变另一个对象的引用数据

深克隆:会另外创建一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

3.4.1 浅克隆

(1)所有的Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份在Java中可以直接使用Object提供的clone()方法来实现对象的克隆(浅克隆)能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持复制如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常

(2)浅克隆示意图
在这里插入图片描述
(3)浅克隆的特点:克隆的是基本数据类型和String类型的数据,其他类型的数据都是直接克隆引用

(4)浅克隆应用场景:

克隆的对象中只含有基本数据类型或String类型
克隆出来的对象,并不会修改除String以外的引用数据
3.4.2 深克隆

(1)深克隆两种实现方式的异同
方式一(使用浅克隆实现深克隆)实现起来很简单,但是面对类中数据比较多时,或者是类中引用数据又存在引用数据时,就会让实现变得很复杂,比较适合用在一些简单的场所
在这里插入图片描述

Step1:将原型类和存储在原型的引用数据对应的类都实现Cloneable接口
Step2:在克隆方法中,不仅克隆student1,还对student1中的Teacher对象进行克隆

方式二(实用序列化、反序列化实现深克隆)实现起来不算复杂,且适用范围很全面,推荐使用
在这里插入图片描述

Step1:将原型类 和 原型类中引用数据类型对应的类 都实现Cloneable和Serializable两个接口
Step2:在原型类中编写一个深刻隆的方法,利用序列化、反序列化克隆出一个新的对象

(2)深克隆内存示意图
在这里插入图片描述
(3)深克隆和浅克隆的区别和联系

1)使用同样的接口。浅克隆和深克隆都需要原型类实现Cloneable接口,不同的是使用深克隆,不仅要让原型类实现Cloneable接口,还要让原型类中引用数据类型对应的类也实现Cloneable接口(如果深克隆是使用方式二,还要同时实现Serializable接口)

2)克隆方式不同。浅克隆只会克隆String类型和基本数据类型的数据,对于其他引用数据类型克隆的是引用,修改克隆得到的对象中的引用数据类型(除String以外的引用数据类型,比如数组、集合、类对象)会同时修改原型类中的引用数据类型;

3)深克隆克隆的是对象的所有数据,克隆出来对象和原型对象除了一开始数据是一样的以外,没有任何关系,两者互不影响

4)性能不同。浅克隆共用引用,不会重新开辟堆内存,能够节约一定的内存;而深克隆会重新开辟堆内存,用于存放克隆的引用数据类型。所以浅克隆的性能要优于深克隆

3.5 原型模式的优缺点

(1)优点:
节约内存。所有实例共享同一个方法,不会创建多个,占用系统资源较少
提高程序的性能。对象的创建是通过克隆出来的,不需要额外消耗时间去进行初始化

(2)缺点:实现较为复杂,需要细心,不然很容易出现bug

3.6 原型模式的应用场景

(1)类初始化消耗资源过多 : 如果类初始化时消耗过多的资源 , 如这个类中某个成员占用大量内存 , 为了节省开销

(2)初始化繁琐耗时 : 类对象创建时经过大量的计算 , 或与本地资源 ( 数据库 , 文件 ) 频繁交互 , 每次创建消耗大量的 CPU 与 时间资源

(3)构造函数复杂 : 类中定义的构造函数复杂

(4)实例对象数量庞大 : 如果在内存中循环创建了很多该实例对象 , 就可以使用原型模式复用不用的对象 , 用于创建新对象

(5)一个对象需要提供给其它对象访问,而且各个调用者可能都需要修改其值时,推荐使用浅克隆
在创建对象的时候,我们不只是希望被创建的对象继承其基类的基本结构,还希望继承原型对象的数据时,推荐使用深克隆
……

(6)在Spring中,原型模式应用得非常广泛。例如 scope = “prototype”,在我们经常用的JSON中的parseObject()方法也是一种原型模式。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者

4、建造者模式

在开发中,有时候我们需要创建出一个很复杂的对象,这个对象的创建有一个固定的步骤,并且每个步骤中会涉及到多个组件对象,这个时候就可以考虑使用建造者模式。

使用建造者模式将原本复杂的对象创建过程按照规律将其分解成多个小步骤,这样在构建对象时可以灵活的选择或修改步骤。

建造者模式将对象的创建和表示过程进行分离,这样我们可以使用同样的过程,只需修改这个过程中的小步骤,便能够构建出不同的对象。

而对于调用方来说,我们只需要传入需要构建的类型,便能够得到需要的对象,并不需要关系创建的过程,从而实现解耦。

4.1 建造者模式结构

(1) builder:为创建一个产品对象的各个部件指定抽象接口。
(2)ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
(3)Director:构造一个使用Builder接口的对象。
(4)Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

4.2 建造者模式类图

在这里插入图片描述

4.3 具体代码实现

(1)四大角色代码

// 产品类,定义产品的三个部分
public class Product {
    private Object part1;
    private Object part2;
    private Object part3;

    public void setPart1(Object part1) {
        this.part1 = part1;
    }

    public void setPart2(Object part2) {
        this.part2 = part2;
    }

    public void setPart3(Object part3) {
        this.part3 = part3;
    }

    @Override
    public String toString() {
        return "Product{" +
                "part1=" + part1 +
                ", part2=" + part2 +
                ", part3=" + part3 +
                '}';
    }
}

// 抽象建造者类,构建了一个产品对象,并定义了构建产品三个部分所需要的三个方法以及获取产品的方法
public abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPart1();
    public abstract void buildPart2();
    public abstract void buildPart3();
    public abstract Product getProduct();
}

// 具体建造者 1
public class ConcreteBuilder1 extends Builder{
    @Override
    public void buildPart1() {
        product.setPart1("builder 1 set part 1.");
    }

    @Override
    public void buildPart2() {
        product.setPart2("builder 1 set part 2.");
    }

    @Override
    public void buildPart3() {
        product.setPart3("builder 1 set part 3.");
    }

    @Override
    public Product getProduct() {
        System.out.println("builder 1 build product.");
        return product;
    }
}

// 具体建造者 2
public class ConcreteBuilder2 extends Builder {
    @Override
    public void buildPart1() {
        product.setPart1("builder 2 set part 1.");
    }

    @Override
    public void buildPart2() {
        product.setPart2("builder 2 set part 2.");
    }

    @Override
    public void buildPart3() {
        product.setPart3("builder 2 set part 3.");
    }

    @Override
    public Product getProduct() {
        System.out.println("builder 2 build product.");
        return product;
    }
}

// 指挥者对象
public class Director {
    private Builder builder;
	//创建指挥者,带参数的构造函数
    public Director(Builder builder) {
        this.builder = builder;
    }
	//生产产品过程
    public Product construct() {
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
        Product product = builder.getProduct();
        return product;
    }
}

(2)调用方代码

public static void main(String[] args) {
    Director director1 = new Director(new ConcreteBuilder1());
    Product product1 = director1.construct();
    System.out.println(product1);
    System.out.println("==================================");

    Director director2 = new Director(new ConcreteBuilder2());
    Product product2 = director2.construct();
    System.out.println(product2);
}

(3)注意
上面例子实Builder模式的常规用法,指挥者类Director在建造者模式中具有很重要作用,它用于指导具体构建者如何构建产品,控制调用先后顺序,并向调用者返回完整的产品类,但有些情况下可以把指挥者和抽象建造者进行结合。

// 抽象建造者类,合并指挥者
public abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPart1();
    public abstract void buildPart2();
    public abstract void buildPart3();
    public abstract Product getProduct();
    //一下是指挥者的代码,控制产品生产过程,并返回完整的产品
    public Product construct() {
        this.buildPart1();
        this.buildPart2();
        this.buildPart3();
        Product product = this.getProduct();
        return product ;
    }
}

说明:这样做确实简化了系统结构,但同时也加重了抽象者类的职责,也不是符合单一职责原则,如果construct()过于复杂,还是建议封装到Director中。

4.4 优缺点

(1)优点:
建造者模式封装性好。使用建造者模式可以有效的封装变化,在使用建造者模式场景中,一般产品类和建造者类比较稳定,因此,将主要的业务逻辑封装到指挥者类中对整体而言可以取得比较好的整体性。

建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

可以更加精细地控制产品的创建过程。将复杂产品的创建过程分解到不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

建造者模式很容易进行扩展,如果有新的需求,通过实现一个新的建造者类就可以完成,基本不用修改之前测试通过的代码,符合开闭原则。

(2)缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间差异很大,则不适合用该模式。

4.5 使用场景

建造者模式创建的是复杂对象,其产品的各个部分经常面临剧烈变化,但将它们组合在一起的算法却相对稳定,所以在以下场合使用。

创建复杂对象,由多个部件构成,各部件面临复杂的变化,但构件建的建造顺序是稳定的。
创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

4.6 扩展模式

当一个类的构造器需要传入很多参数时,如果通过有参构造器创建这个类的实例,代码的可读性非常差,而且容易引入错误,此时就可以利用建造者模式进行重构。

将建造者定义为一个静态内部类,并作为phone的参数传给它私有构造函数
在这里插入图片描述
在这里插入图片描述
改造私有构造函数
在这里插入图片描述
外部调用测试类
在这里插入图片描述

4.7 建造者模式与工厂模式对比

工厂方法模式和建造者模式都属于对象创建类模式,都用来创建类的对象。但它们之间的区别还是比较明显的。

4.7.1意图不同

(1)在工厂方法模式里,我们关注的是一个产品整体,如超人整体,无须关心产品的各部分是如何创建出来的;
(2)但在建造者模式中,一个具体产品的产生是依赖各个部件的产生以及装配顺序,它关注的是“由零件一步一步地组装出产品对象”。
(3)简单地说,工厂模式是一个对象创建的粗线条应用,建造者模式则是通过细线条勾勒出一个复杂对象,关注的是产品组成部分的创建过程。

4.7.2 产品的复杂度不同

(1)工厂方法模式创建的产品一般都是单一性质产品,如成年超人,都是一个模样,而建造者模式创建的则是一个复合产品,它由各个部件复合而成,部件不同产品对象当然不同。

(2)这不是说工厂方法模式创建的对象简单,而是指它们的粒度大小不同。一般来说,工厂方法模式的对象粒度比较粗,建造者模式的产品对象粒度比较细。

(3)两者的区别有了,那在具体的应用中,我们该如何选择呢?是用工厂方法模式来创建对象,还是用建造者模式来创建对象,这完全取决于我们在做系统设计时的意图,如果需要详细关注一个产品部件的生产、安装步骤,则选择建造者,否则选择工厂方法模式。

四、结构型模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

1、代理模式

1.1 概述

由于某些原因需要给某对象提供一个代理用于控制对该对象的访问。这时访问对象不适合或不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理生成时机不同分为静态代理和动态代理。静态代理在编译期就生成,而动态代理则在Java运行时动态生成。动态代理又有JDK代理和CGLIB代理

1.2 结构

代理(prioxy)模式分为三种角色
(1)抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
(2)真实主题(Real Subject)类:实现抽象主题中的具体业务,是代理对象所代表的真实对象,是最终引用的对象。
(3)代理(proxy)类:提供与真实主题系统的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

1.3 静态代理模式

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

1.3.1 静态代理模式类图(以卖火车票为例)

在这里插入图片描述

1.3.2 代码实现

(1)抽象主题类:
在这里插入图片描述
(2)真实主题类
在这里插入图片描述
(3)代理类
在这里插入图片描述
(4)外部访问类
在这里插入图片描述
从上面的代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介,同时也对sell方法进行了增加。

1.3.3 静态代理模式特点

(1)静态代理必须有三个要素
需要有真实角色、代理角色和这两个角色之间必须实现相同的接口。

(2)优点:真实角色也就是业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

(3)缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度

1.4 jdk动态代理模式

1.4.1 jdk动态代理原理:

(1)反射, Method类,表示方法。类中的方法。通过Method可以执行某个方法

(2)jdk动态代理的实现
反射包java.lang. reflect,里面有三个类:InvocationHandler,Method,Proxy

1.4.2 JDK动态代理实现过程

(1)创建被代理的接口和类;
(2)创建InvocationHandler接口的实现类,在invoke方法中实现代理逻辑;
(3)通过Proxy的静态方法newProxyInstance( ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理对象
(4)使用代理对象。

1.4.2.1 编写被代理的接口类
package com.lwp;

//卖票接口
public interface SellTickets {
    void sell();
}
1.4.2.2 编写被代理的实现类
package com.lwp;

public class TrainStation implements  SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}
1.4.2.3 InvocationHandler接口的实现类
package com.lwp;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LwpInvocationHandler implements InvocationHandler {
    //被代理对象,Object类型,可以接收任何代理类,向上转型
    private Object target;
    //y有参构造,传入被代理对象
    public LwpInvocationHandler(Object target) {
        this.target = target;
    }
    //重写invoke函数,在调用被代理对象的方法执行前后可添加新的业务逻辑
    /**
     * Object proxy:代理对象,这里就是proxyObject,本方法中不用
     * Method method:对接口中的方法进行封装的Method对象,代理对象中执行的方法 
    * Object[] args:调用方法的实际参数
    *
    * 返回值,就是方法的返回值
   */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用被代理对象方法执行前的业务处理逻辑");
        //利用method的反射原理,调用被对象实现类的方法,并按该方法的返回值类型,返回指定类型数据。
        //参数target是实际被代理对象
        Object returnvalue = method.invoke(target, args);
        System.out.println("调用被代理对象方法执行后的业务处理逻辑");
        return returnvalue;
    }
}
1.4.2.4 动态代理测试类
package com.lwp;
import java.lang.reflect.Proxy;

public class DynamicProxyTest {
    public static void main(String[] args) {
        //传入被代理类的实现类,多态,父类引用指向子类对象
        SellTickets target = new TrainStation();
        LwpInvocationHandler handler = new LwpInvocationHandler(target);
        //第一个参数是指定代理类的类加载器(我们传入被代理类的类加载器)
        //第二个参数是代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口)
        //第三个参数是invocation handler,用来处理方法的调用。这里传入我们自己实现的handler
        SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), handler);
        proxyObject.sell();
    }
}
1.4.2.5 执行过程

(1)当执行proxyObject.sell()时候调用的是哪里呢?是执行我们的proxyObject在创建时,指定的这个handler他里面的invoke()方法。(handler里面的invoke()方法的形参)此时sell()会赋给method,如果有参数则参数会赋给args

(2)然后就到了invoke()方法里面,之后到了method.invoke(target,args),这里所执行的是目标类的方法,谁把target传给了invoke就执行谁的方法。这里我们传的是目标类,因此他会去调用目标类中的方法,目标类方法执行完成之后会有一个返回值,返回给调用目标类的方法

(3)然后hadler里面的invoke方法结束之后,会返回一个值给客户端的proxyObject.sell()方法

1.5 CGLIB动态代理

本来以为CGLib动态代理和JDK实现的方式差不多的,但是仔细了解一下之后还是有很大的差异的,这里我们先简单说一下这两种代理方式最大的区别。

JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了。

CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法。

CGLIB(Code Generation Library)是一个开源、高性能、高质量的Code生成类库(代码生成包)。

1.5.1 CGLIB基本结构

在这里插入图片描述

1.5.2 代码实现过程

(1)导入依赖包

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>

(2)目标类

package cglib;

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

(3)代理对象工厂,利用Enhancer的create方法,之前需指定父类和设置回调函数

package cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor {
    //声明火车站对象
    private  TrainStation station=new TrainStation();
    public TrainStation getProxyObject(){
        //创建Enhancer对象,类似于JDK中的Proxy类
        Enhancer enhancer = new Enhancer();
        //设置父类的字节,指定父类
        enhancer.setSuperclass(TrainStation.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation proxyObject=(TrainStation) enhancer.create();
        return proxyObject;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //System.out.println("方法执行了");
        System.out.println("代售点收取一定服务费(CGLIB代理)");
        //要调用目标对象的方法
        Object obj = method.invoke(station, objects);
        return obj;
    }
}

(4)外部访问类或测试类

package cglib;

public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory proxyFactory=new ProxyFactory();
        //创建代理对象
        TrainStation proxyObject = proxyFactory.getProxyObject();
        //调用代理对象中的sellff
        proxyObject.sell();
    }
}

1.6 使用场景

(1)优缺点
在这里插入图片描述
(2)使用场景
在这里插入图片描述

2、适配器

适配器模式(Adapter)将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式分为类适配器模式和对象适配器模式两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

2.1 模式的结构

2.1.1 模式结构

(1)目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口,用于适配器类来实现。

(2)适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口,用于适配器来继承或聚合。

(3)适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

2.1.2 类适配器与对象适配器

类适配器模式采用多重继承对一个接口与另外一个接口进行匹配,Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

2.1.3 区别

类适配器使用的是继承的方式,直接继承了Adaptee,所以无法对Adaptee的子类进行适配。

对象适配器使用的是组合的方式,·所以Adaptee及其子孙类都可以被适配。另外,对象适配器对于增加一些新行为非常方便,而且新增加的行为同时适用于所有的源。

基于组合/聚合优于继承的原则,使用对象适配器是更好的选择。但具体问题应该具体分析,某些情况可能使用类适配器会适合,最适合的才是最好的。

2.2 实现

2.2.1 类适配器

(1)类适配器类图
在这里插入图片描述
(2)代码实现

/**
 * 这是客户所期待的接口,目标可以是具体的或抽象类,也可以是接口
 */
public interface Target {
    void request();
}

/**
 * 需要适配的类,被访问和适配的现存组件库中的组件接口
 */
public class Adaptee {
    public void specificRequest(){
        System.out.println("适配者中的业务代码被调用!");
    }
}

/**
 * 类适配器类
 */
public class ClassAdapter extends Adaptee implements Target{
    @Override
    public void request() {
        specificRequest();
    }
}

//客户端代码
public class ClassAdapterTest
{
    public static void main(String[] args)
    {
        System.out.println("类适配器模式测试:");
        Target target = new ClassAdapter();
        target.request();
    }
}
2.2.2 对象适配器

(1)类图
在这里插入图片描述
(2)代码实现

/**
 * 对象适配器,通过在内部包装一个 Adaptee 对象,把源接口转换为目标接口
 */
public class ObjectAdapter implements Target {

    // 建立一个私有的 Adaptee 对象
    private Adaptee adaptee;
	//定义一个私有成员类 adaptee,它的赋值同适配器的有参构造函数进行赋值
    public ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void request() {
        // 把表面上调用 request() 方法变成实际调用 specificRequest() 
        adaptee.specificRequest();
    }
}

//客户端代码
public class ObjectAdapterTest
{
    public static void main(String[] args)
    {
        System.out.println("对象适配器模式测试:");
        // 对客户端来说,调用的就是 Target 的 request()
        Target target = new ObjectAdapter();
        target.request();
    }
}

2.3 优缺点

2.3.1 优点:

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

2.3.2 缺点:

(1)适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
(2)增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

2.4 使用场

在这里插入图片描述

2.5 JDK源码中用到的适配器模式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、装饰者模式

装饰者模式又名包装(Wrapper)模式。装饰者模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

装饰者模式动态地将责任附加到对象身上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

3.1 装饰模式中的角色

(1)抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。

(2)具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。

(3)装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。

(4)具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。

3.2 以快餐店为例的类图

在这里插入图片描述

3.3 代码实现

(1) 抽象构件角色

package com.lwp.decorator;
//抽象构建角色,快餐店类
public abstract class FastFood {
    private  float price; //价格
    private String msg; //描述

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

    public FastFood() {
    }

    public float getPrice() {
        return price;
    }

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

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
    public  abstract  float cost();
}

(2)具体构件角色

package com.lwp.decorator;
//具体构建角色,炒饭
public class FireRice extends FastFood {
    public FireRice(){
        super(10,"炒饭");
    }
    @Override
    public float cost() {
        return getPrice();
    }
}

package com.lwp.decorator;
具体构建角色,炒面
public class FriedNoodles extends FastFood {
    public FriedNoodles(){
        super(12,"炒面");
    }
    @Override
    public float cost() {
        return getPrice();
    }
}

(3):抽象装饰者角色。既要继承抽象构件者,又要聚合抽象构建者,同时把聚合的对象作为参数传递给该抽象装饰者的有参构造函数中。

package com.lwp.decorator;
//抽象装饰角色
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 msg) {
        super(price, msg);
        this.fastFood = fastFood;
    }
}

(4)具体装饰者角色

package com.lwp.decorator;
//鸡蛋,具体装饰角色
public class Egg extends Garnish{
    public Egg(FastFood fastFood){
        super(fastFood,1,"鸡蛋");
    }
    @Override
    public float cost() {
        return getPrice()+ getFastFood().cost();
    }
    @Override
    public String getMsg() {
        return super.getMsg()+getFastFood().getMsg();
    }
}

package com.lwp.decorator;
//培根,具体装饰角色
public class Bacon extends Garnish{
    public Bacon(FastFood fastFood){
        super(fastFood,2,"培根");
    }
    @Override
    public float cost() {
        return getPrice()+ getFastFood().cost();
    }
    @Override
    public String getMsg() {
        return super.getMsg()+getFastFood().getMsg();
    }
}

(4)访问测试类

package com.lwp.decorator;

public class Client {
    public static void main(String[] args) {
        //点一份炒饭,利用多态,food引用可以指向FastHood的任何子类
        //包含抽象装饰者类Garnish。 
        FastFood food =new FireRice();
        System.out.println(food.getMsg()+" " +food.cost()+"元");

        System.out.println("============");
        //在上面的炒饭上加一个鸡蛋,通过将FastFood的具体实现子类作为参数传递给
        //Egg的有参构造,得到一个增强的FastFood子类(Egg本身也是FastHood的
        //子类),下一次仍可作为一个FastFood增强的子类传递给具体装饰者类,
        food = new Egg(food);
        System.out.println(food.getMsg()+" " +food.cost()+"元");
        
        System.out.println("============");
        //再加一个鸡蛋
        food = new Egg(food);
        food = new Bacon(food);
        System.out.println(food.getMsg()+" " +food.cost()+"元");
    }
}

(5)说明:递归构造,类似炒饭或炒面增加鸡蛋或培根的装饰者,需要把增强后的构件者类作为装饰者类的有参构造的参数,这样就可以不停地增加鸡蛋或培根。
在这里插入图片描述

3.4 装饰者模式的简化

(1)如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类。如下图所示:
在这里插入图片描述
(2)如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做。如下图所示:
在这里插入图片描述

3.5 装饰者模式的优点和使用场景

3.5.1 好处:

(1)装饰者模式可以带来比继承更加灵活的扩展功能,使用更加方便,可通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。
(2)装饰者模式完美遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
(3)装饰者和被装饰者可以独立负责,不会互相耦合。

3.5.2 使用场景

在这里插入图片描述

3.6 JDK源码

由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰者模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰者模式是Java I/O库的基本模式。
在这里插入图片描述

根据上图可以看出:

●  抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

●  具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、
StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

●  抽象装饰(Decorator)角色:由FilterInputStream扮演。
它实现了InputStream所规定的接口。

●  具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、
DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。

3.7 装饰者模式与代理

(1)装饰者模式:
就是为了给装饰者的方法增强,单纯的是为了在装饰者方法上增加其他功能。是继承的一种替代方案,可以理解为 在不改变接口的前提下,动态扩展对象的功能

(2)动态代理:
给一个对象提供一个代理对象,并有代理对象来控制对原有对象的引用;被代理的对象难以直接获得或者是不想暴露给客户端

(3)静态代理
在这里插入图片描述

4、桥接模式

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

4.1 桥接模式角色

(1)Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。

(2)RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。

(3)Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。

(4)ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。

4.2 以播放器为例桥接模式类图

在这里插入图片描述

4.3 代码实现

(1)实现类的接口

package com.lwp.bridge;

//实现化抽象接口
public interface VideoFile {
    //解码功能
    void decode(String fineName);
}

(2)具体实现类

package com.lwp.bridge;
//具体实现类
public class AviFile implements  VideoFile{
    @Override
    public void decode(String fineName) {
        System.out.println("AVI视频文件:"+fineName);
    }
}

package com.lwp.bridge;
//具体实现类
public class RmvbFile implements VideoFile{
    @Override
    public void decode(String fineName) {
        System.out.println("Rmvb视频文件:"+ fineName);
    }
}

(3)抽象类角色

package com.lwp.bridge;
//抽象的操作系统,抽象类角色
public abstract class OpratingSystem {
    protected VideoFile videoFile;
    //定义有参构造,并将实现类的接口作为参数传递
    public OpratingSystem(VideoFile videoFile) {
        this.videoFile = videoFile;
    }
    public  abstract  void  play(String fileName);
}

(4)扩展的抽象类

package com.lwp.bridge;
//扩展抽象类
public class Window  extends  OpratingSystem{
    //父类只要有参构造,该子类必须有匹配的有参构造
    public Window(VideoFile videoFile) {
        //调用父类的构造函数,传入具体的实现类
        super(videoFile);
    }

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

package com.lwp.bridge;
//扩展抽象类
public class Mac extends OpratingSystem{
    public Mac(VideoFile videoFile) {
        super(videoFile);
    }

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

(5)定义访问类或测试类

package com.lwp.bridge;

public class Client {
    public static void main(String[] args) {
        //多态,父指针指向子对象,new AviFile()也是将具体的VideoFile子实现类传递进来
        OpratingSystem opratingSystem=new Window(new AviFile());
        opratingSystem.play("战狼3");
    }
}

4.4 桥接模式的优缺点

(1)优点:
抽象与实现分离,扩展能力强
符合开闭原则
符合合成复用原则
其实现细节对客户透明

(2)缺点:
由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度

(3)桥接模式应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.

5、门面模式(外观模式)

外观模式(Facade),他隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个统一的访问接口,这个接口使得子系统更容易被访问或者使用。

5.1 门面模式的角色

(1) 门面(Facade)角色 :客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。

(2)子系统(SubSystem)角色 :可以同时有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合(如上面的子系统就是由ModuleA、ModuleB、ModuleC三个类组合而成)。每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。

5.2 门面模式类图

在这里插入图片描述

5.3 优缺点

5.3.1 优点

(1)减少系统的相互依赖

想想看,如果我们不使用门面模式,外界访问直接深入到子系统内部,相互之间是一种强耦合关系,你死我就死,你活我才能活,这样的强依赖是系统设计所不能接受的,门面模式的出现就很好地解决了该问题,所有的依赖都是对门面对象的依赖,与子系统无关。

(2) 提高了灵活性

依赖减少了,灵活性自然提高了。不管子系统内部如何变化,只要不影响到门面对象,任你自由活动。

(3)提高安全性

想让你访问子系统的哪些业务就开通哪些逻辑,不在门面上开通的方法,你休想访问到。

5.3.2 缺点

最大的缺点就是不符合开闭原则,对修改关闭,对扩展开放,看看我们那个门对象吧,它可是重中之重,一旦在系统投产后发现有一个小错误,你怎么解决?完全遵从开闭原则,根本没办法解决。继承?覆写?都顶不上用,唯一能做的一件事就是修改门面角色的代码,这个风险相当大,这就需要大家在设计的时候慎之又慎,多思考几遍才会有好收获。

5.3.3 使用场景

(1)为一个复杂的模块或子系统提供一个供外界访问的接口

(2) 子系统相对独立——外界对子系统的访问只要黑箱操作即可

比如利息的计算问题,没有深厚的业务知识和扎实的技术水平是不可能开发出该子系统的,但是对于使用该系统的开发人员来说,他需要做的就是输入金额以及存期,其他的都不用关心,返回的结果就是利息,这时候,门面模式是非使用不可了。

(3)预防低水平人员带来的风险扩散

比如一个低水平的技术人员参与项目开发,为降低个人代码质量对整体项目的影响风险,一般的做法是“画地为牢”,只能在指定的子系统中开发,然后再提供门面接口进行访问操作。

5.4 源码中用到的门面模式

在这里插入图片描述
在这里插入图片描述

6、组合模式

又名部分整体模式,是用于把一组类似的对象当作单一的对象。组合模式依据树形结构来组合对象,用来表示部分和整体层次,这种类型的设计属于结构型模式,它创建了对象组的树形结构

6.1 组合模式角色

(1)抽象根节点(Component):定义系统各层级对象的共有方法和属性,可以预先定义一些默认行为和属性。

(2)树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。

(3)叶子结点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

6.2 软件菜单案例

6.2.1 功能需求

在这里插入图片描述

6.2.2 类图

在这里插入图片描述

6.2.3 代码实现

(1)菜单组件,抽象根节点

package com.lwp.combination;
//菜单组件,抽象根节点
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 index){
        throw new UnsupportedOperationException();
    }
    //获取菜单或者菜单项的名称
    public String getName(){
        return name;
    }
    //打印菜单名称的方法(包含子菜单和子菜单项)
    public abstract void print();
}

(2)菜单项,树枝节点

package com.lwp.combination;

import java.util.ArrayList;
import java.util.List;
//菜单项,树枝节点
public class Menu extends MenuComponent{
    //菜单可以拥有多个子菜单或子菜单项
    private List<MenuComponent> menuComponentList =new ArrayList<MenuComponent>();
    //构造方法
    public Menu(String name,int level){
        this.name=name;
        this.level=level;
    }
    @Override
    public void add(MenuComponent menuComponent) {
        menuComponentList.add(menuComponent);
    }

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

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

    @Override
    public void print() {
        for (int i=0;i<level;i++){
            System.out.print("--");
        }
        //打印菜单名称
        System.out.println(name);
        //打印子菜单或子菜单项名称
        for (MenuComponent menuComponent : menuComponentList) {
            menuComponent.print();
        }
    }
}

(3)菜单子项,叶子节点

package com.lwp.combination;
//菜单项类,属于叶子节点
public class MenuItem extends MenuComponent {
    //有参构造
    public MenuItem(String name,int level){
        this.name=name;
        this.level=level;
    }
    @Override
    public void print() {
        for (int i=0;i<level;i++){
            System.out.print("--");
        }
        //打印菜单项的名称
        System.out.println(name);
    }
}

(4)外部访问类或测试类

package com.lwp.combination;

public class Client {
    public static void main(String[] args) {
        //创建菜单树
        MenuComponent menu1 = new Menu("菜单管理",2);
        menu1.add(new MenuItem("页面访问",3));
        menu1.add(new MenuItem("展开菜单",3));
        menu1.add(new MenuItem("编辑菜单",3));
        menu1.add(new MenuItem("删除菜单",3));
        menu1.add(new MenuItem("新增菜单",3));

        MenuComponent menu2= new Menu("权限配置",2);
        menu2.add(new MenuItem("页面访问",3));
        menu2.add(new MenuItem("提交缓存",3));

        MenuComponent menu3 = new Menu("角色管理",2);
        menu3.add(new MenuItem("页面访问",3));
        menu3.add(new MenuItem("新增角色",3));
        menu3.add(new MenuItem("修改角色",3));

        //创建一集菜单
        MenuComponent component = new Menu("系统管理",1);
        //将二级菜单添加到一级菜单中
        component.add(menu1);
        component.add(menu2);
        component.add(menu3);

        //打印菜单名称,包含子菜单和子菜单项
        component.print();
    }
}

6.3 组合模式的分类

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

在这里插入图片描述
(2)安全模式
在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
在这里插入图片描述

在这里插入图片描述

6.4 组合模式的优缺点

6.4.1 优点

(1)可以清楚地定义分层次的复杂类型,表示对象的全部层次或者部分层次 ,它让客户端忽略了层次的差异,方便对整个层次经行控制。

(2)客户端可以一致的使用一个组合模式或对单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端的代码。

(3)在组合模式种增加新的容器构件和叶子构件都很方便,无需对现有类库进行任何修改,符合开闭原则。

(4)为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合可以形成复杂的树形机构,但对树形结构的控制却很简单。

6.4.2 缺点

在增加新的构件时就比较难咯。而且难以限定,有时候希望在一个容器种能有某些特定的对象,例如在某个文件夹只能有image或者gif等。这个就比较难以实现。

6.4.3 使用场景,树形结构

(1)在具有整体和部分的层次结构种希望通过一种忽略整体与个体之间差异的,客户端一致对待的情况。

(2)在一个使用面向对象语言开发的系统中需要处理一个树形结构的。

(3)在一个系统中能分离出叶子和容器的,而且他们的类型还固定不变,需要增加一些新的类型。

7、享元模式

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

运用共享技术有效地支持大量细粒度的对象。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

7.1 享元模式结构

7.1.1 享元模式状态

内部状态 :指对象共享出来的部分,本身不会改变。
外部状态:指对象中会随环境改变的变量,他们不存在享元对象中,而是通过参数传入。

7.1.2 享元模式角色

(1)抽象享元(Flyweight)- 定义子享元类的公有方法,且方法本身依赖于外部状态,通过参数进行数据交互。

(2)具体享元(Concrete Flyweight)- 实现具体方法,并将内部状态初始化。

(3)享元工厂(Flyweight Factory)- 负责创建和管理享元对象,本身可作为单例。

(4)外部状态(External State)- 享元对象需要的数据,但其本身会变化,将它以外部参数的形式传入,从而完成方法。
在这里插入图片描述

7.2 享元模式案例

7.2.1 俄罗斯方块

在这里插入图片描述

7.2.2 类图

在这里插入图片描述

7.2.3 代码实现

(1)抽象享元角色

package com.lwp.flyweight;
//抽象享元角色
public abstract class AbstractBox {
    //获取图形的方法
    public abstract  String getShape();
    //显示图形及颜色
    public void display(String color){
        System.out.println("方块形状:" + getShape() + ",颜色:" + color);
    }
}

(2)具体享元角色

package com.lwp.flyweight;
//具体享元
public class IBox extends  AbstractBox{
    @Override
    public String getShape() {
        return "I" ;
    }
}

package com.lwp.flyweight;
//具体享元
public class LBox extends  AbstractBox{
    @Override
    public String getShape() {
        return "L";
    }
}

package com.lwp.flyweight;
//具体享元
public class OBox extends  AbstractBox{
    @Override
    public String getShape() {
        return "O";
    }
}

(3)享元工厂角色

package com.lwp.flyweight;

import java.util.HashMap;
//享元工厂,将该类设计成单例模式
public class BoxFactory {
    private HashMap<String,AbstractBox> map;
    //在构造函数中进行初始化,私有构造函数
    private BoxFactory(){
        map= new HashMap<String,AbstractBox>();
        map.put("I",new IBox());
        map.put("L",new LBox());
        map.put("O",new OBox());
    }
    //提供一个公共方法获取该工厂对象
    public static BoxFactory getInstance(){
        return factory;
    }
    private static BoxFactory factory = new BoxFactory();
    //根据名称获取图形对象
    public AbstractBox getShape(String name ){
        return map.get(name);
    }
}

(4)外部访问类或测试类

package com.lwp.flyweight;

public class Client {
    public static void main(String[] args) {
        AbstractBox box1 =BoxFactory.getInstance().getShape("I");
        box1.display("灰色");

        AbstractBox box2 =BoxFactory.getInstance().getShape("L");
        box2.display("绿色");

        AbstractBox box3 =BoxFactory.getInstance().getShape("O");
        box3.display("灰色");

        AbstractBox box4 =BoxFactory.getInstance().getShape("O");
        box4.display("红色");
        //判断Box3与Box4是否为同一对象
        System.out.println("两次是否调用同一个对象:"+(box3==box4));
    }
}

7.3 优缺点

7.3.1 优点

大大减少对象的创建,降低系统的内存,使效率提高。

7.3.2 缺点:

提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

7.3.3 使用场景

在这里插入图片描述

7.4 享元模式在JDK中应用

在这里插入图片描述
在这里插入图片描述

五、行为型模式

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式和对象行为模式,前者采用类继承机制在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式对类行为模式具有更大的灵活性。

行为型模式包括策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式和解释器模式。

模板方法模式和解释器模式属于类行为模式,其余属于对象行为模式。

1、模板方法模式

面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时就知道了算法所需的关键步骤,而且确定了这些步骤执行的顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户都是一样的,可以在父类中实现,但办理具体业务却因人而异,它可能是存款、取款或者转账,可以延迟到子类中实现。

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变算法结构的情况下重新定义该算法的某些特定步骤。

1.1 模板方法模式结构

1.1.1 抽象类(Abstract Class):

负责给出一个算法的轮廓和骨架。它由一个模板方法和若干基本方法构成。

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

(2)基本方法:是实现算法各步骤的方法,是模板方法的组成部分。基本方法可以分为三类:

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

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

钩子方法(Hook Method):在抽象类中已经实现,包含用于判断的逻辑方法和需要子类重写的空方法两种。
一般钩子方法用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为Boolean类型
1.1.2 具体子类(Concrete Class):

实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

1.2 案例实现(以炒菜为例)

1.2.1 类图

在这里插入图片描述

1.2.2 代码( 略)

1.3 模板方法模式优缺点

1.3.1 优点:

(1)提高代码复用性:将相同部分的代码放在抽象的父类中,将不同的代码放入不同的子类中。

(2)实现了方向控制:通过一个父类调用其子类操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合“开闭原则”。

1.3.2 缺点

(1)对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计更加抽象。
(2)父类的抽象方法由子类来实现,子类的执行结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

1.3.3 适用场景

(1)算法的整体步骤很固定,但其中个别部分易变时,可以使用模板方法模式,将容易变的部分抽象出来,工子类实现。
(2)需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

1.4 模板方法模式在JDK中的实现

InputStream实现了模板方法模式,在该类中定义了多个reader()方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从上面代码中可以看到,无参的read()方法是抽象方法,要求子类必须实现,而read(byte[])方法调用了read(byte b[],int off,int len)方法,该方法调用了无参的read()方法,实际就是父类调用了子类的方法。

2、策略模式

该模式定义一系列算法,并将每个算法封装起来,使它们可以互相替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

2.1 策略模式结构

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

2.2 案例实现以商场促销为例

2.2.1 类图

在这里插入图片描述

2.2.2 代码

在这里插入图片描述

2.3 优缺点

2.3.1 优点

(1)策略之间可以自由切换:由于策略类实现同一接口,所有使它们之间能自由切换。

(2)易于扩展:增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开闭原则。

(3)避免使用多重条件选择语句(if else),充分体系面向对象设计思想。

2.3.2 缺点

(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
(2)策略模式将造成产生许多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

2.3.3 使用场景

(1)一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。

(2)一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。

(3)系统中各算法彼此完全独立,且要求对客户端隐藏具体算法的实现细节。

(4)系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。

(5)多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

2.4 策略模式在JDK中的应用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、命令模式

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

3.1 命令模式结构

(1)抽象命令类(command)角色:定义命令的接口,声明执行的方法。

(2)具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接受者,并调用接受者的功能来完成命令要执行的操作。

(3)实现者/接受者(Receiver)角色:接受者,真正执行命令的对象,任何类都可能成为一个接受者,只要它能够实现命令所要求的实现的相关功能。

(4)调用者/请求者(Invoker)角色:要求命令对象执行请求,通常持有命令对象,可以持有多个命令对象。这个是客户端真正触发命令并要求执行相应操作的地方,也就是说相当于使用命令对象的入口

3.2 以餐厅点餐案例实现

3.2.1 餐厅点餐类图

在这里插入图片描述

3.2.2 命令模式代码实现

(1)订单类

package com.lwp.command;

import java.util.HashMap;
import java.util.Map;

//订单类
public class Order {
    //餐桌号码
    private int diningTable;
    //所定的食品及份数
    private Map<String,Integer> foodDir= new HashMap<String,Integer>();

    public int getDiningTable() {
        return diningTable;
    }

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

    public Map<String, Integer> getFoodDir() {
        return foodDir;
    }

    public void setFood(String name, int num) {
        foodDir.put(name,num);
    }


}

(2)厨师类,接受者,具体执行命令

package com.lwp.command;
//厨师类,接受者,具体执行命令
public class SeniorChef {
    public void  makeFood(String name,int num){
        System.out.println(num + "份" + name);
    }
}

(3)抽象命令类

package com.lwp.command;
//抽象命令类
public interface Command {
    void execute();
}

(4)订单命令,持有接受者对象

package com.lwp.command;

import javax.swing.*;
import java.util.Map;
import java.util.Set;

//订单命令
public class OrderCommand implements Command {
    //持有接受者对象
    private  SeniorChef receiver;
    private Order order;

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

    @Override
    public void execute() {
        System.out.println(order.getDiningTable() + "桌的订单: ");
        Map<String,Integer> foodDir=order.getFoodDir();
        //遍历map集合
        Set<String> keys=foodDir.keySet();
        for (String foodName : keys){
            receiver.makeFood(foodName,foodDir.get(foodName));
        }
        System.out.println(order.getDiningTable() + "桌的菜");
    }
}

(5)服务员,请求者对象,聚合命令接口

package com.lwp.command;

import java.util.ArrayList;
import java.util.List;

//服务员,请求者对象
public class Waitor {
    //持有多个命令对象
    private List<Command> commands = new ArrayList<Command>();
    public void setCommands(Command cmd){
        commands.add(cmd);
    }
    //发起命令功能,喂,订单来了
    public  void orderUp(){
        System.out.println("美女服务员: 大厨,新订单来了。。。。。。");
        //遍历list集合
        for (Command command : commands) {
            if (command!=null){
                command.execute();
            }
        }
    }
}

(6)外部访问类

package com.lwp.command;

public class Client {
    public static void main(String[] args) {
        //创建第一个订单对象
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.setFood("西红柿炒鸡蛋",1);
        order1.setFood("小杯可口可乐",2);
        //创建第二个订单对象
        Order order2 = new Order();
        order2.setDiningTable(2);
        order2.setFood("尖椒肉丝",1);
        order2.setFood("小杯雪碧",1);

        //创建厨师对象
        SeniorChef receiver= new SeniorChef();
        //创建命令对象
        OrderCommand cmd1 = new OrderCommand(receiver,order1);
        OrderCommand cmd2 = new OrderCommand(receiver,order2);

        //创建调用者(请求者/服务员)
        Waitor invoke = new Waitor();
        invoke.setCommands(cmd1);
        invoke.setCommands(cmd2);
        invoke.orderUp();
    }
}

3.3 优缺点

3.3.1 优点

(1)降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。

(2)增加或删除命令非常方便。常用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。

(3)可以实现宏命令。命令模式与组合模式结合,将多个命令装配成一组命令,即宏命令。

(4)方便实现Undo与Redo操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

3.3.2 缺点

(1)使用命令模式可能会导致某些系统有过多的具体命令类。
(2)系统结构更复杂。

3.3.3 命令模式适用场景

(1)系统需要经请求调用者和请求接受者解耦,使得调用者和接受者不直接交互。

(2)系统需要在不同的事件指定请求、将请求排队和执行请求。

(3)系统需要执行命令的撤销(Undo)操作和恢复(Redo)操作。

3.4 命令模式在JDK中的实现

runable就是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是区执行方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、责任链模式

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。

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

注意:责任链模式也叫职责链模式。

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。

4.1 责任链模式结构

(1)抽象处理者(Handler)角色:定义一个处理请求的接口或抽象类,包含抽象处理方法和一个后继连接。

(2)具体处理者(Concrete Handler)类角色:实现抽象处理者的处理方法,判断是否处理本次请求,如果可以处理该请求则处理,否则将该请求传递给它的后继者。

(3)客户类(Client)角色:创建处理链,并行链头处理者对象条请求,它不关心处理细节和请求的传递过程。

4.2 案例实现(请假条)

4.2.1 责任链模式类图

在这里插入图片描述

4.2.2 责任链模式代码实现

(1)请假条,通过有参构造赋初值,故只有get函数

package com.lwp.responsiblity;
//请假条类
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;
    }
}

(2)抽象处理类:聚合该类本身,用于设置上级领导(下一个处理者),处理方法抽象(每级领导处理方法不同,需具体类实现)

package com.lwp.responsiblity;

import java.util.List;

//抽象处理者类
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;
    }
    //各级领导处理请假条的方法
    protected  abstract  void  handleLeave(LeaveRequest leave);
    //提交请假条请求
    public  final void  submit(LeaveRequest leave){
        //该领导进行审批
        this.handleLeave(leave);
        if(this.nextHandler != null && leave.getNum() > this.numEnd){
            //提交给上级领导进行审批
            this.nextHandler.submit(leave);
        } else{
            System.out.println("流程结束!");
        }
    }
}

(3)具体处理者类:

package com.lwp.responsiblity;
//小组长审批,无参构造调佣父类的有参构造
public class GroupLeader extends  Handler {
    public GroupLeader(){
        super(0,Handler.NUM_ONE);
    }
    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() +"。");
        System.out.println("小组长审批: 同意");
    }
}

package com.lwp.responsiblity;
//部门经理审批
public class Manager extends  Handler {
    //具体实现类的无参构造调用父类的有参构造
    public Manager(){
        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("部门经理审批: 同意");
    }
}

package com.lwp.responsiblity;
//总经理审批
public class GeneralManager extends  Handler {
    //具体实现类的无参构造调用父类的有参构造
    public GeneralManager(){
        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("总经理审批: 同意");
    }
}

(4)外部访问类或测试类

package com.lwp.responsiblity;

public class Client {
    public static void main(String[] args) {
        //创建请假条
        LeaveRequest leave = new LeaveRequest("lwp",3,"有事");
        //创建各级领导
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();

        //设置处理者链
        groupLeader.setNextHandler(manager);
        manager.setNextHandler(generalManager);

        //小明直接将请假条提给处理链头小组长
        groupLeader.submit(leave);
    }
}

4.3 优缺点

4.3.2 优点

(1)降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。

(2)增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。

(3)增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。

(4)责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

(5)责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

4.3.2 缺点

(1)不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。

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

(3)职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

4.4 责任链模式在JDK中应用

在web应用开发中,FilterChain是责任链模式(过滤器)的典型应用,下面简单实现过滤器
(1)定义空的request和response接口

package com.lwp.Filter;

public interface Request {
}

package com.lwp.Filter;

public interface Response {
}

(2)编写自己的FilterChain类

package com.lwp.Filter;

import java.util.ArrayList;
import java.util.List;

public class FilterChain {
    private List<Filter> filters = new ArrayList<Filter>();
    private int index=0;

    //链式调用
    public FilterChain addFilter(Filter filter){
        this.filters.add(filter);
        return this;
    }
    public void  doFilter(Request request,Response response){
        if (index==filters.size()){
            return;
        }
        Filter filter=filters.get(index);
        index++;
        //递归调用
        filter.DoFilter(request,response,this);
    }
}

(3)编写Filter接口类及具体实现类

package com.lwp.Filter;
//传递FilterChain参数
public interface Filter {
    public void DoFilter(Request req,Response res,FilterChain c);
}

package com.lwp.Filter;

public class FirstFilter implements Filter {
    @Override
    public void DoFilter(Request req, Response res, FilterChain chain) {
        System.out.println("过滤器1 前置处理");
        chain.doFilter(req,res);
        System.out.println("过滤器1 后置处理");
    }
}

package com.lwp.Filter;

public class SecondFilter implements Filter {
    @Override
    public void DoFilter(Request req, Response res, FilterChain chain) {
        System.out.println("过滤器2 前置处理");
        chain.doFilter(req,res);
        System.out.println("过滤器2 后置处理");
    }
}

(4)编写外部访问类或测试类

package com.lwp.Filter;

public class Client {
    public static void main(String[] args) {
        Request req = null;
        Response res = null;
        FilterChain filterChain=new FilterChain();
        //创建责任链
        filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());
        filterChain.doFilter(req,res);
    }
}

(5)运行结果
在这里插入图片描述

5、状态模式

对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变是改变其行为。

5.1 状态模式结构

(1)环境(context)角色:也称上下文,它定义了客户端程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。

(2)程序状态(state)角色:定义一个接口,用于封装环境对象中的特定状态所对应的行为。

(3)具体状态(COncrete State)角色:实现抽象状态所对应的行为。

5.2 案例实现(电梯状态)

5.2.1 状态模式类图

在这里插入图片描述

5.2.2 代码实现

(1)电梯状态抽象类,聚合环境类

package com.lwp.state;

public abstract class LifeState {
    //声明环境角色类变量
    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();
}

(2)电梯的4中状态类,继承抽象类的4个操作方法,每个状态类中,操作类的方法内容不同;并且调用父类中的context(环境类实例)中的setsetLifeState方法改变电梯状态。

package com.lwp.state;
//电梯开启状态
public class OpeningState extends LifeState{
    @Override
    //当前状态要执行的方法
    public void open() {
        System.out.println("电梯开启。。。");
    }

    @Override
    public void close() {
        super.context.setLifeState(context.CLOSING_STATE);
        super.context.close();
    }

    @Override
    public void run() {
        //什么都不做
    }

    @Override
    public void stop() {
        //什么都不做
    }
}

package com.lwp.state;
//电梯关闭状态
public class ClosingState extends LifeState{
    @Override
    public void open() {
        super.context.setLifeState(Context.OPENING_STATE);
        super.context.open();
    }

    @Override
    public void close() {
        System.out.println("电梯正在关闭。。。");
    }

    @Override
    public void run() {
        super.context.setLifeState(Context.RUNNING_STATE);
        super.context.run();
    }

    @Override
    public void stop() {
        super.context.setLifeState(Context.STOPPING_STATE);
        super.context.stop();
    }
}

package com.lwp.state;
//电梯运行状态
public class RunningState extends LifeState{
    @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.setLifeState(Context.CLOSING_STATE);
        super.context.stop();
    }
}

package com.lwp.state;
//电梯停止状态
public class StoppingState extends LifeState{
    @Override
    public void open() {
        super.context.setLifeState(Context.OPENING_STATE);
        super.context.open();
    }

    @Override
    public void close() {
        super.context.setLifeState(Context.CLOSING_STATE);
        super.context.close();
    }

    @Override
    public void run() {
        super.context.setLifeState(Context.RUNNING_STATE);
        super.context.run();
    }

    @Override
    public void stop() {
        System.out.println("电梯停止中。。。");
    }
}

(3)定义环境类(上下文)

把电梯的4个状态声明成4个常量;
聚合电梯抽象类,把它作为私有成员变量(用着保存电梯当前状态);
在电梯状态类的set函数中,把当前环境类(this)赋值给电梯状态LifeState 的私有成本变量context;
定义4个操作函数,调用电梯 状态类中的函数;
相互聚合,电梯抽象状态类聚合环境类;环境类聚合电梯抽象状态类。
package com.lwp.state;

public class Context {
    //定义对应状态对象的常量
    public final static OpeningState OPENING_STATE = new OpeningState();
    public final static ClosingState CLOSING_STATE = new ClosingState();
    public final static RunningState RUNNING_STATE = new RunningState();
    public final static StoppingState STOPPING_STATE = new StoppingState();

    //定义一个当前电梯状态变量
    private LifeState lifeState;

    public LifeState getLifeState() {
        return lifeState;
    }

    public void setLifeState(LifeState lifeState) {
        this.lifeState = lifeState;
        //设置当前状态对象中的Context对象。
        this.lifeState.setContext(this);
    }
    public void open(){
        this.lifeState.open();
    }
    public void close(){
        this.lifeState.close();
    }
    public void run(){
        this.lifeState.run();
    }
    public void stop(){
        this.lifeState.stop();
    }
}

(4)外部访问类或测试类,引入环境类,给环境类赋电梯的初始值

package com.lwp.state;

public class Client {
    public static void main(String[] args) {
        //创建环境角色
        Context context = new Context();
        //设置当前电梯状态
        context.setLifeState(new RunningState());

        context.run();
        context.close();
        context.open();
        context.stop();
    }
}

5.3 优缺点

5.3.1 优点

(1)将所有与某个状态相关的行为放在一个类中,并且可以方便地增加新的状态,只需改变对象状态即可改变对象行为。

(2)允许状态转换逻辑和状态对象合成一体,而不是某一个巨大的条件语句。

5.3.2 缺点

(1)状态模式的使用必然会增加系统类和对象个数。
(2)状态模式的实现和结构比较复杂,使用不当当导致程序结构和代码的混乱。
(3)状态模式对“开闭原则”的支持并不好。

5.3.4 使用场景

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

6、观察者模式

又称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;这个主题对象发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。

6.1 观察者模式结构

(1)Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合中,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加或删除观察者对象。
如果抽象主题是一个接口,则集合成员变量由该接口的自实现类来定义。

(2)ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题内部状态发生变化时,给所有注册过的观察者发送通知。

(3)Observer:抽象观察者,是观察者的抽象类,定义一个更新接口,在得到主题更改通知时更新自己。

(4)ConcreteObserver:具体观察者,实现抽象观察者定义的更新下接口,以便在得到主题更改通知时更新自身状态。

6.3 案例实现(微信公众号)

6.3.1 观察者模式类图

在这里插入图片描述

6.3.2 代码(略)

6.4 优缺点

6.4.1 优点

在这里插入图片描述

6.4.2 缺点

在这里插入图片描述

6.4.2 使用场景

在这里插入图片描述

6.5 观察者模式在JDK中应用

6.5.1 Observable源码解析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.5.2 警察盯小偷代码

(1)小偷代码实现Observable类

package com.lwp.observer;

import java.util.Observable;

public class Thief extends Observable {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public void steal(){
        System.out.println("小偷:我偷东西了,有没有人来抓我!");
        super.setChanged();
        super.notifyObservers();
    }
}

(2)警察代码实现Oberver接口

package com.lwp.observer;

import java.util.Observable;
import java.util.Observer;

public class Policeman implements Observer {
    private String name;

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

    public String getName() {
        return name;
    }

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

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("警官:" + ((Thief)o).getName() + ",我已经盯你很久了!!");
    }
}

(3)外部访问类

package com.lwp.observer;

public class Client {
    public static void main(String[] args) {
        //创建小偷
        Thief t = new Thief("隔壁老王");
        //创建警察
        Policeman p =new Policeman("黑猫警长");
        //让警察盯着小偷
        t.addObserver(p);
        //小偷偷东西
        t.steal();
    }
}

7、中介者模式

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

7.1 中介者模式结构

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

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

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

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

7.2 案例实现(租房为例)

7.2.1 中介者模式类图

在这里插入图片描述

7.2.2 代码实现

(1)抽象中介者


public abstract class Mediator {
    /**
     * 声明一个联络的方法
     * @param message
     * @param person
     */
    protected abstract void contact(String message,Person person);

(2)中介者实现类(核心类)

/**
 * @author:lwp
 * @create: 2022-11-03
 * @description: 具体中介者
 **/
public class MediatorStructure extends Mediator{
    /**
     * 中介者需要知道房主是谁
     */
    private HouseOwner houseOwner;
    /**
     * 中介者需要知道承租者是谁
     */
    private Tenant tenant;
 
    public MediatorStructure() {
    }
 
    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;
    }
 
    /**
     * 重写联络方法
     * @param message
     * @param person
     */
    @Override
    protected void contact(String message, Person person) {
        if (person == houseOwner){
            //将房主的信息反馈给承租者
            this.tenant.getMessage(message);
        }else {
            //将承租者的信息反馈给房主
            this.houseOwner.getMessage(message);
        }
    }
}

(3)抽象同事类


/**
 * @author: lwp
 * @create: 2022-11-3
 * @description: 抽象Person类
 **/
public abstract class Person {
    protected String name;
    protected Mediator mediator;
	//有参构造函数,包含抽象中介者类 
    protected Person(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
}

(4)具体同事类

/**
 * @author:lwp
 * @create: 2022-11-3
 * @description: 具体房主类
 **/
public class HouseOwner extends Person{
    public HouseOwner(String name, Mediator mediator) {
    	//调用父类的有参构造
        super(name, mediator);
    }
 
    /**
     * 与中介者联系的方法
     * @param message
     * 调用中介者的联系方法,并把自己传进去
     */
    public void contact(String message){
        this.mediator.contact(message,this);
    }
 
    /**
     * 获取信息
     * @param message
     */
    public void getMessage(String message){
        System.out.println("房主" + this.name + "获取到的信息是:"+message);
    }
}

/**
 * @author: lwp
 * @create: 2022-11-3
 * @description: 具体承租人
 **/
public class Tenant extends Person{
    public Tenant(String name, Mediator mediator) {
        super(name, mediator);
    }
 
    /**
     * 与中介者联系的方法
     * @param message
     * 用中介者的联系方法,并把自己传进去
     */
    public void contact(String message){
        this.mediator.contact(message,this);
    }
 
    /**
     * 获取信息
     * @param message
     */
    public void getMessage(String message){
        System.out.println("承租者" + this.name + "获取到的信息是:"+message);
    }
}

(5)外部访问类

/**
 * @author: lwp
 * @create: 2022-11-3
 * @description: 测试
 **/
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.contact("我需要租三室一厅的房子");
        //房主与中介联系
        houseOwner.contact("我这里有三室一厅的房子,你要租吗?");
    }
}

7.3 优缺点

7.3.1 优点

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

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

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

7.3.2 缺点

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

7.3.3 使用场景

(1)系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
(2)当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

8、迭代器模式

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

8.1 迭代器模式结构

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

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

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

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

8.2 案例实现(学生)

8.2.1 迭代器模式类图

在这里插入图片描述

8.2.2 代收实现

(1)定义迭代器接口,声明hasNext、next方法

public interface StudentIterator {
    boolean hasNext();
    Student next();
}

(2)定义具体的迭代器类,重写所有的抽象方法

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

(3)定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法

public interface StudentAggregate {
    void addStudent(Student student);

    void removeStudent(Student student);

    StudentIterator getStudentIterator();
}

(4) 定义具体的容器类,重写所有的方法

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);
    }
	
	//获取学生迭代器,并把this(容器本身)传递给学生迭代器的有参构造
    @Override
    public StudentIterator getStudentIterator() {
        return new StudentIteratorImpl(list);
    }
}

8.3 优缺点

8.3.1 优点

(1)它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。

(2)迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。

(3)在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

8.3.2 缺点

增加了类的个数,这在一定程度上增加了系统的复杂性。

8.3.3 使用场景

(1)当需要为聚合对象提供多种遍历方式时。

(2)当需要为遍历不同的聚合结构提供一个统一的接口时。

(3)当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

8.4 迭代器模式在JDK中的实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

9、访问者模式

表示一个作用于其对象结构中的各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

可以对定义这么理解:有这么一个操作,它是作用于一些元素之上的,而这些元素属于某一个对象结构。同时这个操作是在不改变各元素类的前提下,在这个前提下定义新操作是访问者模式精髓中的精髓。

主要解决:稳定的数据结构和易变的操作耦合问题。就是把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。

9.1 访问者模式结构

(1)抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以方位的元素,它的方法个数从理论上来讲与元素的个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素的个数不能改变。

(2)具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。

(3)抽象元素(Element)角色:定义了一个接收访问者的方法(accept),其意义是指,每个元素都要可以被访问者访问。

(4)具体元素(ConcreteElement)角色:提供接收访问方法的具体实现,这个具体的实现,通常情况下是使用访问者提供的服务该元素的方法。

(5)对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(element),并且可以迭代这些元素,供访问者访问。

9.2 案例实现(喂宠物)

在这里插入图片描述

9.2.1 访问者模式类图

在这里插入图片描述

9.2.2 代码实现

(1)抽象访问类角色

package com.lwp.visitor;
//抽象访问类角色
public interface Person {
    //喂食宠物狗
    void feed (Cat cat);
    //喂食宠物猫
    void feed (Dog dog);
}

(2)具体访问者角色

package com.lwp.visitor;
//具体访问者
public class Owner implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食狗");
    }
}

package com.lwp.visitor;
//具体访问者
public class Someone implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("其他人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("其他人喂食狗");
    }
}

(3)抽象元素类角色

package com.lwp.visitor;
//抽象元素角色类
public interface Animal {
    //接收访问者访问的功能
    void accept(Person person);
}

(4)具体元素类角色

package com.lwp.visitor;
public class Dog implements Animal {
    @Override
    //接收访问者来访问,访问直接调用访问者的喂食函数,并把当前对象作为参数传递
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,汪汪汪。。。");
    }
}

package com.lwp.visitor;
public class Cat implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,喵喵喵。。。");
    }
}

(5)对象结构角色

package com.lwp.visitor;

import java.util.ArrayList;
import java.util.List;

//数据接口对象
public class Home {
   //声明一个集合对象,用于存储元素
    private List<Animal> nodeList = new ArrayList <Animal>();
    //添加元素功能
    public void add(Animal animal){
        nodeList.add(animal);
    }

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

(6)外部访问类

package com.lwp.visitor;

public class Client {
    public static void main(String[] args) {
        //声明一个结构
        Home home = new Home();
        //添加元素
        home.add(new Dog());
        home.add(new Cat());
        //声明访问者
        Person person = new Owner();
        //通过访问者访问
        home.action(person);
    }
}
外部访问类中,声明对象结构角色后,项该角色添加元素;
声明一个访问者角色,调用对象结构类中的允许访问者访问方法(允许访问者喂食)中,并将访问者Person传入;
在允许访问者访问方法中,遍历元素,调用每个元素的允许访问者访问(具体的允许访问者喂食)的函数,传入参数是访问者person;
最后调用访问者person的操作(喂食)方法,并将元素自己传递进去。

9.3 优缺点

9.3.1 优点

(1)扩展性好
在不改变对象结构中的元素情况下,为对象结中的元素添加新的功能。

(2)复用性好
通过访问者来定义整个对象结构通用的功能,从而提供复用程度。

(3)分离无关行为
提供访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能比较单一。

9.3.2 缺点

(1)对象结构变化很困难
在访问者模式中,每增加一个新的元素类,都要在每一个具体的访问者类中增加相应的具体操作,这违背了“开闭原则”。

(2)违反了依赖倒置原则
访问者模式依赖具体类,而没有依赖抽象类。

9.4 访问者模式扩展

9.4.1 分派

变量被声明时的类型叫做静态类型,而变量所引用的类型叫做实际类型。如Map m=new HashMap(),m变量的静态类型是Map,实际类型是HashMap。根据对象的类型进行选择,就是分派。

9.4.2 动态分派

动态分派发生在运行期,分派会动态置换某个方法,如重写就是动态分派。

public class Animal {
    public void execute(){
        System.out.println("animal");
    }
}
class Dog extends Animal{
    @Override
    public void execute() {
        System.out.println("dog");
    }
}
class Cat extends Animal{
    @Override
    public void execute() {
        System.out.println("cat");
    }
}
class Client{
    public static void main(String[] args) {
        Animal a1=new Animal();
        Animal a=new Dog();
        Animal b=new Cat();
        a1.execute();
        a.execute();
        b.execute();
    }
}

以上代码的运行结果很明了,这里会动态的置换掉execute方法,从而输出三个不同的值。

9.4.3 静态分派

静态分派发生在编译期,分派根据静态类型进行分派,如重载就是静态分派。

public class Animal {}
class Dog extends Animal{}
class Cat extends Animal{}
class Execute{
    public void execute(Animal a){
        System.out.println("animal");
    }
    public void execute(Dog a){
        System.out.println("dog");
    }
    public void execute(Cat a){
        System.out.println("cat");
    }
}
class Client{
    public static void main(String[] args) {
        Animal a=new Animal();
        Animal b=new Dog();
        Animal c=new Cat();
        Execute execute=new Execute();
        execute.execute(a);
        execute.execute(b);
        execute.execute(c);
        //输出结果
        //animal
        //animal
        //animal
    }
}

以上代码的运行输出是三个相同的值animal。原因就是重载的方法是根据静态类型,Animal,进行的,而且这个分派过程在编译期就完成了。

9.4.4 双分派

双分派就是在第一次的分派的基础上,进行第二次的分派。

public class Animal {
    public void accetp(Execute execute){
        execute.execute(this);
    }
}
class Dog extends Animal {
    public void accetp(Execute execute){
        execute.execute(this);
    }
}
class Cat extends Animal {
    public void accetp(Execute execute){
        execute.execute(this);
    }
}
class Execute{
    public void execute(Animal a){
        System.out.println("animal");
    }
    public void execute(Dog a){
        System.out.println("dog");
    }
    public void execute(Cat a){
        System.out.println("cat");
    }
}
class Clients{
    public static void main(String[] args) {
        Animal a=new Animal();
        Animal b=new Dog();
        Animal c=new Cat();
        Execute execute=new Execute();
        a.accetp(execute);
        b.accetp(execute);
        c.accetp(execute);
    }
}

如上面的代码,Execute对象作为参数传递给Animal类型的a,b,c对象,这里的b和c对象的accetp方法是重写的,所以是一次动态分派,也就是执行实际类型中的方法,这时将this对象传递进去完成第二次分派,而传递的是当前的对象不是父类型的对象(对于b c来说),所以执行的是对应的实际类型的方法。最后输出三个不同的值。

10、备忘录模式

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到某一特定的历史时刻,当新的状态无效或存在问题时,可以使用暂时存储起来的备忘录将状态复原。

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

10.1 结构

10.1.1 三种角色

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

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

(3)管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

10.1.2

(1)备忘录有两个等效的接口
窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他吧备忘录传递给其他对象。

宽接口:与管理者看到的窄接口相反,发起人可以看到一个宽接口(Wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这份发起人对象的内部状态。

10.2 宽接口案例实现(白箱打Boss游戏)

10.2.1 宽接口备忘录模式类图

在这里插入图片描述

10.2.2 代码实现

(1)游戏角色,发起人角色

package com.lwp.memento.white_box;
//游戏角色,发起人角色
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 restoreState(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;
    }

}

(2)备忘录角色

package com.lwp.memento.white_box;
//备忘录角色类
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 RoleStateMemento() {
    }

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

(3)管理者角色

package com.lwp.memento.white_box;

public class RoleStateCaretaker {
    private RoleStateMemento roleStateMemento;

    public RoleStateMemento getRoleStateMemento() {
        return roleStateMemento;
    }

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

(4)外部访问类

package com.lwp.memento.white_box;

public class Client {
    public static void main(String[] args) {
        System.out.println("=========大战Boss前=============");
        //创建游戏角色对象
        GameRole gameRole = new GameRole();
        gameRole.initState();
        gameRole.stateDisplay();
        //将游戏角色状态保存
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());

        System.out.println("=========大战Boss后=============");
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("=========恢复之前状态=============");
        gameRole.restoreState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

游戏角色的保存调用管理者类的set函数,需要传入一个备忘录类,该备忘录类由游戏角色的saveState()方法生成。

游戏角色的恢复以前状态,是直接调用游戏角色的restoreState()方法,需要传入一个备忘录类的参数,该备忘录参数由管理者的get方法得到。

10.3 案例实现(黑箱打Boss游戏)

在这里插入图片描述

10.3.1 类图

在这里插入图片描述

10.3.2 代码实现

(1)游戏角色,发起人角色

package com.lwp.memento.black_box;

//游戏角色,发起人角色
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 Memento saveState(){
        return new RoleStateMemento(vit,atk,def);
    }

    //恢复角色初始化状态
    public void restoreState(Memento memento){
        //传递进来的memento,向下转型
        RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
        //将备忘录中存储的状态恢复到角色中
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }
    //备忘录具体实现类,定义在发起者内部,并且私有,外部就访问不到,
    //它必须继承备忘录的抽象接口,即窄接口
    private class RoleStateMemento implements Memento {
        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 RoleStateMemento() {
        }

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

}

(2)备忘录抽象接口,窄接口

package com.lwp.memento.black_box;
//备忘录接口,对外提供窄接口
public interface Memento {
}

//它的实现类在发起者(游戏角色)的内部私有类,故除发起者外,其它类无法访问

(3)管理者类

package com.lwp.memento.black_box;

public class RoleStateCaretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

(4)外部访问类

package com.lwp.memento.black_box;

public class Client {
    public static void main(String[] args) {
        System.out.println("=========大战Boss前=============");
        //创建游戏角色对象
        GameRole gameRole = new GameRole();
        gameRole.initState();
        gameRole.stateDisplay();
        //将游戏角色状态保存
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setMemento(gameRole.saveState());

        System.out.println("=========大战Boss后=============");
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("=========恢复之前状态=============");
        gameRole.restoreState(roleStateCaretaker.getMemento());
        gameRole.stateDisplay();
    }
}

白箱与黑箱区别在于,白箱是宽接口,黑箱是双接口(发起者调用私有内部类作为宽接口;其余采用空的抽象接口类作为窄接口)。

10.4 优缺点

10.4.1 优点

(1)提供了一种可以恢复状态的机制,当用户需要时能够比较方便地将数据恢复到某个历史的状态。

(2)实现了内部状态的封装,处理创建它的发起者之外,其他对象都不能够访问这些状态信息。

(3)简化了发起者类,发起人不需要管理和保存内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

10.4.2 缺点

资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

10.4.3 使用场景

(1)需要保存与恢复数据的场景,如玩游戏时的中间结构的存档功能。

(2)需要提供一个可回滚操作的场景。

11、解释器模式

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
在这里插入图片描述
在这里插入图片描述

11.1 解释器模式结构

(1)抽象表达式(Abstract Expression)角色:定义解释器的接口,约解释器的解释操作,主要包含解释方法interpret()。

(2)终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结符表达式与之对应。

(3)非终结符表达式(NonTerminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每一条规则都对应一个非终结符表达式。

(4)环境(Context)角色:通常包括各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。

(5)客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

11.2 案例实现(加减运算为例)

11.2.1 类图

在这里插入图片描述

11.2.2 代码实现

(1)抽象表达式

package com.lwp.interpreter;

public abstract class AbstractExpression {
    public abstract int interpret(Context context);
}

(2)变量(终止符表达式)

package com.lwp.interpreter;

public class Variable extends AbstractExpression{
    //定义存储变量名的成员变量
    private String name;

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

    @Override
    public int interpret(Context context) {
        return context.getValue(this);
    }

    @Override
    public String toString() {
        return name;
    }
}

(3)±表达式(非终止符表达式)

package com.lwp.interpreter;

public class Plus extends AbstractExpression{
    //+号左边表达式
    private  AbstractExpression left;
    //+号右边表达式
    private AbstractExpression right;

    public Plus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        //将左边表达式的结果和右边表达式的结果相加
        return left.interpret(context) + right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + " + " + right.toString() + ")";
    }
}


package com.lwp.interpreter;

public class Minus extends AbstractExpression{
    //-号左边表达式
    private  AbstractExpression left;
    //-号右边表达式
    private AbstractExpression right;

    public Minus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret(Context context) {
        //将左边表达式的结果和右边表达式的结果相减
        return left.interpret(context) - right.interpret(context);
    }

    @Override
    public String toString() {
        return "(" + left.toString() + " - " + right.toString() + ")";
    }
}

(4)环境类

package com.lwp.interpreter;

import java.util.HashMap;
import java.util.Map;

public class Context {
    //定义一个map集合,用来存储变量及对应的值
    private Map<Variable,Integer> map = new HashMap<Variable,Integer>();

    //添加变量的功能
    public void assign(Variable var,Integer value){
        map.put(var,value);
    }

    //根据变量获取对应的值
    public  int  getValue(Variable var){
        return map.get(var);
    }
}

(5)外部访问类

package com.lwp.interpreter;

public class Client {
    public static void main(String[] args) {
        Context context = new Context();

        //创建多个变量对象
        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");

        //将变量存在环境对象中
        context.assign(a,1);
        context.assign(b,2);
        context.assign(c,3);
        context.assign(d,4);

        AbstractExpression expression = new Minus(a,new Plus(new Minus (b,c),d));
        int result = expression.interpret(context);
        System.out.println(expression + "=" + result);
    }
}

11.3 优缺点

11.3.1 优点

在这里插入图片描述

11.3.2 缺点

在这里插入图片描述

11.3.3 使用场景

在这里插入图片描述

七、自定义spring框架

1、spring框架

Spring框架包含的功能大约由20个小模块组成。这些模块按组可分为核心容器(Core Container)、数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP和Aspects)、设备(Instrumentation)、消息(Messaging)和测试(Test)。如下图所示:
在这里插入图片描述

1.1 核心层(Core Container):Beans、Core、Context、Expression

该层由4个模块组成:spring-beans spring-core spring-context spring-expression(spring expression Language,SpEl) 。它们对应的jar包如下:

(1)spring-core:该模块是依赖注入IoC与DI的最基本实现。

(2)spring-beans:该模块是Bean工厂与bean的装配。

(3)spring-context:该模块构架于核心模块之上,它扩展了 BeanFactory,为它添加了 Bean 生命周期控制、框架事件体系以及资源加载透明化等功能。ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory 不同,ApplicationContext 容器实例化后会自动对所有的单实例 Bean 进行实例化与依赖关系的装配,使之处于待用状态。

(4)spring-context-indexer:该模块是 Spring 的类管理组件和 Classpath 扫描。

(5)spring-context-support:该模块是对 Spring IOC 容器的扩展支持,以及 IOC 子容器。

(6)spring-expression:该模块是Spring表达式语言块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。

//使用步骤:
//1、定义Spel表达式,ExpressionParser parser = new SpelExpressionParser()
//2、用SpEl解析表达式:Expression exp = parser.parseExpression(str);
//3、处理占位符变量时需要用到表达式的上下文
//EvaluationContext context = new StandardEvaluationContext()
//4、通过上下文对占位符变量进行赋值 context.setVariable("start",4)
//5、执行SpEl表达式,并得到字符串表达式的值exp.getValue(context)

package com.lwp;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpEl {
    public static void main(String[] args) {
        //定义一个字符串,字符串截取后转为大写,表达式中有两个变量,一个是start,一个是end
        String str = "(\"www.\" + \"suho.com\").substring(#start,#end).toUpperCase()";
        ExpressionParser parser = new SpelExpressionParser();//定义一个SpEl表达式
        Expression exp = parser.parseExpression(str); //表达式解析
        //在表达式执行前还需要考虑两个占位符的配置问题
        EvaluationContext context = new StandardEvaluationContext();//定义表达式的上下文
        context.setVariable("start",4);//配置start参数信息
        context.setVariable("end",9);//配置end参数信息
        System.out.println("ApEL处理结果是: " + exp.getValue(context));
    }

在这里插入图片描述

1.2 数据访问与集成(Data Access/Integration)——Jdbc、Orm、Oxm、Jms、Transactions

该层由spring-jdbc、spring-tx、spring-orm、spring-jms 和 spring-oxm 5 个模块组成。它们对应的jar包如下

(1)spring-jdbc:该模块提供了 JDBC抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。

(2)spring-tx:该模块支持编程式事务和声明式事务,可用于实现了特定接口的类和所有的 POJO 对象。编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细。

(3)spring-orm:该模块提供了对流行的对象关系映射 API的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring 的其它功能整合,比如前面提及的事务管理。

(4)spring-oxm:该模块提供了对 OXM 实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。

(5)spring-jms:该模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了 spring-messaging 模块。

1.3 Web——Web、Webmvc、WebFlux、Websocket

该层由 spring-web、spring-webmvc、spring-websocket 和 spring-webflux 4 个模块组成。它们对应的jar包如下

(1)spring-web:该模块为 Spring 提供了最基础 Web 支持,主要建立于核心容器之上,通过 Servlet 或者 Listeners 来初始化 IOC 容器,也包含一些与 Web 相关的支持。

(2)spring-webmvc:该模块众所周知是一个的 Web-Servlet 模块,实现了 Spring MVC(model-view-Controller)的 Web 应用。

(3)spring-websocket:该模块主要是与 Web 前端的全双工通讯的协议。

(4)spring-webflux:该模块是一个新的非堵塞函数式 Reactive Web 框架,可以用来建立异步的,非阻塞,事件驱动的服务,并且扩展性非常好。

1.4 面向切面编程——AOP,Aspects

该层由spring-aop和spring-aspects 2个模块组成。它们对应的jar包如下:

(1)spring-aop:该模块是Spring的另一个核心模块,是 AOP 主要的实现模块。

(2)spring-aspects:该模块提供了对 AspectJ 的集成,主要是为 Spring AOP提供多种 AOP 实现方法,如前置方法后置方法等。

1.5 设备(Instrumentation)——Instrmentation

spring-instrument:该模块是基于JAVA SE 中的"java.lang.instrument"进行设计的,应该算是 AOP的一个支援模块,主要作用是在 JVM 启用时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现 AOP 的功能。

1.6 消息(Messaging)——Messaging

spring-messaging:该模块是从 Spring4 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

1.7 测试(Test)——Test

spring-test:该模块主要为测试提供支持的,通过 JUnit 和 TestNG 组件支持单元测试和集成测试。它提供了一致性地加载和缓存 Spring 上下文,也提供了用于单独测试代码的模拟对象(mock object)。

2、Spring的核心

在前面部分就一直强调Spring的最核心部分是控制反转(IoC)和面向切面编程(AOP),那么它们到底是什么东西?,下面我们来简单介绍一下:

注意:有的人认为 控制反转 应该包含 IoC 和 DI,而实质上它们二者是一样的,控制反转(IOC)和依赖注入(DI)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖注入的方式,实现对象之间的解耦。其中IOC是个更宽泛的概念,而DI是更具体的概念。

2.1 IOC(Inversion of Control 控制反转) 或DI (Dependency Injection依赖注入)

(1)IOC (控制反转):说简单点就是当我们使用对象调用一个方法或者类时,不再由我们主动去创建这个类的对象,控制权交给spring框架。说复杂点就是资源(组件)不再由使用资源双方进行管理,而是由不使用资源的第三方统一管理,这样带来的好处。第一,资源的集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度

(2)DI (依赖注入):由spring框架主动创建被调用类的对象,然后把这个对象注入到我们自己的类中,使得我们可以直接使用它。

2.2 AOP(Aspect Oriented Programming面向切面编程)

(1)AOP(Aspect Oriented Programming面向切面编程):说简单点就是我们可以在不修改源代码的情况下,对程序的方法进行增强。

(2)说复杂点就是将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。即系统级的服务从代码中解耦出来。

(3)例如:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来。允许你把遍布应用各处的功能分离出来形成可重用组件。提高程序的可重用性,同时提高了开发的效率。

3、spring bean

3.1 三种不同的方式定义Spring bean

3.1.1 使用构造型@Component注释(或其衍生物)注释你的类
@Component衍生列表包括:
@Service
@Repository
@Controller

@Component
class MySpringBeanClass {
 //...
}
3.1.2 使用@Bean作为工厂方法

如果某个类属于某个外部库而你无法使用@Component进行注释,你必须在自定义bean的配置类中使用@ Bean注释创建工厂方法。如果你不想使类依赖于Spring,你也可以将此选项用于你拥有的类。

@Bean必须用着方法上。

@Configuration
class MyConfigurationClass {
 
 @Bean
 public NotMyClass notMyClass() {
 return new NotMyClass();
 }
}
3.1.3 在XML配置文件中声明bean定义
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="com.sxsl.spring.dao.impl.UserDaoImpl"></bean>

    <bean id="userService" class="com.sxsl.spring.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
</beans>

3.2 Spring bean属性列表包括

.(1)Bean类
创建bean定义时,将其与应用程序中的单个具体类连接。这个类本身是bean的主要属性。

(2)Bean名称name
Spring bean名称是Spring用于标识bean的自定义字符串。与bean类不同,名称在整个应用程序中必须是唯一的。你不能定义两个具有相同名称的bean,即使它们的类型相同。

(3)Bean依赖项
用作bean的对象可以使用其他bean来执行其作业。当Spring创建一个定义某些依赖项的对象时,框架需要首先创建这些依赖项。这些依赖项也可以有自己的依赖项。

(4) Bean作用域

Spring bean的范围定义了框架在运行时创建的特定类的实例数。作用域还描述了创建新对象的条件。

Spring为你的bean提供了几个作用域。框架的核心有两个:
单例 - 单个实例
原型 - 多个实例

此外,Spring还附带了专门用于Web应用程序的bean作用域:
请求
会话
全局会话
应用级别Application

4、Spring IOC相关接口分析

4.1 BeanFactory

(1)Spring的本质是一个bean工厂(beanFactory)或者说bean容器,它按照我们的要求,生产我们需要的各种各样的bean,提供给我们使用。只是在生产bean的过程中,需要解决bean之间的依赖问题,才引入了依赖注入(DI)这种技术。也就是说依赖注入是beanFactory生产bean时为了解决bean之间的依赖的一种技术而已。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2)高级容器的顶级接口——ApplicationContext接口
ApplicationContext接口继承自BeanFactory的二级接口ListableBeanFactory和HierarchicalBeanFactroy。所以高级容器其实也是在普通容器的基础上实现的,在普通容器的基础上扩展除了非常丰富的功能接口来供用户使用,如果深入去阅读源码,会发现其实在高级容器的一个很重要的抽象类AbstractRefreshableApplicationContext内部,是有一个私有的普通容器对象的,而我们常用的一些高级容器实现类,比如FileSystemXmlApplicationContext、AnnotatioConfigWebApplicationContext等都继承自这个抽象类,也就是说都继承了这个普通容器对象:
在这里插入图片描述
在这里插入图片描述

4.2 BeanDefinition

(1)Spring 将管理的对象称之为 Bean,容器会先实例化 Bean,然后自动注入,实例化的过程就需要依赖 BeanDefinition。

(2)BeanDefinition 用于保存 Bean 的相关信息,包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等,它是实例化 Bean 的原材料,Spring 就是根据 BeanDefinition 中的信息实例化 Bean。

在这里插入图片描述

(3)BeanDefinition 主要是用来描述 Bean,其存储了 Bean 的相关信息,Spring 实例化 Bean 时需读取该 Bean 对应的 BeanDefinition。BeanDefinition 整体可以分为两类,一类是描述通用的 Bean,还有一类是描述注解形式的 Bean。一般前者在 XML 时期定义 <bean‘> 标签以及在 Spring 内部使用较多,而现今我们大都使用后者,通过注解形式加载 Bean。

(4)BeanDefinition 是对 Bean 的定义,其保存了 Bean 的各种信息,如属性、构造方法参数、是否单例、是否延迟加载等。这里的注册 Bean 是指将 Bean 定义成 BeanDefinition,之后放入 Spring 容器中,我们常说的容器其实就是 Beanfactory 中的一个 Map,key 是 Bean 的名称,value 是 Bean 对应的 BeanDefinition,这个注册 Bean 的方法由 BeanFactory 子类实现。

4.3 BeanDefinitionReader

(1)BeanDefinitionReader 的作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。

(2)BeanDefinitionRegistry 接口一次只能注册一个 BeanDefinition,而且只能自己构造 BeanDefinition 类来注册。BeanDefinitionReader 解决了这些问题,它一般可以使用一个 BeanDefinitionRegistry 构造,然后通过 loadBeanDefinitions()等方法,把 Resources 转化为多个 BeanDefinition 并注册到 BeanDefinitionRegistry。

在这里插入图片描述
(3)AbstractBeanDefinitionReader:实现了 EnvironmentCapable,提供了获取/设置环境的方法。定义了一些通用方法,使用策略模式,将一些具体方法放到子类实现。

XmlBeanDefinitionReader:读取 XML 文件定义的 BeanDefinition
PropertiesBeanDefinitionReader:可以从属性文件,Resource,Property 对象等读取 BeanDefinition
GroovyBeanDefinitionReader:可以读取 Groovy 语言定义的 Bean

在这里插入图片描述

4.4 BeanDefinitionRegistry

BeanDefinitionRegistry 是一个接口,它定义了关于 BeanDefinition 的注册、移除、查询等一系列的操作。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.5 创建容器

(1)spring可以通过创建ApplicationContext容器可以实现对于bean的操作,而实现ApplicationContext接口的实现类就是ClassPathXmlApplicationContext类,这里使用多态的特性。
在这里插入图片描述
(2)refresh()
该方法是 Spring Bean 加载的核心,它是 ClassPathXmlApplicationContext 的父类 AbstractApplicationContext 的一个方法 , 顾名思义,用于刷新整个Spring 上下文信息,定义了整个 Spring 上下文加载的流程。

5、自定义Spring IOC

5.1 项目相关知识

5.1.1 property中ref和value的区别

(1)ref引用一个已经存在的对象; value创建一个新的对象

(2)value可以赋一些简单类型的值和对象的值; ref可以引用其他的bean对象。

(3)使用ref的时候,spring容器会在引用后进行验证,验证当前的xml是否存在引用的bean; 使用value的时候,spring会在容器启动,实例化bean的时候进行验证。

5.1.2 项目的文件架构

在这里插入图片描述

5.2 定义bean相关的pojo类

5.2.1 PropertyValue类

用于封装bean的属性,体现下面的配置文件就是封装bean标签的子标签property标签数据。

(1)applicationContext.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans>

    <bean id="userDao" class="com.lwp.spring.dao.impl.UserDaoImpl">
        <property name="name" value="李四"></property>
        <property name="password" value="123456"></property>
    </bean>

    <bean id="userService" class="com.lwp.spring.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
</beans>

用于封装bean的属性,体现配置文件就是封装bean标签的子标签property标签数据。
(2)代码

package com.sxsl.framework.beans.factory;
/**
 * 用来封装bean标签下的property标签
 * name
 * ref
 * value:给基本数据类型或字符串赋初值
 */
public class PropertyValue {
    private String name;
    private String ref;
    private String value;

    public PropertyValue() {
    }

    public PropertyValue(String name, String ref, String value) {
        this.name = name;
        this.ref = ref;
        this.value = value;
    }

    public String getName() {
        return name;
    }

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

    public String getRef() {
        return ref;
    }

    public void setRef(String ref) {
        this.ref = ref;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
5.2.2 MutablePropertyValues类

一个bean标签可以有多个property子标签,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。

package com.sxsl.framework.beans.factory;
//用于存储管理多个PropertyValue对象
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class MutablePropertyValues implements Iterable<PropertyValue> {
    private final List<PropertyValue>  propertyValueList;

    public MutablePropertyValues() {
        this.propertyValueList = new ArrayList<PropertyValue>();
    }

    public MutablePropertyValues(List<PropertyValue> propertyValueList) {
        if (propertyValueList == null){
            this.propertyValueList = new ArrayList<PropertyValue>();
        }else{
            this.propertyValueList = propertyValueList;
        }
    }
    //获取所有的Propertyvalue对象,返回以数组的形式
    public PropertyValue[] getPropertyValues(){
        //将集合转换为数组并返回
        return propertyValueList.toArray(new PropertyValue[0]);
    }

    //根据name属性获取PropertyValue对象
    public PropertyValue getPropertyValue(String propertyName){
        //遍历集合对象
        for (PropertyValue propertyValue : propertyValueList) {
            if (propertyValue.getName().equals(propertyName)){
                return  propertyValue;
            }
        }
        return null;
    }

    //判断集合是否为空
    public boolean isEmpty(){
        return propertyValueList.isEmpty();
    }

    //添加PropertyValue对象
    public MutablePropertyValues addPropertyValue(PropertyValue pv){
        //判断集合中存储的PropertyValue对象是否和传递进来的对象重复,如果重复则进行覆盖
        for (int i=0;i <propertyValueList.size();i++) {
            //获取集合中每一个propertyv对象
            PropertyValue currentPv = propertyValueList.get(i);
            if(currentPv.getName().equals(pv.getName())){
                propertyValueList.set(i,pv);
                return  this;//目的实现连式编程
            }
        }
        this.propertyValueList.add(pv);
        return this;
    }

    //判断是否有指定name属性值的对象
    public boolean contains(String propertyName){
        return getPropertyValue(propertyName) != null;
    }

    @Override
    public Iterator<PropertyValue> iterator() {
        return propertyValueList.iterator();
    }
}
5.2.3 BeanDefinition类

BeanDefinition类用来封装bean信息,主要包含id(即bean对象的名称)、class(需要spring管理类的全类名)及子标签property数据。

package com.sxsl.framework.beans.factory;
/**
 * 用来封装bean标签对象
 * id
 * class
 * propertyvalue子标签
 */
public class BeanDefinition {
    private String id;
    private String className;
    private MutablePropertyValues propertyValues;

    public BeanDefinition() {
        propertyValues = new MutablePropertyValues();
    }

    public String getId() {
        return id;
    }

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

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public MutablePropertyValues getPropertyValues() {
        return propertyValues;
    }

    public void setPropertyValues(MutablePropertyValues propertyValues) {
        this.propertyValues = propertyValues;
    }
}

5.3 定义注册表相关类

5.3.1 BeanDefinitionRegistry接口

BeanDefinitionRegistry接口定义了注册表的相关操作,第几个已如下:
注册BeanDefinition对象到注册表中;
从注册表中删除指定名称的BeanDefinition对象;
根据名称冲注册表中获取BeanDefinition对象;
判断注册表中是否包含BeanDefinition对象;
获取BeanDefinition对象的个数;
获取注册表中所有的BeanDefinition名称。

package com.sxsl.framework.beans.factory.support;

import com.sxsl.framework.beans.factory.BeanDefinition;

//注册表相关操作
public interface BeanDefinitionRegistry {
    //注册BeanDefinition对象到注册表
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) ;
    //从注册表中删除指定名称的BeanDefinition对象
    void removeBeanDefinition(String beanName) throws Exception;
    //根据名称从注册表中获取BeanDefinition对象
    BeanDefinition getBeanDefinition(String beanName) throws Exception;
    boolean containBeanDefinition(String beanName);
    int getBeanDefinitionCount();
    String[] getBeanDefinitionNames();
}
5.3.2 SimpleBeanDefinitionRegistry类

该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器

package com.sxsl.framework.beans.factory.support;

import com.sxsl.framework.beans.factory.BeanDefinition;

import java.util.HashMap;
import java.util.Map;

//注册表接口子实现类
public class SimpleBeanDefinitionRegistry implements  BeanDefinitionRegistry{
    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String,BeanDefinition>();

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName,beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws Exception {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
        return beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

5.4 定义解释器相关类

5.4.1 BeanDefinitionReader接口

BeanDefinitionReader接口是用来解析配置文件并在注册表中注册bean的信息,定义了两个规范:
获取注册表的功能,让外界可以通过该对象获取注册表对象;
加载配置文件,并注册bean数据。

package com.sxsl.framework.beans.factory.support;
//用来解析配置文件的接口,该接口只是定义了规范
public interface BeanDefinitionReader {
    //获取注册表对象
    BeanDefinitionRegistry getRegistry();
    //加载配置文件并在注册表中进行注册
    void loadBeanDefinitions(String configLocation) throws Exception;
}
5.4.2 XmlBeanDefinitionReader类

XmlBeanDefinitionReader类是专门用来解析Xml配置文件,该类实现了BeanDefinitionReader接口中的两个功能。

package com.sxsl.framework.beans.factory.xml;

import com.sxsl.framework.beans.factory.BeanDefinition;
import com.sxsl.framework.beans.factory.MutablePropertyValues;
import com.sxsl.framework.beans.factory.PropertyValue;
import com.sxsl.framework.beans.factory.support.BeanDefinitionReader;
import com.sxsl.framework.beans.factory.support.BeanDefinitionRegistry;
import com.sxsl.framework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

//针对xml配置文件进行解析的类
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
    //声明注册表对象
    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader() {
        registry = new SimpleBeanDefinitionRegistry();
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public void loadBeanDefinitions(String configLocation) throws Exception {
        //使用dom4j对xml文件进行解析
        SAXReader reader = new SAXReader();
        //获取类路径下的配置文件
        InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
        //需要一个输入流
        Document document = reader.read(is);
        //根据Document对象获取根标签对象(beans)
        Element rootElement = document.getRootElement();
        //获取根标签下所有的bean标签对象
        List<Element> beanElements = rootElement.elements("bean");
        //遍历集合
        for (Element beanElement : beanElements) {
            //获取id属性
            String id = beanElement.attributeValue("id");
            //获取class属性
            String className = beanElement.attributeValue("class");
            //经id和class属性封装到BeanDefinition对象中
            //1、创建BeanDefinition对象
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setId(id);
            beanDefinition.setClassName(className);

            //2、解析MutablePropertyValues
            //2.1 先生成MutablePropertyValues对象
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
            //2.2 获取bean标签下所有的property标签属性
            List<Element> propertyElements = beanElement.elements("property");
            //遍历所有属性
            for (Element propertyElement : propertyElements) {
                String name = propertyElement.attributeValue("name");
                String ref = propertyElement.attributeValue("ref");
                String value = propertyElement.attributeValue("value");
                //调用PropertyValue的有参构造,生成一个PropertyValue对象
                PropertyValue propertyValue = new PropertyValue(name,ref,value);
                //将生成的PropertyValue对象添加到MutablePropertyValues对象中
                mutablePropertyValues.addPropertyValue(propertyValue);
            }
            //2.3将解析MutablePropertyValues对象封装到BeanDefinition对象中
            beanDefinition.setPropertyValues(mutablePropertyValues);

            //3、将BeanDefinition对象注册到注册表中
            registry.registerBeanDefinition(id,beanDefinition);
        }

    }
}

5.5 容器相关的类

5.5.1 BeanFactory接口

在该接口中定义IOC容器的统一规范即获取bean对象。

package com.sxsl.framework.beans.factory;
//IOC容器父接口
public interface BeanFactory {
    //根据bean对象的名称,获取bean方法
    Object getBean(String name) throws Exception;
    //根据bean对象名称、以及对象CLass获取bean
    <T> T getBean(String name ,Class <? extends T> clazz) throws Exception;
}
5.5.2 ApplicationContext接口

该接口的所有子实现类对bean对象的创建都是非延时的,所以在该接口中定义了refresh()方法,该方法主要完成一些两个功能:
加载配置文件;
根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建。

package com.sxsl.framework.context;

import com.sxsl.framework.beans.factory.BeanFactory;

//定义非延时加载功能
public interface ApplicationContext extends BeanFactory {

    void refresh() throws Exception;
}
5.5.3 AbstractApplicationContext类

作为 ApplicationContext接口的子类,故该类也是非延时加载,需要在该类中定义一个Map集合,作为bean对象存储的容器。
声明BeanDefinitionReader类型的变量,用来进行XML配置文件的解析,符合单一职责原则。
BeanDefinitionReader类型对象的创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader的哪几个实现对象。

package com.sxsl.framework.context.support;

import com.sxsl.framework.beans.factory.BeanFactory;
import com.sxsl.framework.beans.factory.support.BeanDefinitionReader;
import com.sxsl.framework.beans.factory.support.BeanDefinitionRegistry;
import com.sxsl.framework.context.ApplicationContext;

import java.util.HashMap;
import java.util.Map;

//applicationContext接口的子实现类,用于立即加载
public abstract class AbstractApplicationContext implements ApplicationContext {
    //声明一个解释器
    protected BeanDefinitionReader beanDefinitionReader;
    //定义一个存储Bean对象的rq
    protected Map<String,Object> singletonObjects = new HashMap<String,Object>();
    //定义存储配置文件的路径
    protected String configLocation;

    @Override
    public void refresh() throws Exception {
        //加载BeanDefinition对象
        beanDefinitionReader.loadBeanDefinitions(configLocation);
        //初始化bean
        finishBeanInitialization();
    }
    private void finishBeanInitialization() throws Exception {
        //获取注册表对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        //获取BeanDefinition对象
        String[] beanNames = registry.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            //进行bean初始化
            getBean(beanName);
        }
    }
}
5.5.4 ClassPathXMLApplicationContext类

该类主要加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:
在构造方法中,创建BeanDefinitionReader对象;
在构造方法中,调用refresh()方法,用于进行配置文件加载,创建bean对象并存储到容器中;
重写父接口中的getBean方法,并实现依赖注入。

package com.sxsl.framework.context.support;

import com.sxsl.framework.beans.factory.BeanDefinition;
import com.sxsl.framework.beans.factory.MutablePropertyValues;
import com.sxsl.framework.beans.factory.PropertyValue;
import com.sxsl.framework.beans.factory.support.BeanDefinitionRegistry;
import com.sxsl.framework.beans.factory.xml.XmlBeanDefinitionReader;
import com.sxsl.framework.utils.StringUtils;

import java.lang.reflect.Method;

//IOC容器具体的子实现类,用于加载类路径下Xml格式的配置文件
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
    //有参构造,将配置文件传进去
    public ClassPathXmlApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        beanDefinitionReader = new XmlBeanDefinitionReader();
        try{
            this.refresh();
        } catch (Exception e){ }
    }

    @Override
    //根据bean对象的名称获取bean对象
    public Object getBean(String name) throws Exception {
        //判断对象容器是否包含指定名称的bean对象,如果包含则直接返回,如果不包含,则需要自行创建
        Object obj = singletonObjects.get(name);
        if(obj != null){
            return obj;
        }
        // 获取BeanDefinition对象
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
        //获取bean信息中中的classname
        String className = beanDefinition.getClassName();
        //通过反射创建对象
        Class<?> clazz = Class.forName(className);
        Object beanObj = clazz.newInstance();

        //进行依赖注入操作
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        for (PropertyValue propertyValue : propertyValues) {
            //获取name属性值
            String propertyName = propertyValue.getName();
            //获取value值
            String value = propertyValue.getValue();
            //获取ref值
            String ref = propertyValue.getRef();
            //处理ref不为空的场景
            if(ref != null && !"".equals(ref)){
                //获取依赖的bean对象
                Object bean = getBean(ref);
                //拼接方法名
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                //获取所有的方法对象
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if(methodName.equals(method.getName())){
                        //执行该setter方法
                        method.invoke(beanObj,bean);
                    }
                }
            }

            //处理value不为空的场景
            if(value != null && !"".equals(value)){
                //拼接方法名
                String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
                //获取method对象
                Method method = clazz.getMethod(methodName, String.class);
                method.invoke(beanObj,value);
            }
        }

        //在返回beanObj对象前,将对象存储到map容器中
        singletonObjects.put(name,beanObj);
        return beanObj;
    }

    @Override
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
        Object bean = getBean(name);
        if(bean == null){
            return null;
        }
        //类型强制转换将Object对象强制转换成clazz对应的对象类型
        return clazz.cast(bean);
    }
}

5.6 测试

5.6.1 生成相关的包

在这里插入图片描述

5.6.2 在maven项目中引入该依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>lwp_spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
<!--        <dependency>-->
<!--            <groupId>org.springframework</groupId>-->
<!--            <artifactId>spring-context</artifactId>-->
<!--            <version>5.2.0.RELEASE</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>lwp_spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>
5.6.3 创建自己的项目

(1)项目架构
在这里插入图片描述
(2)Dao层

package com.lwp.spring.dao;

public interface UserDao {
    public void add();
}

public class UserDaoImpl implements UserDao {
    private String name;
    private String password;

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

    public void setPassword(String password) {
        this.password = password;
    }

    public UserDaoImpl() {
        System.out.println("UserDao被创建了");
    }

    @Override
    public void add() {
        System.out.println("UserDao...."+name +"=" +password);
    }
}

(3)Service层

package com.lwp.spring.service;

public interface UserService {
    public void add();
}

package com.lwp.spring.service.impl;

import com.lwp.spring.dao.UserDao;
import com.lwp.spring.service.UserService;

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public UserServiceImpl() {
        System.out.println("Userservice被创建了");
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void add() {
        userDao.add();
        System.out.println("UserService...");
    }
}

(4)controller层

package com.lwp.spring.controller;

import com.lwp.spring.service.UserService;
import com.sxsl.framework.context.ApplicationContext;
import com.sxsl.framework.context.support.ClassPathXmlApplicationContext;

public class UserController {
    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");

       // BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.add();
    }
}

(5)applicationContext.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans>

    <bean id="userDao" class="com.lwp.spring.dao.impl.UserDaoImpl">
        <property name="name" value="李四"></property>
        <property name="password" value="123456"></property>
    </bean>

    <bean id="userService" class="com.lwp.spring.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>
</beans>

5.7 自定义spring IOC总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值