目录

一、设计模式是什么

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

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

设计模式分类

1.创建型模式
用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。主要有单例、原型、工厂方法、抽象工厂、创建者等5中创建型模式。
2.结构型模式
用于描述如何将类或对象按某种布局组成更大的结构,主要有代理、适配器、桥接、装饰、外观、享元、组合等7中结构模式。
3.行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。主要有模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等11中行为型模式。

二、UML

类图表示法

类的表示方法

在这里插入图片描述

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

1、单向关联在这里插入图片描述
2、双向关联
在这里插入图片描述
3、自关联
在这里插入图片描述

聚合关系

在这里插入图片描述
“单体”可以脱离“整体”,继续完成自身的功能

组合关系

在这里插入图片描述
“整体” 不存在 “单体” 也无法存在。

依赖关系

在这里插入图片描述

继承关系

在这里插入图片描述

实现关系

在这里插入图片描述

三、软件设计原则

1、开闭原则

对扩展开放,对修改关闭
在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们需要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,但软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以。
事例:
在这里插入图片描述
该“皮肤功能”,通过“AbstractSkin”抽象皮肤的功能,当有新皮肤加入时只需要重新创建一个继承“AbstractSkin”的类就可以,不需要修改以往的代码以及架构。

2、里氏代换原则

里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别时运用多态比较频繁时,程序运行出错的概率会非常大。
在这里插入图片描述
在这里插入图片描述
实例描述:可以理解为一个基类/父类的子类由于重写了基类/父类中的方法,导致原来实现的功能模块出现异常。因此尽量不要重写基类/父类的方法。

3、依赖倒转原则

高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节(实现类/子类),细节(实现类/子类)应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。(依赖倒转原则其实也是开闭原则的具体实现)
在这里插入图片描述

例子:电脑与CPU、硬盘、内存条

在这里插入图片描述
如果想要更换CPU或者是其他硬件,我们就需要修改Computer里的代码,因为上图中Computer的成员变量是IntelCpu只能使用英特尔的CPU,违背了开闭原则。

解决:对抽象进行编程

在这里插入图片描述

4、接口隔离原则

客户端不应该被迫依赖与它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。
在这里插入图片描述

例子:安全门的实现

在这里插入图片描述
如果当我们只想做有防火、防盗的新产品时,因为规范接口里面同时有防火、防盗和防水的功能,所有就会导致我必须实现防水的功能(方法)。

解决:依赖应该建立在最小的接口

在这里插入图片描述

5、迪米特法则

迪米特法则又叫最少知道原则。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

例子:

在这里插入图片描述
“明星”、“粉丝”、“媒体公司”三者都能单独运作,当三者因为需求需要合作就需要“经纪人”来作为中间人来负责处理需要的事务,“明星”、“粉丝”、“媒体公司”就根据经济人的需求做自己能做的就可以,不需要具体了解其他人的详细工作流程。

6、合成复合原则

合成复用原则是指:尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
1.继承复用破坏了类的封装性。因为继承会间父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
2.子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
3.它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时以及定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以间已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已又对象的功能,它有以下优点:
1.它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
2.对象间的耦合度低。可以在类的成员位置声明抽象。
3.复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

例子:汽车

在这里插入图片描述
如上图:是一个汽车的类结构图,每一个汽车动力源需要一个子类实现,没有一个动力源子类有需要不同的颜色子类来实现才能满足需求。

当添加新的动力源子类:光能汽车
在这里插入图片描述
根据需求我们最少需要添加3个子类才能完成。
在这里插入图片描述
聚合复用可以看到实现总需要的5个类1个接口。

当添加新的动力源子类:光能汽车
在这里插入图片描述
可以看到,当添加新的动力源类型时,在聚合复用只需要添加1个新类即可实现。
所以尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现

四、创建者模式

1、单例设计模式

单例模式是JAVA中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的结构

单例模式的主要有以下角色:

  1. 单例类,只能创建一个实例的类
  2. 访问类,使用单例类
单例模式的实现

单例设计模式分为两种:
饿汉式:类加载就会导致该单例实例对象被创建
懒汉式:类加载不会导致该单例实例对象被创建,而是首次使用该对象时才会创建

饿汉式 - 01 - 静态成员变量
在这里插入图片描述
饿汉式 - 02 - 静态代码块
在这里插入图片描述
对象的创建时在静态代码中,也就是跟随类加载而创建。所以饿汉式的方式创建,当代码逻辑中没有使用到该单例,该单例对象也会一直在内存中占用空间,会导致内存浪费。

饿汉式创建优缺点

  • 优点:创建单例线程安全,因为在类加载中创建不存在线程安全问题。
  • 缺点:内存浪费。

饿汉式 - 03 - 枚举方式
在这里插入图片描述
枚举类实现单例模式时极力推荐的单例实现模式,因为枚举类型时线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型时所用单例实现中唯一一种不会被破坏的单例实现模式。

懒汉式 - 01
在这里插入图片描述
“getInstance”需要添加synchronized来确保线程安全,不然这种实现方式在多线程的情况会导致该单例创建两个实例。

懒汉式优缺点

  • 优点:在不使用该单例的情况不会创建实例,不会占据内存空间。
  • 缺点:为了确保线程安全需要添加**synchronized*保证方法同步,会导致方法执行有线程阻塞,在多线程情况会有一定耗时。

懒汉式 - 02 - 双重检查锁方式
对于getInstance绝大部分得操作都是读操作,读操作时线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,需要调整加锁的时机。改良之后就是双重检查锁模式
在这里插入图片描述
双重检查锁其实也存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实力化对象的时候会进行优化指令重排序操作。要解决双重检查锁模式带来的问题,只需要使用volatile关键字,该关键字可以保证可见性和有序性。
在这里插入图片描述
添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

懒汉式 - 03 - 静态内部类
静态内部类单例模式中实例有内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实力化一次。并且严格保证实力化顺序。
在这里插入图片描述
静态内部类单例模式时一种优秀的单例模式,时开源项目中比较常用的一种单例模式。在灭有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

破坏单例模式

使单例类可以创建多个对象,枚举方式除外。

1、序列化反序列化
第一步:将类序列化输出
将单例类实现Serializable序列化接口
在这里插入图片描述
将单例类序列化并输出成File
在这里插入图片描述
第二步:反序列化创建对象
在这里插入图片描述
每当调用反序列化时,系统都会创建一个新的对象。这就导致单例类不再是单例了

2、反射
在这里插入图片描述
通过反射创建多次单例。

1、序列化反序列化 - 解决办法
在单例类中添加**readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的指,如果没有定义,则返回新new出来的对象。
在这里插入图片描述
可以看
ObjectInputStream#readObject()**方法
在这里插入图片描述

2、反射 - 解决办法
在这里插入图片描述
不然该单例类重复调用构造方法。

JDK源码中的单例模式

Runtime

2、工厂模式

需求:设计一个咖啡店点餐系统。
在这里插入图片描述
核心类CoffeeStore具体实现
在这里插入图片描述
假如我们需要更换对象,所有的咖啡对象new的地方都需要修改,这违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,咖啡店彻底和对象解耦。所以说工厂模式最大的优点就是:解耦

简单工厂模式

将 new 咖啡的逻辑解耦提取
在这里插入图片描述
这样咖啡店就与咖啡解耦了
在这里插入图片描述
简单工厂模式,优缺点:
优点:
封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工程类,而不需要在原代码中修改,这样就降低了客户段代码修改的可能性,更加容易扩展。
缺点:
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

静态工厂
在这里插入图片描述

工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

概念
定义一个用于创建对象的接口,让子类决定实力化那个产品类对象,工厂方法使一个产品类的实例化延迟到其工厂的子类。

结构

  • 抽象工厂:提供了产品的接口,调用者通过它访问具体工厂的工厂方法类创建产品。
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,有具体工厂来成员将,它同具体工厂之间一一对应。

实现
在这里插入图片描述
咖啡店使用抽象工厂,这样在新增咖啡时就可以通过新增工厂咖啡来实现,旧的代码类不需要修改,满足开闭原则

工厂方法优缺点:
优点:
1.用户只需要知道具体工厂的名称就可得到所需要的产品,无须知道产品的具体创建过程;
2.在系统增加新的产品时只需要添加具体产品类和队友的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。

缺点:
1.每增加一个产品就要增加一个具体产品类和一个对于的具体工厂类,这增加了系统的复杂度。

抽象工厂模式

概念
是一种为访问类提供一个创建一组相关相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族不同等级的产品模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

结构

  • 抽象工厂:提供创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品:实现了抽象产品角色所定义的接口,有具体工厂来创建,它同具体工厂之间是多对一的关系。

实现
在这里插入图片描述
由图可以看到,通过抽象工厂模式,可以实现生产同类型,不同族的产品,也就是说如果有新的咖啡新增这个咖啡必须是新的族,也就需要添加新的咖啡类和新的工厂类,之后如果新增了与新咖啡相同族新的提拉米苏就不需要新增工厂

所以在抽象工厂模式,每一个新增的同类型产品,与工厂方法模式一样需要新增新产品类以及新工厂类,只是优化了新增不同产品的情况。

抽象工厂模式,优缺点

优点:
当**一个产品族(咖啡同级)**中的多个对象被设计一起工作时,它能保证客户端始终只使用同一个产品族中的工厂对象。

缺点:
产品族(咖啡同级)中需要新增一个新的产品时,所有的工厂类都需要进行修改(新增新产品的生产方法)。

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

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

第一步:定义配置文件
在这里插入图片描述
第二步:改进工厂类
通过读取配置文件,以及反射来创建产品对象
在这里插入图片描述
创建对象时,通过传入配置文件中的产品名称来返回对应的对象
在这里插入图片描述
该例子可以看出,当有新增产品的时候就不需要修改工厂类和产品类(不需要修改原来的代码),只需要新增一个产品类并且将新增产品添加到配置文件中就可以。实现了工厂类与产品类的解耦

JDK源码中的工厂模式

Collection.iterator

3、原型模式

概述

用一个以及创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

结构

原型模式包含如下角色:

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

在这里插入图片描述

实现

原型模式的克隆分为浅克隆深克隆

**浅克隆:**创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

**深克隆:**创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Realizetype对象实现Cloneable,并且重写clone()方法。
在这里插入图片描述
这个原型复制是
浅克隆
,因为创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。所有两个对象是不同的,但是里面的非基础类型属性的内存地址是相同的
在这里插入图片描述

案例

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

使用场景
  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。(如创建一个能克隆的对象,之后还需要该对象就直接复制就不需要new了)。

疑问:new与clone有什么区别?

深克隆

以上说的实现与案例都是浅克隆,如果想实现深克隆(也就是两个对象内的属性都各自有各自的内存空间,不相互影响),可以通过序列化与反序列来实现。
在这里插入图片描述

4、创建者模式

概述

将一个复杂对象的构建与表示分离,使得同样的构建过程(创建步骤)可以创建不同的表示(不同的实例对象)。

结构

建造者模式包含如下角色:

  • 抽象建造者类:这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类:实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类:要创建的复杂对象。
  • 指挥者:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

在这里插入图片描述

实例

在这里插入图片描述
在这里插入图片描述
声明产品类时就new了产品createBike多次调用返回都是同一个类。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在主程序,构建产品时,主程序并不需要知道具体的产品构建过程。

优缺点

在这里插入图片描述

使用场景
  • 创建对象较为复杂的时候,各各**部件(类成员)**创建复杂变化多样,但是构建顺序过程是不变的。
  • 创建复杂对象的过程与其组件(类成员)相互独立
模式扩展

在一些对象构建需要大量的属性传入时。

在这里插入图片描述

当我们去实例化这个类时,代码可读性会很低,代码写起来也很冗长。

在这里插入图片描述
改造Phone类

public class Phone{
	private String cpu;
	private String screen;
	private String memory;
	private String mainboard;
	
	// 私有了Phone构造方法。
	private Phone(Builder builder){
		this.cpu = builder.cpu;
		this.screen= builder.screen;
		this.memory= builder.memory;
		this.mainboard= builder.mainboard;
	}

	//set 与 get方法这里就省略了
	
	public static final class Builder{
		private String cpu;
		private String screen;
		private String memory;
		private String mainboard;
		
		public Builder cpu(String cpu){
			this.cpu = cpu;
		}
		public Builder screen(String screen){
			this.screen= screen;
		}
		public Builder memory(String memory){
			this.memory= memory;
		}
		public Builder mainboard(String mainboard){
			this.mainboard= mainboard;
		}
		
		// 同内部类去访问Phone的构造方法,并返回给调用者
		public Phone build(){
			return new Phone(this)
		}
	}
}

改造完了之后,使用起来。

在这里插入图片描述
可以看到代码一目了然,并且用链式结构编码,后续有新增成员变量也方便修改。

五、结构型模式

1、代理模式

概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
JAVA中的代理安装代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在JAVA运行时动态生成,动态代理又JDK代理和CGLIB代理两种。

结构

代理模式分为三种角色:

  • 抽象主题类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题类: 实现了抽象主题中的具体业务,时代理对象所代表的真实对象,时最终要引用的对象。
  • 代理类: 提供了真实主题相同的接口,其内部含有对真实主题的引用,它可以访问,控制或扩展真实主题的功能。
静态代理

静态代理代理类在编译期就生成
在这里插入图片描述
在这里插入图片描述
代理类真实主题类都要实现抽象主题类

动态代理

动态代理代理类则是在JAVA运行时动态生成
有点像匿名内部类,动态代理的代理类具体的java文件。
在这里插入图片描述

cglib代理

在这里插入图片描述
真实主题类没有抽象主题接口时可以通过cglib来实现代理。
在这里插入图片描述
在这里插入图片描述

三种代理对比

在这里插入图片描述

优缺点

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
  • 代理对象可以扩展目标对象的功能。
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

缺点

  • 增加了系统的复杂度
使用场景

在这里插入图片描述

2、适配器模式

概述

简单理解为转接口,如将一个Type-C的USB数据线,通过转接口转换为苹果设备的数据接口

定义:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所有引用相对较少。

结构

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

类适配器模式 - 1

实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中以及存在的组件。
在这里插入图片描述
主程序,读取SD卡与TF卡中的数据
在这里插入图片描述
在这里插入图片描述
1、通过继承适配者来调用适配者的方法。
2、通过实现目标接口来让适配器类可以当作目标实例来使用。

类适配器模式问题

1、类适配器模式违背了合成复用原则。少用继承多用组合与聚合
2、类适配器是客户类有一个接口规范的情况下可用,反之不可用。没有目标的接口,就没有办法实现

对象适配器模式 - 2

在这里插入图片描述

满足合成复用原则,少用继承多用组合于聚合

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

通过聚合降低了适配器类适配者类之间的耦合度。

接口适配器模式 - 3

不希望实现一个接口中所有的方法时,可以创建一个抽象类,实现所有方法,而此时我们只需要继承该抽象类,重写我们只需要的方法即可。

应用场景
  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
适配器模式在JDK源码中的运用

IO流中的使用,该图中有两对适配者模式。
在这里插入图片描述

3、装饰者模式

概述

例子:
快餐点有炒面、炒饭。可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那计算总价就会比较麻烦。
在这里插入图片描述
假设,追加配料“培根”,需要添加两个新类
在这里插入图片描述
假设,在添加一个新品“炒河粉”,需要新增四个类。
在这里插入图片描述
如果使用继承的方式,扩展性不好(新增一个配料需要修改之前的代码),导致类爆炸(在配料太多的情况新增产品需要创建很多的子类)。

定义

指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(新增额外功能)的模式。

结构
  • 抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件角色:实现抽象构件,通过装饰角色为器添加新功能。
  • 抽象装饰角色:继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰角色:实现抽象装饰的相关方法,并给具体构件对象添加附加功能。
案例

还是上的快餐案例
在这里插入图片描述

装饰者类
在这里插入图片描述

具体装饰类 - 鸡蛋
在这里插入图片描述

主程序的使用
在这里插入图片描述

输出结果:
在这里插入图片描述

优缺点

在这里插入图片描述

使用场景
  • 不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
    不能采用继承的情况主要有两种:
    1. 系统中安存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目爆炸增长。
    2. 以你为类定义不能继承,如 final 类。
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  • 当对象在功能要求可以动态地添加,也可以再动态地撤销时。
装饰者模式在JDK中的使用

IO流中的保证类,BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
在这里插入图片描述

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

相同点:

  • 都要实现与目标类相同的业务接口
  • 在两个类中都要声明目标对象
  • 都可以在不修改目标类的前提下增强目标方法

不同点:

  • 目的不同,装饰者是为了增持目标对象,静态代理是为了保护和隐藏目标对象
  • 获取目标对象构建的地方不同,装饰者是由外界传递进来,可以通过构造方法传递,静态代理是在代理类内部创建,以此来隐藏目标对象。

3、桥接模式

概述

在这里插入图片描述

结构

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

案例

在这里插入图片描述

好处
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明
使用场景
  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

4、外观模式

概述

在这里插入图片描述

结构

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

案例

在这里插入图片描述

优缺点

好处:

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易
    缺点:
  • 不符合开闭原则,修改很麻烦
使用场景
  • 分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性
源码实例

tomcat - RequestFacade
在这里插入图片描述
外观模式?我怎么看着像静态代理?

5、组合模式

概述

在这里插入图片描述
定义:又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构模式,它创建了对象组的树形结构。

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

在这里插入图片描述
在这里插入图片描述
抽象根节点对象
在这里插入图片描述

树枝节点对象
继承 -> 抽象根节点对象
在这里插入图片描述

叶子节点对象
继承 -> 抽象根节点对象
在这里插入图片描述

透明组合模式总结:
在这里插入图片描述

组合模式分类 - 安全组合模式 - 2

在这里插入图片描述

优点

在这里插入图片描述

使用场景

组合模式正是因树形结构而生,所有组合模式的使用场景就是出现树形结构的地方,比如:文件目录显示,多级目录呈现等树形结构数据的操作。

6、享元模式

概述

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

结构

享元模式中存在以下两种状态:
1、内部状态,即不会随着环境的改变而改变的可共享部分。
2、外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的者两种状态,并将外部状态外部化。
享元模式的主要有以下角色:

  • 抽象享元角色:通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元角色:它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元角色:并不是所有的抽象享元类的子类都行被共享,不能被共享的子类可以设计为非共享具体享元类;当需要一个非共享具体享元类的对象可以直接实力化创建。
  • 享元工厂角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
案例实现

在这里插入图片描述

类图
在这里插入图片描述
其中的具体享元角色,可以是单例。

下图是享元工厂角色
在这里插入图片描述

优缺点 和 使用场景

1、优点:

  • 极大减少内存中相似或相同的对象数量,节约系统资源,提供系统性能
  • 享元模式中的外部状态相对独立(相对具体享元类实例相互独立),且不影响内部状态。

2、缺点:
为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

3、使用场景:

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

Integer类使用了享元模式。

六、行为型模式

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

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

1、模板方法模式

概述

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

结构

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

  • 模板方法:定义了算法的骨架,按某中顺序调用其包含的基本方法。
  • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法由可以分为三种。
    • 抽象方法:一个抽象方法由抽象类声明,由其具体子类实现。
    • 具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
    • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用与判断的逻辑方法,这类方法名一般为isXXXX,返回值类型为boolen类型。

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

案例

炒菜:
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。
在这里插入图片描述

炒菜抽象类具体实现:
在这里插入图片描述

具体的炒菜类实现:
在这里插入图片描述

优缺点

优点:

  • 提供代码复用性,将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现了反转控制,通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制,并符合”开闭原则“。

缺点:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这会导致一种反向控制结构,它提高了代码阅读的难度。
适用场景
  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变得部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类得反向控制。
JDK源码中的使用

InputStrem类
在这里插入图片描述

2、策略模式

概述

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

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

在这里插入图片描述
代码没有什么特别的,就环境类直接调用抽象策略类的接口就可以,不需要知道具体策略类的具体实现细节。

优缺点:

优点

  • 策略之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换。
  • 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则”。
  • 避免使用多重条件选择语句,充分体现面向对象设计思想。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用那个策略类。
  • 策略模式将造成很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景
  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
JDK源码的运用

Comparator在Array排序中的使用

3、命令模式

概述

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

结构
  • 抽象命令:定义命令的接口,声明执行的方法。
  • 具体命令:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者:接收者,真正执行命令的对象。任何类都可能称为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
实例

在这里插入图片描述

请求者类:
在这里插入图片描述

具体命令类:
在这里插入图片描述

抽象命令类:
就一个 execute() 抽象方法。

优缺点

优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令,命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现Undo和Redo操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 系统结构更加复杂。
使用场景
  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不能直接交互。
  • 系统需要在不同的时间指定请求,将请求排队和执行请求。
  • 系统需要支持命令的撤销操作和恢复操作。
JDK源码中的使用

Runable - 抽象命令类,run抽象方法
Thread - 请求者
Runable实例 - 具体命令类
Runable实例的执行内容 - 接收者

4、责任链模式

概述

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

结构
  • 抽象处理者:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
  • 具体处理者:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求到处理,否则将该请求转给它的后继者
  • 客户类:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
案例

在这里插入图片描述

抽象处理者
在这里插入图片描述

具体处理者 - 小组长
在这里插入图片描述

具体处理者 - 部门经理
在这里插入图片描述

具体处理者 - 总经理
在这里插入图片描述

优缺点

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

JDK源码实现

FileterChain类,网络请求过滤器

5、状态模式

概述

案例
在这里插入图片描述
具体实现类
在这里插入图片描述
可以看出这样的方式,在添加新的状态或者“操作”都需要对象该实现类进行大量的改动。

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

结构

环境角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
具体状态角色:实现抽象状态所对应的行为。

案例实现

在这里插入图片描述

环境对象
在这里插入图片描述

开启状态 - 实现类
在这里插入图片描述

优缺点

在这里插入图片描述

使用场景

在这里插入图片描述

6、观察者模式

概述

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

结构

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

案例

在这里插入图片描述

具体主题类
在这里插入图片描述

优缺点

优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
  • 被观察者发送通知,所有注册的观察者都会收到信息

缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃。
使用场景
  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。
JDK源码中的使用

Observable类和Observer类

7、中介者模式

概述

在这里插入图片描述

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

结构

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

案例

”同事类“通过”中介者“类,实习解耦。
在这里插入图片描述

具体中介类
在这里插入图片描述
在这里插入图片描述

具体同事类 - 租房者
在这里插入图片描述

主程序
在这里插入图片描述

优缺点

在这里插入图片描述

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

8、迭代器模式

概述

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

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

在这里插入图片描述

具体聚合类
在这里插入图片描述

具体迭代器类
在这里插入图片描述

主程序
在这里插入图片描述

优缺点

优点:

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

缺点:

  • 增加了类的个数,这在一定程度上增加了系统的复杂性。
使用场景
  • 但需要为聚合对象提供多种遍历方式时。
  • 但需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
JDK中的使用

ArrayList类
在这里插入图片描述

9、访问者模式

定义
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作

结构

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

案例

在这里插入图片描述

具体元素类
在这里插入图片描述
这里直接传递具体元素类,是因为元素类作为数据,不需要做任何的逻辑处理。

具体访问者
在这里插入图片描述
为什么不只传需要数据,因为元素类做为数据,不应该做逻辑操作,所有的业务逻辑都由访问者类来处理。

对象结构类
在这里插入图片描述
对象结构类,没有什么特别的地方,就是用来管理元素和访问者的

主程序
在这里插入图片描述
目的不在于解耦“访问者”和“元素”,而是将所有的业务逻辑都放置于“访问者”方便后续的扩展

优缺点

在这里插入图片描述

使用场景
  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多中不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
扩展

1、分派
变量被声明的类型叫做变量的静态类型,有些如又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap(),map变量的静态类型是Map,实际类型是HashMap,根据对象的类型而对方进行的选择,就是分派。分派又分为两种,即静态分派和动态分派。

静态分派翻身在编译时期,分派根据静态类型信息发生,静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

2、动态分派:

public class Animal{
	public void execute(){
		System.out.println("Animal");
	}
}

public class Dog extends Animal{
	public void execute(){
		System.out.prinln("dog");
	}
}

public class Cat extends Animal{
	public void execute(){
		System.out.prinln("cat");
	}
}

public class Client{
	public static void main(String[] args){
		Animal a = new Dog();
		a.execute();
		
		Animal a1 = new Cat();
		a1.execute();
	}
}

Java编译器在编译时期并不总是知道那些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型

3、静态分派

public class Animal{
}

public class Dog extends Animal{
}

public class Cat extends Animal{
}

public class Exectue{
	public void exectue(Animal a){
		System.out.println("Animal");
	}
	public void exectue(Dog d){
		System.out.println("dog");
	}
	public void exectue(Cat c){
		System.out.println("cat");
	}
}

public class Client{
	public static void main(String[] args){
		Animal a = new Animal ();
		Animal a1 = new Dog();
		Animal a2 = new Cat();
		
		Execute exe = new Execute();
		exe.execute(a); // 输出结果 animal
		exe.execute(a1); // 输出结果 animal
		exe.execute(a2); // 输出结果 animal
	}
}

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

4、双分派

public class Animal{
	public void accept(Execute exe){
		exe.execute(this);
	}
}

public class Dog extends Animal{
	public void accept(Execute exe){
		exe.execute(this);
	}
}

public class Cat extends Animal{
	public void accept(Execute exe){
		exe.execute(this);
	}
}

public class Exectue{
	public void exectue(Animal a){
		System.out.println("Animal");
	}
	public void exectue(Dog d){
		System.out.println("dog");
	}
	public void exectue(Cat c){
		System.out.println("cat");
	}
}

public class Client{
	public static void main(String[] args){
		Animal a = new Animal ();
		Animal a1 = new Dog();
		Animal a2 = new Cat();
		
		Execute exe = new Execute();
		exe.execute(a); // 输出结果 animal
		exe.execute(a1); // 输出结果 dog
		exe.execute(a2); // 输出结果 cat
	}
}

在上面代码中,客户端将Execute对象作为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也给自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。

10、备忘录模式

概述

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

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

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

  • 管理者:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改

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

  • 窄接口:管理者对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口,这个窄接口只允许他把备忘录对象传给其他的对象

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

案例

游戏挑战Boss
游戏中的某个场景,游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

实现有两种方式:
1、白箱备忘录模式
2、黑箱备忘录模式

白箱备忘录模式
在这里插入图片描述

发起人
在这里插入图片描述

备忘录类
在这里插入图片描述

管理者类
白箱备忘录模式下,管理者是可以修改“备忘录”对象的。
在这里插入图片描述

主程序
白箱备忘录模式下,主程序也是可以修改“备忘录”对象的。
在这里插入图片描述

黑箱备忘录模式
备忘录角色对发起人对象提供一个宽接口(能访问备忘录里的数据),而为其他对象提供一个窄接口。在Java语言中,实现双重接口的版本就是将备忘录类设计成发起人类的内部成员类
在这里插入图片描述

发起人类
在这里插入图片描述

备忘录类
在这里插入图片描述

管理者类
在这里插入图片描述

优缺点

在这里插入图片描述

使用场景

在这里插入图片描述

11、解释器模式

概述

定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

文法(语法)规则:
在这里插入图片描述
一种自定义的语法格式

抽象语法树:
在这里插入图片描述
从树最底层逐一往上运算

结构

**抽象表达式:**定义解释器的解释操作,主要包含解释器方法。
**终结符表达式:**是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每个终结符都有一个具体终结表达式与之相对应。
**非终结符表达式:**也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
**环境:**通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
**客户端:**主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

案例

在这里插入图片描述

抽象表达式类
在这里插入图片描述

终结表达式 - 变量类
在这里插入图片描述

环境类
在这里插入图片描述

非终结表达式 - 加法
在这里插入图片描述
减法表达式就由 + 变 -,不在此展示了。

主程序
在这里插入图片描述

优缺点

在这里插入图片描述

使用场景

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐