设计模式
简介
设计模式是解决软件设计中常见问题的可重用解决方案模板,通过总结反复验证的代码设计经验,提升代码的可重用性、可维护性和扩展性。其核心是通过标准化的设计思想,让代码更易理解、协作更高效,并在面向对象编程中协调类与对象的交互关系。
设计模式类型
设计模式总共有23种设计模式,这些模式总共可分为三大类:创建型模式,结构型模式,行为型模式
序号 |
模式 & 描述 |
包括 |
1 |
创建型模式 |
|
2 |
结构型模式 |
|
3 |
行为型模式 |
|
六大原则
- 开闭原则(Open Close Principle)
开闭原则,即对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简而言之,就是为了让程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,具体的设计在后面会介绍
- 里氏代换原则(Liskov Substitution Principle)
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤是抽象化,而基类和子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
- 依赖倒转原则(Dependence Inversion Principle)
依赖倒转原则主张高层模块不应依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应依赖于抽象。这个原则的核心思想是面向接口编程,即依赖于抽象而不是具体的实现
- 接口隔离原则(Interface Segregation Principle)
接口隔离原则,即使用多个隔离的接口,比使用单个接口要好,这样可以降低类之间的耦合度。由此可见,设计模式就是从大型软件架构出发,便于升级和维护的软件设计思想,他强调降低依赖,降低耦合
- 迪米特法则,即最少知道原则(Demeter Principle)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立
- 合成复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是使用继承
创建型模式
工厂模式
工厂模式提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离,因此无需指定要创建的具体类
通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码主功能直接实例化对象,这样可以提高代码的可维护性和可扩展性
结构
工厂模式主要有以下主要角色:
- 抽象产品:定义了产品的共同接口或抽象类。可以使具体产品类的父类或接口,规定了产品对象的共同方法
- 具体产品:实现了抽象产品接口,定义了具体产品的特定行为和属性
- 抽象工厂:声明了创建产品的抽象方法,可以使接口或抽象类。可以有多个方法用于创建不如同类型的产品
- 具体工厂:实现了抽象工厂接口,负责实际创建具体产品的对象
示例
将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。
FactoryPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。
抽象工厂模式
抽象工厂模式,顾名思义,实际上就是把工厂模式中的工厂抽象出来,成为了一个超级工厂,原本的工厂只能生成一个产品,但是这个超级工厂则是能生产一个工厂,然后每个工厂各司其职,生产出一系列产品。这个超级工厂称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
抽象工厂模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体实现类。通过使用抽象工厂模式,可以将客户端与具体产品的创建过程解耦,使得客户端可以通过工厂接口来创建一族产品。
结构
抽象工厂模式包含以下几个角色:
- 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
- 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
- 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
- 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
示例
我们将创建 Shape 和 Color 接口和实现这些接口的实体类。下一步是创建抽象工厂类 AbstractFactory。接着定义工厂类 ShapeFactory 和 ColorFactory,这两个工厂类都是扩展了 AbstractFactory。然后创建一个工厂创造器/生成器类 FactoryProducer。
AbstractFactoryPatternDemo 类使用 FactoryProducer 来获取 AbstractFactory 对象。它将向 AbstractFactory 传递形状信息 Shape(CIRCLE / RECTANGLE / SQUARE),以便获取它所需对象的类型。同时它还向 AbstractFactory 传递颜色信息 Color(RED / GREEN / BLUE),以便获取它所需对象的类型。
单例模式
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
为什么要实现强制唯一性:为了解决特定场景下多实例可能引发的核心矛盾。这种强制并非为了限制灵活性,而是通过约束设计来保障系统的稳定性、效率和一致性。
如何实现:通过控制实例化过程和访问权限实现的。
实现方式:
- 私有化构造函数:禁止通过
new
运算符随意创建实例,确保只能通过指定方法获取对象。 - 静态私有实例:在类内部维护一个静态私有实例,作为唯一对象。
- 全局访问点:提供静态方法(如
getInstance()
)作为唯一访问入口,控制实例的创建和返回。
结构
单例模式包含以下几个主要角色:
- 单例类:包含单例实例的类,通常将构造函数声明为私有
- 静态成员变量:用于存储单例实例的静态成员变量
- 获取实例方法:静态方法,用于获取单例实例
- 私有构造函数:防止外部直接实例化单例类
- 线程安全处理:确保在多线程环境下单例实例的创建是安全的
示例
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。
单例模式的几种实现方式
- 懒汉式,线程不安全
是否Lazy初始化:是
是否多线程安全:否
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式
这种方式lazy loading 很明显不要求线程安全,在多线程下不能正常工作
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 懒汉式,线程安全
是否Lazy初始化:是
是否多线程安全:是
描述:具备很好的lazy loading,能够在多线程中很好的工作,但是效率很低,大多情况下不需要同步
优点:第一次调用才初始化,避免内存浪费
缺点:必须加锁synchronized才能保证单例,但是加锁会影响效率
getInstance()的性能对应用程序不是很关键(该方法使用不太频繁)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 饿汉式
是否Lazy初始化:否
描述:这种方式很常用,但是容易产生垃圾对象
优点:没有加锁,执行效率会提高
缺点:类加载时就初始化,浪费内存
它基于classloader机制避免了多线程的同步问题,不过,instance在类加载时就实例化,虽然导致类加载的原因有很多种,在单例模式中大多数都是调用getInstance()方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
- 双检锁/双重校验锁(DCL,double-checked locking)
是否Lazy初始化:是
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能
DCL的关键在于使用volatile关键字,防止指令重排序,确保实例完全初始化
getInstance()的性能对应用程序很关键
注意:instance的声明有一个volatile关键字,如果不用该关键字,有可能会出现异常。因为instance = new SingleInstance(); 这并不是一个原子操作,会被编译成三条指令:
- jvm开辟一块内存S
- 在内存S上初始化实例
- 将内存S的地址赋值给instance
java会指令重排序,即jvm执行上面三条指令时,不一定是1-2-3执行,有可能是1-3-2这行。可能会出现下面这种情况:Thread1执行到3,并未将实例初始化,同时Thread2执行到第一行,发现为false,直接返回了instance实例,但是此时instance在Thread1中并未初始化,因此上层在使用时会出现问题
不过instance变量加上volatile关键字,就禁止了jvm的指令重排,在实例化对象时就会按部就班执行
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 登记式/静态内部类
是否Lazy初始化:是
描述:这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 枚举
是否Lazy初始化:否
描述:这种实现方式还没有被广泛采用,但是是实现单例模式的最佳方法。更加简洁,自动支持序列化机制,绝对防止多次实例化。
不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
不能通过reflection attack来调用私有构造方法
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
建造者模式
建造者模式是一种创建型设计模式,主要目的是将一个复杂对象的构建过程与其表示相分离,从而创建具有不同表现形式的对象。
结构
主要包含以下角色:
产品:要构建的复杂对象。产品类通产包含多个部分或属性
抽象建造者:定义了构建产品的抽象接口,包括构建产品的各个部分的方法
具体建造者:实现抽象建造者接口,具体确定如何构建产品的各个部分,并负责返回最终构建的产品
指导者:负责调用建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式
示例
我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。
我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。
然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo 类使用 MealBuilder 来创建一个 Meal。
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
结构
原型模式包含以下几个主要角色:
- 原型接口(Prototype Interface):定义一个用于克隆自身的接口,通常包括一个
clone()
方法。 - 具体原型类(Concrete Prototype):实现原型接口的具体类,负责实际的克隆操作。这个类需要实现
clone()
方法,通常使用浅拷贝或深拷贝来复制自身。 - 客户端(Client):使用原型实例来创建新的对象。客户端调用原型对象的
clone()
方法来创建新的对象,而不是直接使用构造函数。
示例
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
PrototypePatternDemo 类使用 ShapeCache 类来获取 Shape 对象。
结构型模式
适配器模式
适配器模式充当两个不兼容接口之间的桥梁,属于结构型设计模式。它通过一个中间件(适配器)将一个雷的接口转换成客户期望的另一个接口,使原本不能一起工作的类能够协同工作。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
假设有一个音频播放器,只能播放MP3文件。现在需要播放VLC和MP4文件,可以通过创建一个适配器来实现:
- 目标接口:定义一个可以播放多种格式文件的音频播放器接口
- 适配着类:现有的音频播放器,只能播放MP3文件
- 适配器类:创建一个新的类,实现目标接口,并在内部使用适配者类来播放MP3文件,同时添加对VLC和MP4文件的支持
结构
适配器模式主要包含以下角色:
- 目标接口:定义客户需要的接口
- 适配者类:定义一个已经存在的接口,这个接口需要适配
- 适配器类:实现目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现目标接口
示例
我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。
我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。
我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。
AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式。
桥接模式
桥接是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个座位桥接的接口,使得实体类的功能独立于接口实现类,这两种类型的类可被结构化改变而互不影响。
桥接模式的目的是将抽象与实现分离,使他们可以独立的变化,该模式通过将一个对象的抽象部分与它的实现部分分离,使他们可以独立的改变。他通过组合的方式,而不是继承的方式,将抽象和实现的部分连接起来。
结构
以下是桥接模式的几个角色:
- 抽象(Abstraction):定义抽象接口,通常包含对实现接口的引用
- 扩展抽象(Refined Abstraction):对抽象的扩展,可以使抽象类的子类或具体实现类
- 实现(Implementor):定义实现接口,提供基本操作的接口
- 具体实现(Concrete Implementor):实现实现接口的具体类
示例
过滤器模式
过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
结构
过滤器模式包含以下几个主要角色:
- 过滤器接口(Filter/Criteria):定义一个接口,用于筛选对象。该接口通常包含一个方法,用于根据特定条件过滤对象。
- 具体过滤器类(Concrete Filter/Concrete Criteria):实现过滤器接口,具体定义筛选对象的条件和逻辑。
- 对象集合(Items/Objects to be filtered):要被过滤的对象集合。这些对象通常是具有共同属性的实例,例如一组人、一组产品等。
- 客户端(Client):使用具体过滤器类来筛选对象集合。客户端将对象集合和过滤器结合起来,以获得符合条件的对象。
示例
组合模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构
结构
组合模式的核心角色包括:
- 组件(Component):
-
- 定义了组合中所有对象的通用接口,可以是抽象类或接口。它声明了用于访问和管理子组件的方法,包括添加、删除、获取子组件等。
- 叶子节点(Leaf):
-
- 表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但通常不包含子组件。
- 复合节点(Composite):
-
- 表示组合中的复合对象,复合节点可以包含子节点,可以是叶子节点,也可以是其他复合节点。它实现了组件接口的方法,包括管理子组件的方法。
- 客户端(Client):
-
- 通过组件接口与组合结构进行交互,客户端不需要区分叶子节点和复合节点,可以一致地对待整体和部分。
示例
我们有一个类 Employee,该类被当作组合模型类。CompositePatternDemo 类使用 Employee 类来添加部门层次结构,并打印所有员工。
装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
装饰器模式通过将对象包装在装饰器类中,以便动态地修改其行为。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
结构
装饰器模式包含以下几个核心角色:
- 抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。
- 具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。
- 抽象装饰器(Decorator):继承自抽象组件,它包含了一个抽象组件对象,并定义了与抽象组件相同的接口,同时可以通过组合方式持有其他装饰器对象。
- 具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。
装饰器模式通过嵌套包装多个装饰器对象,可以实现多层次的功能增强。每个具体装饰器类都可以选择性地增加新的功能,同时保持对象接口的一致性。
示例
外观模式
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用
结构
外观模式涉及以下核心角色:
- 外观(Facade):
-
- 提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。
- 子系统(Subsystem):
-
- 由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。
- 客户端(Client):
-
- 使用外观对象来与系统交互,而不需要了解系统内部的具体实现。
示例
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 ShapeMaker。
ShapeMaker 类使用实体类来代表用户对这些类的调用。FacadePatternDemo 类使用 ShapeMaker 类来显示结果。
享元模式
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。
结构
享元模式包含以下几个核心角色:
- 享元工厂(Flyweight Factory):
-
- 负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。
- 具体享元(Concrete Flyweight):
-
- 实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递。
- 抽象享元(Flyweight):
-
- 定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。
- 客户端(Client):
-
- 使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现。
示例
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类 Circle。下一步是定义工厂类 ShapeFactory。
ShapeFactory 有一个 Circle 的 HashMap,其中键名为 Circle 对象的颜色。无论何时接收到请求,都会创建一个特定颜色的圆。ShapeFactory 检查它的 HashMap 中的 circle 对象,如果找到 Circle 对象,则返回该对象,否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。
FlyWeightPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(red / green / blue/ black / white),以便获取它所需对象的颜色。
代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。
代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
结构
主要涉及到以下几个核心角色:
- 抽象主题(Subject):
-
- 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
- 真实主题(Real Subject):
-
- 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
- 代理(Proxy):
-
- 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
- 客户端(Client):
-
- 使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类
示例
我们将创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。
ProxyPatternDemo 类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。
行为型模式
责任链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
责任链模式通过将多个处理器(处理对象)以链式结构连接起来,使得请求沿着这条链传递,直到有一个处理器处理该请求为止。
责任链模式允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求。
结构
主要涉及到以下几个核心角色:
- 抽象处理者(Handler):
-
- 定义一个处理请求的接口,通常包含一个处理请求的方法(如
handleRequest
)和一个指向下一个处理者的引用(后继者)。
- 定义一个处理请求的接口,通常包含一个处理请求的方法(如
- 具体处理者(ConcreteHandler):
-
- 实现了抽象处理者接口,负责处理请求。如果能够处理该请求,则直接处理;否则,将请求传递给下一个处理者。
- 客户端(Client):
-
- 创建处理者对象,并将它们连接成一条责任链。通常,客户端只需要将请求发送给责任链的第一个处理者,无需关心请求的具体处理过程。
示例
我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。
命令模式
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。
命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
结构示意图:
结构
主要涉及到以下几个核心角色:
- 命令(Command):
-
- 定义了执行操作的接口,通常包含一个
execute
方法,用于调用具体的操作。
- 定义了执行操作的接口,通常包含一个
- 具体命令(ConcreteCommand):
-
- 实现了命令接口,负责执行具体的操作。它通常包含了对接收者的引用,通过调用接收者的方法来完成请求的处理。
- 接收者(Receiver):
-
- 知道如何执行与请求相关的操作,实际执行命令的对象。
- 调用者/请求者(Invoker):
-
- 发送命令的对象,它包含了一个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给命令对象来实现。
- 客户端(Client):
-
- 创建具体命令对象并设置其接收者,将命令对象交给调用者执行。
示例
我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。
Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo 类使用 Broker 类来演示命令模式。
解释器模式
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。
解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
这种模式被用在 SQL 解析、符号处理引擎等。
结构
解释器模式包含以下几个主要角色:
- 抽象表达式(Abstract Expression):定义了解释器的抽象接口,声明了解释操作的方法,通常是一个抽象类或接口。
- 终结符表达式(Terminal Expression):实现了抽象表达式接口的终结符表达式类,用于表示语言中的终结符(如变量、常量等),并实现了对应的解释操作。
- 非终结符表达式(Non-terminal Expression):实现了抽象表达式接口的非终结符表达式类,用于表示语言中的非终结符(如句子、表达式等),并实现了对应的解释操作。
- 上下文(Context):包含解释器之外的一些全局信息,在解释过程中提供给解释器使用,通常用于存储变量的值、保存解释器的状态等。
- 客户端(Client):创建并配置具体的解释器对象,并将需要解释的表达式传递给解释器进行解释。
实现
我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpression、AndExpression 用于创建组合式表达式。
InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。
迭代器模式
迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式属于行为型模式。
目的:提供一种统一的方法来遍历不同的聚合对象。
结构
迭代器模式包含以下几个主要角色:
- 迭代器接口(Iterator):定义了访问和遍历聚合对象中各个元素的方法,通常包括获取下一个元素、判断是否还有元素、获取当前位置等方法。
- 具体迭代器(Concrete Iterator):实现了迭代器接口,负责对聚合对象进行遍历和访问,同时记录遍历的当前位置。
- 聚合对象接口(Aggregate):定义了创建迭代器对象的接口,通常包括一个工厂方法用于创建迭代器对象。
- 具体聚合对象(Concrete Aggregate):实现了聚合对象接口,负责创建具体的迭代器对象,并提供需要遍历的数据。
实现
我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。
IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names。
中介者模式
中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性,属于行为型模式。
中介者模式定义了一个中介对象来封装一系列对象之间的交互。中介者使各对象之间不需要显式地相互引用,从而使其耦合松散,且可以独立地改变它们之间的交互。
结构
中介者模式包含以下几个主要角色:
- 中介者(Mediator):定义了一个接口用于与各个同事对象通信,并管理各个同事对象之间的关系。通常包括一个或多个事件处理方法,用于处理各种交互事件。
- 具体中介者(Concrete Mediator):实现了中介者接口,负责实现各个同事对象之间的通信逻辑。它会维护一个对各个同事对象的引用,并协调它们的交互。
- 同事对象(Colleague):定义了一个接口,用于与中介者进行通信。通常包括一个发送消息的方法,以及一个接收消息的方法。
- 具体同事对象(Concrete Colleague):实现了同事对象接口,是真正参与到交互中的对象。它会将自己的消息发送给中介者,由中介者转发给其他同事对象。
实现
我们通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。我们将创建两个类 ChatRoom 和 User。User 对象使用 ChatRoom 方法来分享他们的消息。
MediatorPatternDemo,我们的演示类使用 User 对象来显示他们之间的通信。
备忘录模式
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象,备忘录模式属于行为型模式。
备忘录模式允许在不破坏封装性的前提下,捕获和恢复对象的内部状态。
结构
备忘录模式包含以下几个主要角色:
- 备忘录(Memento):负责存储原发器对象的内部状态。备忘录可以保持原发器的状态的一部分或全部信息。
- 原发器(Originator):创建一个备忘录对象,并且可以使用备忘录对象恢复自身的内部状态。原发器通常会在需要保存状态的时候创建备忘录对象,并在需要恢复状态的时候使用备忘录对象。
- 负责人(Caretaker):负责保存备忘录对象,但是不对备忘录对象进行操作或检查。负责人只能将备忘录传递给其他对象。
实现
备忘录模式使用三个类 Memento、Originator 和 CareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。
MementoPatternDemo,我们的演示类使用 CareTaker 和 Originator 对象来显示对象的状态恢复。
观察者模式
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
结构
观察者模式包含以下几个核心角色:
- 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
- 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
- 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
- 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。
观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。
实现
观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。
ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。
状态模式
在状态模式(State Pattern)中,类的行为是基于它的状态改变的,这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
状态模式允许对象在内部状态改变时改变其行为,使得对象在不同的状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。
结构
状态模式包含以下几个主要角色:
- 上下文(Context):定义了客户感兴趣的接口,并维护一个当前状态对象的引用。上下文可以通过状态对象来委托处理状态相关的行为。
- 状态(State):定义了一个接口,用于封装与上下文相关的一个状态的行为。
- 具体状态(Concrete State):实现了状态接口,负责处理与该状态相关的行为。具体状态对象通常会在内部维护一个对上下文对象的引用,以便根据不同的条件切换到不同的状态。
实现
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。
StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。
空对象模式
在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。
在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。
结构
空对象模式包含以下几个主要角色:
- 抽象对象(Abstract Object):定义了客户端所期望的接口。这个接口可以是一个抽象类或接口。
- 具体对象(Concrete Object):实现了抽象对象接口的具体类。这些类提供了真实的行为。
- 空对象(Null Object):实现了抽象对象接口的空对象类。这个类提供了默认的无效行为,以便在对象不可用或不可用时使用。它可以作为具体对象的替代者,在客户端代码中代替空值检查。
实现
我们将创建一个定义操作(在这里,是客户的名称)的 AbstractCustomer 抽象类,和扩展了 AbstractCustomer 类的实体类。工厂类 CustomerFactory 基于客户传递的名字来返回 RealCustomer 或 NullCustomer 对象。
NullPatternDemo,我们的演示类使用 CustomerFactory 来演示空对象模式的用法。
策略模式
在策略模式(Strategy Pattern)中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
结构
策略模式包含以下几个核心角色:
- 环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
- 抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
- 具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。
策略模式通过将算法与使用算法的代码解耦,提供了一种动态选择不同算法的方法。客户端代码不需要知道具体的算法细节,而是通过调用环境类来使用所选择的策略。
实现
我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
结构
模板模式包含的几个主要角色
- 抽象父类(Abstract Class):
-
- 定义了模板方法和一些抽象方法或具体方法。
- 具体子类(Concrete Classes):
-
- 继承自抽象父类,并实现抽象方法。
- 钩子方法(Hook Method)(可选):
-
- 在抽象父类中定义,可以被子类重写,以影响模板方法的行为。
- 客户端(Client)(可选):
-
- 使用抽象父类和具体子类,无需关心模板方法的细节。
实现
我们将创建一个定义操作的 Game 抽象类,其中,模板方法设置为 final,这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类,它们重写了抽象类的方法。
TemplatePatternDemo,我们的演示类使用 Game 来演示模板模式的用法。
访问者模式
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
结构
访问者模式包含的几个主要角色
- 访问者(Visitor):
-
- 定义了访问元素的接口。
- 具体访问者(Concrete Visitor):
-
- 实现访问者接口,提供对每个具体元素类的访问和相应操作。
- 元素(Element):
-
- 定义了一个接受访问者的方法。
- 具体元素(Concrete Element):
-
- 实现元素接口,提供一个
accept
方法,允许访问者访问并操作。
- 实现元素接口,提供一个
- 对象结构(Object Structure)(可选):
-
- 定义了如何组装具体元素,如一个组合类。
- 客户端(Client)(可选):
-
- 使用访问者模式对对象结构进行操作。
实现
我们将创建一个定义接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是实现了 ComputerPart 接口的实体类。我们将定义另一个接口 ComputerPartVisitor,它定义了访问者类的操作。Computer 使用实体访问者来执行相应的动作。
VisitorPatternDemo,我们的演示类使用 Computer、ComputerPartVisitor 类来演示访问者模式的用法。
参考资料: