一、概念
1、什么是设计模式
设计模式是在软件设计中反复出现的问题的通用解决方案。它们是经过多次验证和应用的指导原则,旨在帮助软件开发人员解决特定类型的问题,提高代码的可维护性、可扩展性和重用性。
设计模式是一种抽象化的思维方式,可以帮助开发人员更好地组织和设计他们的代码。它们提供了一种通用的框架,可以用于解决各种不同的软件设计问题。设计模式不是完整的代码,而是一种描述问题和解决方案之间关系的模板。
设计模式并不是一成不变的法则,而是根据不同的问题和情境来决定是否使用以及如何使用。了解和应用设计模式可以帮助开发人员更好地组织代码,提高代码的可读性和可维护性,同时也有助于促进团队之间的合作和沟通。
2、设计模式的分类
创建型模式(Creational):关注对象的实例化过程,包括了如何实例化对象、隐藏对象的创建细节等。常见的创建型模式有单例模式、工厂模式、抽象工厂模式等。
结构型模式(Structural):关注对象之间的组合方式,以达到构建更大结构的目标。这些模式帮助你定义对象之间的关系,从而实现更大的结构。常见的结构型模式有适配器模式、装饰器模式、代理模式等。
行为型模式(Behavioral):关注对象之间的通信方式,以及如何合作共同完成任务。这些模式涉及到对象之间的交互、责任分配等。常见的行为型模式有观察者模式、策略模式、命令模式等。
类型 | 范围 | 名称 | 概述 |
创建型 | 类 | 工厂方法模式(Factory Method) | 定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method 使一个类的实例化延迟到其子类。 |
对象 | 抽象工厂模式(Abstract Factory) | 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。 | |
建造者模式(Builder) | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 | ||
原型模式(Prototype) | 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。 | ||
单例模式(Singleton) | 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 | ||
结构型 | 类 | 适配器模式(Adapter) | 将一个类的接口转换成客户希望的另一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 |
对象 | 适配器模式(Adapter) | 将一个类的接口转换成客户希望的另一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 | |
桥接模式(Bridge) | 将抽象部分与它的实现部分分离,使它们都可以独立地变化。 | ||
组合模式(Composite) | 将对象组合成树形结构以表示 “部分 - 整体” 的层次结构。Composite 使得客户对单个对象和复合对象的使用具有一致性。 | ||
装饰模式(Decorator) | 动态地给一个对象添加一些额外的职责。就扩展功能而言,Decorator 模式比生成子类方式更为灵活。 | ||
外观模式(Facade) | 运用共享技术有效地支持大量细粒度的对象。 | ||
享元模式(Flyweight) | 为子系统中的一组接口提供一个一致的界面,Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 | ||
对象 | 代理模式(Proxy) | 为其他对象提供一个代理以控制对这个对象的访问。 | |
行为型 | 类 | 解释器模式(Interpreter) | 给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。 |
模板方法模式(Template Method) | 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以不改变一个算法的结构可重定义该算法的某些特定步骤。 | ||
对象 | 责任链模式(Chain of Responsibility) | 为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,沿着这条链传递该请求,直到有一个对象处理它为止。 | |
命令模式(Command) | 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。 | ||
迭代器模式(Iterator) | 提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。 | ||
中介者模式(Mediator) | 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 | ||
备忘录模式(Memento) | 在不破坏封装性的前提下,捕获一个对象的内部状态。 | ||
观察者模式(Observer) | 定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。 | ||
状态模式(State) | 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。 | ||
策略模式(Strategy) | 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。 | ||
访问者模式(Visitor) | 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 |
设计模式之间的关系:
3、设计模式的六大原则
总原则:开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
1、单一职责原则
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
2、里氏替换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5、迪米特法则(最少知道原则)(Demeter Principle)
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则(Composite Reuse Principle)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。
二、Java中的23种设计模式
1、创建型模式(Creational Patterns)
创建型设计模式聚焦于对象创建过程,通过抽象对象创建逻辑,隐藏具体实现细节,让代码更具灵活性与可维护性。
1.1、单例模式(Singleton Pattern)
核心思想
确保一个类只有一个实例,并提供一个全局访问点。该模式常用于管理资源或共享状态,像数据库连接池、配置管理类、日志记录器等场景就很适用。
应用
JDK中的应用:
- java.lang.Runtime.getRuntime()
- java.util.logging.Logger
Spring 中的应用:Spring 的 Bean 默认是单例模式。可以通过 @Scope("prototype") 将其改为多例。
优点
- 控制实例数量:保证系统中该类只有一个实例存在,避免因多个实例造成的资源浪费或数据不一致问题。
- 提供全局访问点:方便在全局范围内访问该实例,使得不同部分的代码可以方便地共享和使用这个唯一实例。
一、单例模式的基本概念
1.1 应用单例模式的必要性
- 唯一实例保障:对于线程池、日志对象、配置文件加载器等资源,单个实例足以满足系统需求,多个实例可能引发资源浪费或状态混乱。
- 全局便捷访问:通过提供全局访问点,单例模式允许程序在任何位置轻松获取该实例,提升了代码的便捷性和一致性。
- 资源访问控制:在需要协调共享资源(如数据库连接)的并发访问场景中,单例模式能有效避免冲突,确保资源的合理使用。
1.2 单例模式的关键构成要素
- 私有构造方法:通过将构造方法设为私有,阻止外部使用
new
关键字直接创建实例,保证类实例的唯一性。 - 静态实例引用:在类内部维护一个静态的实例引用,指向类的唯一实例,确保实例的全局可访问性。
- 全局访问方法:通常通过一个静态方法(如
getInstance()
)对外提供获取实例的途径,方便程序在各处获取该实例。
二、单例模式的实现方式
主要通过私有化构造函数,防止外部直接实例化该类,同时提供一个静态方法来获取类的唯一实例。
2.1 饿汉式(静态常量)
public class Singleton {
// 类加载时创建实例,保证线程安全
private static final Singleton INSTANCE = new Singleton();
// 私有构造方法,防止外部实例化
private Singleton() {
// 防止通过反射调用私有构造方法创建多个实例
if (INSTANCE != null) {
throw new IllegalStateException("实例已经存在!");
}
}
// 全局访问点
public static Singleton getInstance() {
return INSTANCE;
}
}
原理:利用 Java 类加载的线程安全性,在类加载阶段就创建好单例实例。
优点:实现简单,天然线程安全,无需额外同步控制。
缺点:不支持延迟加载,即使实例可能永远不会被使用,也会在类加载时创建,造成资源浪费。
2.2 饿汉式(静态代码块)
public class Singleton {
private static final Singleton INSTANCE;
// 静态代码块,在类加载时执行
static {
INSTANCE = new Singleton();
}
private Singleton() {
if (INSTANCE != null) {
throw new IllegalStateException("实例已经存在!");
}
}
public static Singleton getInstance() {
return INSTANCE;
}
}
原理:与静态常量方式类似,通过静态代码块在类加载时创建实例。
优缺点:与静态常量方式相同,线程安全且实现简单,但同样不支持延迟加载。
2.3 懒汉式(线程不安全)
public class Singleton {
// 延迟加载实例
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
// 多线程环境下可能出现问题
instance = new Singleton();
}
return instance;
}
}
原理:在首次调用 getInstance()
方法时创建实例,实现延迟加载。
问题与风险:在多线程环境中,多个线程可能同时判断 instance == null
并创建实例,导致单例原则被破坏。
2.4 懒汉式(线程安全 —— 同步方法)
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 方法同步,保证线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
原理:通过在 getInstance()
方法上添加 synchronized
关键字,确保同一时刻只有一个线程能够进入该方法,从而保证实例的唯一性。
优点:实现简单,能有效保证线程安全。
缺点:每次调用 getInstance()
方法都需要进行同步操作,即使实例已经创建,也会带来不必要的性能开销。
2.5 双重检查锁定(Double-Check Locking)
public class Singleton {
// volatile 关键字确保多线程环境下变量的可见性和禁止指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查(有锁)
instance = new Singleton();
}
}
}
return instance;
}
}
原理:先进行一次非同步检查,只有当实例为 null
时才进入同步块;在同步块内再进行一次检查,确保只创建一次实例。volatile
关键字用于防止指令重排序带来的线程安全问题。
深入分析:第一次检查避免了每次都进入同步块,提高了性能;第二次检查则确保多线程环境下只有一个线程能创建实例。volatile
关键字保证了实例化过程中步骤的有序性,避免其他线程看到未完全初始化的对象。
优点:实现延迟加载且线程安全,同步代码块仅在首次初始化时执行,性能较高。
缺点:实现相对复杂,需要正确使用 volatile
和双重检查机制,对开发者要求较高。
2.6 静态内部类
public class Singleton {
private Singleton() {}
// 静态内部类,负责持有 Singleton 实例
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 当调用 getInstance() 时,SingletonHolder 会被加载并初始化 INSTANCE
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理:利用 JVM 的类加载机制,静态内部类仅在外部类调用 getInstance()
时才会被加载,实现延迟加载,同时 JVM 保证类加载的线程安全性。
优点:实现延迟加载,借助 JVM 机制保证线程安全,无需显式同步,代码简洁易懂。
缺点:静态内部类的机制对于初学者可能不够直观,需要对类加载原理有一定理解。
2.7 枚举实现
public enum Singleton {
INSTANCE; // 枚举中的唯一实例
// 可以添加其他方法
public void someMethod() {
// 实现具体逻辑
}
}
public class TestSingleton {
public static void main(String[] args) {
// 获取枚举单例实例
Singleton singleton = Singleton.INSTANCE;
singleton.someMethod();
}
}
原理:利用 Java 枚举的特性,保证单例的唯一性和线程安全性,同时天然防止反射攻击和反序列化重新创建实例。
优点:实现简单精炼,天然线程安全,有效防止反射和反序列化破坏单例。
缺点:不支持延迟加载(枚举常量在类加载时即被实例化),且在需要继承其他类或实现接口时存在一定局限性,语法风格可能不符合某些团队规范。
三、反射与反序列化对单例模式的影响及应对
3.1 反射攻击
即便构造方法为私有,通过反射仍可能调用构造方法创建多个实例,破坏单例模式。
应对策略:在构造方法中添加逻辑判断,若已有实例存在,则抛出异常,如饿汉式实现中的处理方式。
3.2 反序列化
当单例类实现了 Serializable
接口,反序列化时可能会创建新实例,破坏单例。
应对策略:实现 readResolve()
方法,确保反序列化时返回同一个实例;或者采用枚举方式,天然避免该问题。
四、单例模式实现方式对比及应用场景
实现方式 | 延迟加载 | 线程安全 | 实现复杂度 | 性能影响 | 反射 / 反序列化防护 |
饿汉式(静态常量 / 代码块) | 否 | 是 | 低 | 较好(无同步开销) | 需额外判断防护 |
懒汉式(同步方法) | 是 | 是 | 低 | 每次调用均有同步开销 | 需额外判断防护 |
双重检查锁定 | 是 | 是 | 中 | 初次同步,后续无同步 | 需额外判断防护 |
静态内部类 | 是 | 是 | 中 | JVM 机制,无额外同步 | 需额外判断防护 |
枚举 | 否 | 是 | 最低 | 最优(JVM 保证) | 天然防护 |
推荐使用场景:
- 双重检查锁定:适用于对延迟加载有需求且对性能要求较高的多线程环境,但需确保正确理解和使用
volatile
关键字。 - 静态内部类:简洁实现延迟加载和线程安全,适用于大多数业务场景,对代码简洁性和可读性有要求的项目。
- 枚举实现:实现单例的最简洁和安全方式,尤其适用于 JDK1.5 及以上版本,以及对防止反射和反序列化攻击有严格要求的场合。
五、总结
单例模式在 Java 开发中应用广泛,不同实现方式各有优劣。选择合适的实现方式需综合考虑实际需求,如是否需要延迟加载、对线程安全和性能的要求,以及是否需要防止反射和反序列化攻击等因素。饿汉式实现简单但不支持延迟加载;懒汉式支持延迟加载但需注意线程安全问题;双重检查锁定和静态内部类在延迟加载和线程安全方面表现较好;枚举实现则提供了最简单且安全的方式,但在扩展性上有一定限制。在实际开发中,还应充分考虑反射和反序列化等潜在风险,必要时添加防护代码,以确保单例模式的正确实现和应用。
1.2、工厂方法模式(Factory Method Pattern)
工厂模式(Factory Pattern)属于创建型设计模式,核心在于提供统一的对象创建接口,避免直接使用 new
关键字实例化对象,以此提升代码的可维护性、扩展性与灵活性。工厂模式主要分为简单工厂模式、工厂方法模式和抽象工厂模式三种类型,以下将详细介绍各模式的特点、实现与应用场景。
应用
JDK 中的应用:
- java.util.Calendar.getInstance()
- javax.xml.parsers.DocumentBuilderFactory.newInstance()
Spring 中的应用:
- BeanFactory 和 ApplicationContext 都是工厂模式的体现。
一、简单工厂模式
核心概念
简单工厂模式通过一个静态方法,依据传入参数返回不同类型的对象。尽管它严格意义上不属于标准设计模式,但却是其他工厂模式的基础。
优点
- 降低耦合:客户端无需知晓具体产品类的名称,仅需传入特定参数即可创建实例,减少了客户端与具体产品类的依赖。
缺点
- 违反开闭原则:新增产品时需修改工厂类代码,不符合 “对扩展开放,对修改关闭” 原则。
- 职责过重:所有产品的创建逻辑集中在工厂类,不利于代码维护与功能扩展。
应用场景
- 工厂类创建的对象数量较少时。
- 客户端只需传入参数即可创建对象,无需关注具体创建过程。
实现示例
// 产品接口
public interface Product {
void use();
}
// 具体产品A
public class ProductA implements Product {
@Override
public void use() {
System.out.println("Using Product A");
}
}
// 具体产品B
public class ProductB implements Product {
@Override
public void use() {
System.out.println("Using Product B");
}
}
// 简单工厂类
public class SimpleFactory {
// 静态方法,根据传入的参数创建不同的产品对象
public static Product createProduct(String type) {
switch (type) {
case "A":
return new ProductA();
case "B":
return new ProductB();
default:
throw new IllegalArgumentException("Unknown product type");
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建产品A
Product productA = SimpleFactory.createProduct("A");
productA.use();
// 创建产品B
Product productB = SimpleFactory.createProduct("B");
productB.use();
}
}
二、工厂方法模式
核心概念
定义创建对象的接口,将具体实例化操作延迟到子类中实现,即由子类决定创建哪一个类的实例。
优点
- 遵循开闭原则:新增产品时,仅需添加对应的具体产品类和具体工厂类,无需修改现有代码。
- 单一职责原则:每个具体工厂类仅负责创建特定产品,职责清晰。
缺点
- 增加系统复杂度:随着产品和工厂子类的增多,类的数量增加,系统复杂度提升。
应用场景
- 类不知道所需对象的具体类时。
- 类通过子类指定创建对象的类型时。
- 将对象创建职责委托给多个工厂子类,客户端根据具体需求选择相应工厂类。
实现示例
// 产品接口
public interface Product {
void operation();
}
// 具体产品A
public class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductA operation");
}
}
// 具体产品B
public class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductB operation");
}
}
// 工厂接口
public abstract class Creator {
public abstract Product factoryMethod();
}
// 具体工厂A
public class ConcreteCreatorA extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}
// 具体工厂B
public class ConcreteCreatorB extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreatorA();
Product product = creator.factoryMethod();
product.operation();
}
}
1.3、抽象工厂模式(Abstract Factory Pattern)
核心概念
抽象工厂模式提供了一个创建一系列相关或依赖对象的接口,而无需指定它们的具体类。这使得系统能够独立于对象的创建过程,便于管理和维护相关产品对象。
优点
- 符合开闭原则:分离了具体类的生成,新增产品系列时无需修改已有代码,增强了系统的可扩展性。
- 保证对象一致性:通过一个工厂创建多个相关的产品对象,确保了这些产品对象之间的兼容性和一致性。
缺点
增加了系统的复杂性,类的数量会大幅增加,导致系统结构变得复杂,维护难度提升。
应用场景
- 系统需要独立于其产品的创建、组合和表示时。
- 系统需要配置多个产品系列中的一个时。
- 需要强调一系列相关的产品对象一起使用时,例如跨平台 UI 组件库(如 Windows/MacOS 风格的按钮和文本框)。
与工厂方法模式的区别
工厂方法模式关注单一产品的创建,而抽象工厂模式处理的是产品族的创建。
实现示例
示例一
// 抽象产品接口
interface Color {
void fill();
}
interface Shape {
void draw();
}
// 具体产品类
class Red implements Color {
@Override
public void fill() {
System.out.println("Filling with red color");
}
}
class Green implements Color {
@Override
public void fill() {
System.out.println("Filling with green color");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
// 抽象工厂接口
interface AbstractFactory {
Color getColor(String color);
Shape getShape(String shape);
}
// 具体工厂类
class ShapeFactory implements AbstractFactory {
@Override
public Color getColor(String color) {
return null;
}
@Override
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
if ("CIRCLE".equalsIgnoreCase(shapeType)) {
return new Circle();
} else if ("RECTANGLE".equalsIgnoreCase(shapeType)) {
return new Rectangle();
}
return null;
}
}
class ColorFactory implements AbstractFactory {
@Override
public Color getColor(String color) {
if (color == null) {
return null;
}
if ("RED".equalsIgnoreCase(color)) {
return new Red();
} else if ("GREEN".equalsIgnoreCase(color)) {
return new Green();
}
return null;
}
@Override
public Shape getShape(String shape) {
return null;
}
}
// 工厂创造器
class FactoryProducer {
public static AbstractFactory getFactory(String choice) {
if ("SHAPE".equalsIgnoreCase(choice)) {
return new ShapeFactory();
} else if ("COLOR".equalsIgnoreCase(choice)) {
return new ColorFactory();
}
return null;
}
}
示例二
// 抽象产品1
public interface Product1 {
void use();
}
// 抽象产品2
public interface Product2 {
void use();
}
// 具体产品A1
public class ProductA1 implements Product1 {
@Override
public void use() {
System.out.println("Using Product A1");
}
}
// 具体产品A2
public class ProductA2 implements Product2 {
@Override
public void use() {
System.out.println("Using Product A2");
}
}
// 具体产品B1
public class ProductB1 implements Product1 {
@Override
public void use() {
System.out.println("Using Product B1");
}
}
// 具体产品B2
public class ProductB2 implements Product2 {
@Override
public void use() {
System.out.println("Using Product B2");
}
}
// 抽象工厂
public interface AbstractFactory {
Product1 createProduct1();
Product2 createProduct2();
}
// 具体工厂A
public class FactoryA implements AbstractFactory {
@Override
public Product1 createProduct1() {
return new ProductA1();
}
@Override
public Product2 createProduct2() {
return new ProductA2();
}
}
// 具体工厂B
public class FactoryB implements AbstractFactory {
@Override
public Product1 createProduct1() {
return new ProductB1();
}
@Override
public Product2 createProduct2() {
return new ProductB2();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建具体工厂A
AbstractFactory factoryA = new FactoryA();
// 通过工厂A创建产品A1和A2
Product1 productA1 = factoryA.createProduct1();
Product2 productA2 = factoryA.createProduct2();
productA1.use();
productA2.use();
// 创建具体工厂B
AbstractFactory factoryB = new FactoryB();
// 通过工厂B创建产品B1和B2
Product1 productB1 = factoryB.createProduct1();
Product2 productB2 = factoryB.createProduct2();
productB1.use();
productB2.use();
}
}
总结
工厂模式通过封装对象创建过程,为软件开发提供了灵活且可扩展的解决方案。在实际应用中,可根据具体需求选择合适的工厂模式:
- 简单工厂模式:适用于对象类型较少、创建逻辑简单的场景。
- 工厂方法模式:适用于对象类型较多且需频繁扩展,注重遵循开闭原则和单一职责原则的场景。
- 抽象工厂模式:适用于需要创建一系列相关或依赖对象,对产品对象一致性要求较高的复杂场景。
1.4、建造者模式(Builder Pattern)
建造者模式属于创建型设计模式,其核心思想是将复杂对象的构建过程分解为多个简单步骤,通过多个简单对象逐步构建出一个复杂对象。该模式在实际开发中广泛应用,例如 Java 中的 StringBuilder
用于构建字符串,Android 开发中的 AlertDialog.Builder
用于创建对话框,都是建造者模式的典型应用。
应用
JDK 中的应用:
- StringBuilder
- Stream.Builder
Spring 中的应用:
- UriComponentsBuilder 用于构建 URI。
实现方式
通过定义一个建造者接口和具体建造者类,将复杂对象的构建逻辑封装在建造者类中,实现构建过程与对象表示的分离。
建造者模式提供了一种将一个复杂对象的构建过程与其表示分离的方法。它将对象的构建过程封装在一个独立的"建造者"类中,由该类负责逐步构建对象。这样,可以根据需要创建不同的建造者来构建不同的对象变体。通常,建造者模式涉及以下角色:
- 产品(Product):表示正在构建的复杂对象。建造者模式的目标是构建这个产品。(比如要构建一个复杂的汽车模型,汽车模型就是产品 )
- 抽象建造者(Abstract Builder):定义了构建产品的步骤和方法,但没有具体的实现。不同的具体建造者可以实现不同的构建步骤,从而创建不同的产品变体。(像构建汽车模型时,抽象建造者会定义构建车身、安装车轮、配置内饰等步骤方法,但不涉及具体如何构建,只是声明)
- 具体建造者(Concrete Builder):实现了抽象建造者定义的方法,完成了产品的构建过程。每个具体建造者负责构建特定的产品变体。(例如,一个具体建造者实现的是构建普通家用轿车,另一个实现的是构建赛车,通过不同的实现来创建不同变体的产品)
- 指导者(Director):负责控制建造的过程。它通过将客户端与具体建造者分离,确保产品的构建是按照一定顺序和规则进行的。(比如在构建汽车模型时,指导者会按照先构建车身,再安装车轮,最后配置内饰这样的顺序来调用具体建造者的方法,同时也让客户端不用关心具体的构建过程和细节,只需要和指导者交互来获取最终产品)
优点
- 解耦:把复杂对象的构建过程和最终表示分离开来,使得构建过程的变化不会影响对象的表示,提高了代码的可维护性和扩展性。
- 灵活性:可以根据不同需求,使用不同的具体建造者类创建出具有不同表示的复杂对象,增强了系统的灵活性。
基础示例
// 产品类
public class Product {
private String partA;
private String partB;
// 构造函数
public Product(String partA, String partB) {
this.partA = partA;
this.partB = partB;
}
@Override
public String toString() {
return "Product [partA=" + partA + ", partB=" + partB + "]";
}
}
// 建造者接口
public interface Builder {
void buildPartA();
void buildPartB();
Product getResult();
}
// 具体建造者类
public class ConcreteBuilder implements Builder {
private String partA;
private String partB;
@Override
public void buildPartA() {
partA = "Part A";
}
@Override
public void buildPartB() {
partB = "Part B";
}
@Override
public Product getResult() {
return new Product(partA, partB);
}
}
// 指导者类
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct() {
builder.buildPartA();
builder.buildPartB();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.getResult();
System.out.println(product);
}
}
在上述代码中,Product
类是要构建的复杂对象,Builder
接口定义了构建 Product
的各个步骤方法以及获取最终产品的方法,ConcreteBuilder
是具体的建造者类,实现了接口中的方法来构建产品。Director
类作为指导者,负责安排构建步骤,客户端通过调用 Director
和 Builder
相关方法,逐步构建出复杂对象 Product
。
餐饮示例
// 产品类
class Meal {
private String burger;
private String drink;
public void setBurger(String burger) {
this.burger = burger;
}
public void setDrink(String drink) {
this.drink = drink;
}
@Override
public String toString() {
return "Burger: " + burger + ", Drink: " + drink;
}
}
// 建造者接口
interface MealBuilder {
void buildBurger();
void buildDrink();
Meal getMeal();
}
// 具体建造者类
class VegMealBuilder implements MealBuilder {
private Meal meal;
public VegMealBuilder() {
this.meal = new Meal();
}
@Override
public void buildBurger() {
meal.setBurger("Veg Burger");
}
@Override
public void buildDrink() {
meal.setDrink("Coke");
}
@Override
public Meal getMeal() {
return meal;
}
}
// 指挥者类
class MealDirector {
private MealBuilder mealBuilder;
public MealDirector(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public Meal constructMeal() {
mealBuilder.buildBurger();
mealBuilder.buildDrink();
return mealBuilder.getMeal();
}
}
在餐饮示例中,Meal
是产品类,代表一份餐食;MealBuilder
接口定义了构建餐食中汉堡和饮料的方法以及获取完整餐食的方法;VegMealBuilder
是具体建造者类,用于构建素食餐食;MealDirector
作为指挥者,控制餐食的构建流程。通过这样的结构,可以方便地扩展不同类型的餐食建造者,以创建具有不同内容的餐食对象 ,充分体现了建造者模式的灵活性和解耦优势。
1.5、原型模式(Prototype Pattern)
核心思想
原型模式的核心是通过复制现有的实例来创建新实例,而非使用构造函数。这种方式能有效避免重复初始化的开销,在某些场景下提升对象创建的效率。
实现方式
在 Java 里,实现 Cloneable
接口并重写 clone()
方法是实现原型模式的常见途径,以此来复制对象。
应用场景
原型模式适用于游戏角色复制、避免重复初始化开销等场景。在游戏开发中,当需要大量相同或相似的角色时,通过复制已有的角色实例可以快速创建新角色,避免每次都从头开始初始化;在一些需要频繁创建对象且初始化过程复杂的场景中,使用原型模式可以显著减少开销。
优点
- 减少创建新对象的开销:直接复制现有对象来创建新对象,避免了重新执行构造函数和初始化过程,提高了性能。
- 动态配置对象:能够在运行时对对象状态进行配置,通过复制对象并修改其部分属性,可以快速得到满足不同需求的新对象。
简单示例
// 原型接口
public interface Prototype extends Cloneable {
Prototype clone();
}
// 具体原型类
public class ConcretePrototype implements Prototype {
private String data;
public ConcretePrototype(String data) {
this.data = data;
}
// 使用浅拷贝
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
// 处理克隆不支持异常
return null;
}
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ConcretePrototype prototype = new ConcretePrototype("Prototype Data");
ConcretePrototype clonedPrototype = (ConcretePrototype) prototype.clone();
System.out.println(clonedPrototype.getData()); // 输出: Prototype Data
clonedPrototype.setData("Modified Data");
System.out.println(prototype.getData()); // 输出: Prototype Data
System.out.println(clonedPrototype.getData()); // 输出: Modified Data
}
}
在这个示例中,ConcretePrototype
类实现了 Prototype
接口,并重写了 clone()
方法。通过 clone()
方法复制对象,修改克隆对象的属性不会影响原对象,体现了原型模式的特点。
带有原型管理器的示例
import java.util.Hashtable;
// 抽象原型类
abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType() {
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
protected Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
// 具体原型类
class Circle extends Shape {
public Circle() {
type = "Circle";
}
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Rectangle extends Shape {
public Rectangle() {
type = "Rectangle";
}
@Override
void draw() {
System.out.println("Drawing a rectangle");
}
}
// 原型管理器
class ShapeCache {
private static Hashtable<String, Shape> shapeMap = new Hashtable<>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(), circle);
Rectangle rectangle = new Rectangle();
rectangle.setId("2");
shapeMap.put(rectangle.getId(), rectangle);
}
}
在这个示例中,Shape
是抽象原型类,Circle
和 Rectangle
是具体原型类。ShapeCache
作为原型管理器,负责存储和管理原型对象。通过 loadCache()
方法将原型对象加载到缓存中,使用 getShape()
方法从缓存中获取原型对象并进行克隆,从而快速创建新的对象。这种方式在需要管理多个原型对象时非常有用。
2、结构型模式(Structural Patterns)
结构型设计模式主要用于处理类或对象的组合,通过将类或对象组合成更大的结构,以实现更复杂的功能和灵活的系统架构。
2.1、适配器模式(Adapter Pattern)
适配器模式作为一种结构型设计模式,其核心思想是将一个类的接口转换为客户期望的另一个接口,使原本因接口不兼容而无法协作的类能够协同工作。这种模式在旧系统改造、第三方库集成等场景中尤为重要,可有效复用现有类,减少重复开发工作。
实现方式
通过引入一个适配器类,在该类中对适配者(Adaptee)的接口进行转换,使其符合目标(Target)接口的要求,从而实现不同接口之间的兼容。
原型模式的解决方案是通过复制现有对象来创建新对象,而不是从头开始构建。这允许我们以更高效的方式创建新对象,同时避免了与对象类的直接耦合。核心概念是在原型对象的基础上进行克隆,使得新对象具有与原型相同的初始状态。
在原型模式中,通常会有以下几个角色:
- 抽象原型(Prototype):声明克隆方法,作为所有具体原型的基类或接口。
- 具体原型(Concrete Prototype):实现克隆方法,从自身创建一个副本。
- 客户端(Client):使用原型对象的客户端代码,在需要新对象时通过克隆现有对象来创建新实例。
应用场景
- 旧系统改造:当旧系统中的类接口与新系统不兼容时,可使用适配器模式进行适配,避免对旧系统进行大规模重构。
- 第三方库集成:在集成第三方库时,如果库的接口与项目现有接口不匹配,可通过适配器模式实现无缝对接。
优点
- 接口兼容:有效解决接口不兼容问题,让原本无法协作的类能够相互配合。
- 复用性强:充分利用现有的类,无需重新开发,提高了代码的复用率,降低开发成本。
Java 示例
基础示例
// 目标接口
public interface Target {
void request();
}
// 适配者类
public class Adaptee {
public void specificRequest() {
System.out.println("SpecificRequest");
}
}
// 适配器类
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest(); // 将适配者的方法适配为目标接口方法
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // 通过适配器调用适配者的方法
}
}
在这个示例中,Target
是目标接口,Adaptee
是需要被适配的类,Adapter
类实现了 Target
接口,并在内部持有 Adaptee
的实例,将 Adaptee
的 specificRequest
方法适配为 Target
接口的 request
方法,使得客户端可以通过 Target
接口调用 Adaptee
的功能。
进阶示例
// 目标接口
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 适配者接口
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// 具体适配者类
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: " + fileName);
}
@Override
public void playMp4(String fileName) {}
}
class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: " + fileName);
}
}
// 适配器类
class MediaAdapter implements MediaPlayer {
private final AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if ("vlc".equalsIgnoreCase(audioType)) {
advancedMusicPlayer = new VlcPlayer();
} else if ("mp4".equalsIgnoreCase(audioType)) {
advancedMusicPlayer = new Mp4Player();
} else {
advancedMusicPlayer = null;
}
}
@Override
public void play(String audioType, String fileName) {
if (advancedMusicPlayer != null) {
if ("vlc".equalsIgnoreCase(audioType)) {
advancedMusicPlayer.playVlc(fileName);
} else if ("mp4".equalsIgnoreCase(audioType)) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
}
// 具体类
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
if ("mp3".equalsIgnoreCase(audioType)) {
System.out.println("Playing mp3 file. Name: " + fileName);
} else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media. " + audioType + " format not supported");
}
}
}
在这个进阶示例中,MediaPlayer
是目标接口,AdvancedMediaPlayer
及其实现类 VlcPlayer
、Mp4Player
是适配者。MediaAdapter
作为适配器类,将 AdvancedMediaPlayer
的特定播放方法(如 playVlc
、playMp4
)适配为 MediaPlayer
的统一 play
方法。AudioPlayer
类使用 MediaAdapter
实现对不同音频格式的播放支持,展示了适配器模式在复杂场景下如何协调不同接口的类共同完成任务。
2.2、装饰器模式(Decorator Pattern)
装饰器模式属于结构型设计模式,其核心思想是动态地为对象添加额外职责,相较于继承,它提供了更为灵活的功能扩展方式,避免因大量子类继承导致的代码臃肿和维护困难。
应用
JDK 中的应用:
- java.io.BufferedInputStream 和 java.io.BufferedOutputStream
Spring 中的应用:
- BeanPostProcessor 用于动态修改 Bean 的行为。
实现方式
通过定义装饰器类,在运行时将装饰器类与被装饰对象进行组合,从而扩展被装饰对象的功能。
装饰模式的优点包括避免了类爆炸问题,因为你可以通过组合少量的装饰类来实现各种功能组合。它也使得功能的增加和修改更加灵活,不会影响到其他部分的代码。然而,装饰模式可能会导致增加很多小型的类,从而增加了代码的复杂性。
在装饰模式中,通常涉及以下角色:
- 组件(Component):定义了一个抽象的接口,可以是具体对象或装饰器所共有的接口。
- 具体组件(Concrete Component):实现了组件接口,是被装饰的原始对象。
- 装饰器(Decorator):持有一个指向组件对象的引用,并实现了组件的接口。它可以包含额外的功能,也可以将请求传递给组件对象。
- 具体装饰器(Concrete Decorator):扩展了装饰器类,通过添加额外的功能来装饰具体组件。
通过这种方式,装饰模式允许你将功能嵌套地堆叠在一起,以实现各种不同的功能组合,同时保持代码的灵活性和可维护性。
应用场景
- Java I/O 流:典型应用如
BufferedInputStream
包装FileInputStream
,通过层层包装,在不修改原有类的基础上,为输入流添加缓冲等功能。 - 图形绘制:在绘制图形时,动态添加颜色、边框等装饰效果。
优点
- 灵活性高:能够在运行时动态选择为对象添加不同的装饰器,实现功能的灵活扩展。
- 避免子类爆炸:无需为每个功能组合创建大量子类,通过装饰器类的组合替代继承,有效减少类的数量,降低代码复杂度。
基础示例
// 组件接口
public interface Component {
void operation();
}
// 具体组件
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
// 装饰器抽象类
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰器
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
private void addedBehavior() {
System.out.println("ConcreteDecorator addedBehavior");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decorator = new ConcreteDecorator(component);
decorator.operation(); // 执行装饰后的操作
}
}
在该示例中,Component
是基础组件接口,ConcreteComponent
为具体组件实现。Decorator
作为抽象装饰器类,持有 Component
实例,并调用其方法。ConcreteDecorator
是具体装饰器,在调用被装饰对象方法后,添加额外的 addedBehavior
行为。客户端通过将具体装饰器与具体组件组合,实现功能扩展。
图形绘制示例
// 组件接口
interface Shape {
void draw();
}
// 具体组件类
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
// 抽象装饰器类
abstract class ShapeDecorator implements Shape {
protected final Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape) {
this.decoratedShape = decoratedShape;
}
@Override
public void draw() {
decoratedShape.draw();
}
}
// 具体装饰器类
class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape) {
System.out.println("Border Color: Red");
}
}
此例中,Shape
为图形绘制接口,Circle
是具体图形实现。ShapeDecorator
抽象装饰器类持有 Shape
实例,确保装饰器与被装饰对象遵循统一接口。RedShapeDecorator
具体装饰器类在绘制图形后,添加设置红色边框的功能。通过这种方式,可灵活地为不同图形添加各种装饰效果 。
2.3、代理模式(Proxy Pattern)
核心思想
代理模式的核心思想是为其他对象提供一种代理,以此来控制对该对象的访问。代理就像是真实对象的替身,客户端通过代理间接地访问真实对象,从而可以在访问过程中添加额外的逻辑或限制。
应用
JDK 中的应用:
- 动态代理 java.lang.reflect.Proxy
- RMI(远程方法调用)
Spring 中的应用:
- AOP(面向切面编程)广泛使用代理模式。
实现方式
通过定义代理类来控制对真实对象的访问。代理类通常会实现与真实对象相同的接口,这样客户端就可以像使用真实对象一样使用代理对象,而代理类内部则负责管理和控制对真实对象的调用。
应用场景
- AOP(面向切面编程)实现:在 AOP 中,代理模式被广泛应用于在方法执行前后添加额外的逻辑,如日志记录、事务管理等。通过代理类,可以在不修改原有业务逻辑的基础上,对方法进行增强。
- RPC(远程过程调用):在分布式系统中,RPC 调用时会使用代理模式。客户端通过本地的代理对象发起远程调用,代理对象负责处理网络通信、序列化和反序列化等细节,使得客户端可以像调用本地方法一样调用远程服务。
优点
- 控制访问:可以在代理类中实现对真实对象的访问控制,例如对某些敏感操作进行权限验证,只有满足条件的客户端才能访问真实对象。
- 增强功能:可以在代理类中增加额外的功能,如延迟加载。在需要使用真实对象时才进行创建和初始化,避免不必要的资源消耗。
效果
代理模式的应用可以带来多种效果:
- 远程代理(Remote Proxy): 代理对象可以隐藏原始对象存在于远程服务器上的事实,使得客户端可以透明地访问远程对象。这对于分布式系统非常有用。
- 虚拟代理(Virtual Proxy): 当创建原始对象需要大量资源时,代理对象可以充当一个轻量级的替代品,延迟原始对象的实际创建和初始化,从而提高性能。
- 保护代理(Protection Proxy): 代理对象可以控制对原始对象的访问权限,确保只有具有特定权限的客户端可以访问原始对象。
- 缓存代理(Cache Proxy): 代理对象可以缓存原始对象的结果,以便在后续相同请求时能够直接返回缓存的结果,减少重复计算。
- 日志记录代理(Logging Proxy): 代理对象可以在访问原始对象之前或之后记录日志,用于调试、监控或审计。
基础示例
// 抽象主题接口
public interface Subject {
void request();
}
// 真实主题类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject request");
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
realSubject.request(); // 代理控制对真实主题的访问
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.request(); // 通过代理访问真实主题
}
}
在这个示例中,Subject
是抽象主题接口,定义了客户端可以调用的方法。RealSubject
是真实主题类,实现了 Subject
接口的具体业务逻辑。Proxy
是代理类,同样实现了 Subject
接口,在 request
方法中,代理类会先检查真实对象是否已经创建,如果没有则创建,然后调用真实对象的 request
方法。客户端通过代理对象调用 request
方法,而不需要直接与真实对象交互。
图片加载示例
// 接口
interface Image {
void display();
}
// 真实主题类
class RealImage implements Image {
private final String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
// 代理类
class ProxyImage implements Image {
private RealImage realImage;
private final String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// 客户端代码
class ImageClient {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
// 第一次调用,会加载图片
image.display();
System.out.println("");
// 第二次调用,不会再次加载图片
image.display();
}
}
在这个示例中,Image
是接口,定义了图片显示的方法。RealImage
是真实主题类,负责从磁盘加载图片并显示。ProxyImage
是代理类,实现了延迟加载的功能。在 display
方法中,只有当真实图片对象还未创建时,才会创建并加载图片,后续再次调用 display
方法时,直接使用已经创建好的真实图片对象进行显示,避免了重复加载。客户端通过代理对象调用 display
方法,实现了对图片加载的控制和优化。
2.4、外观模式(Facade Pattern)
外观模式是一种结构型设计模式,核心思想是为子系统中的一组接口提供一个统一、简化的界面,降低子系统的使用复杂度,使子系统更易于被外部调用和理解。
实现方式
通过创建一个外观类,将子系统内部复杂的交互和实现细节进行封装,仅对外暴露简洁、统一的接口,从而隐藏子系统的复杂性,实现客户端与子系统之间的松耦合。
应用场景
- 框架与工具类:如 Spring 框架中的
JdbcUtils
,它封装了数据库连接、SQL 执行、结果集处理等复杂的数据库操作细节,开发人员只需调用其提供的简单接口,就能完成数据库相关操作。 - 复杂系统整合:当多个子系统协同工作时,外观模式可以为这些子系统提供统一接口,方便外部系统调用。
优点
- 简化使用:将复杂子系统的操作封装成简单接口,极大降低了客户端使用子系统的难度,提高开发效率。
- 解耦:外观类作为客户端和子系统之间的中间层,解耦了客户端与子系统,使得子系统内部的变化不会直接影响客户端,增强了系统的可维护性和扩展性。
示例一:基础功能封装
// 子系统类A
public class SubsystemA {
public void operationA() {
System.out.println("SubsystemA operationA");
}
}
// 子系统类B
public class SubsystemB {
public void operationB() {
System.out.println("SubsystemB operationB");
}
}
// 外观类
public class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
}
public void operation() {
subsystemA.operationA();
subsystemB.operationB();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation(); // 通过外观类调用子系统
}
}
在此示例中,SubsystemA
和 SubsystemB
是两个子系统类,各自拥有独立的操作方法。Facade
外观类将这两个子系统类实例化,并在 operation
方法中整合了它们的操作。客户端无需了解 SubsystemA
和 SubsystemB
的具体实现细节,直接调用 Facade
的 operation
方法即可完成相关操作。
示例二:图形绘制封装
// 子系统类
class Rectangle {
public void draw() {
System.out.println("Rectangle::draw()");
}
}
class Square {
public void draw() {
System.out.println("Square::draw()");
}
}
class Circle {
public void draw() {
System.out.println("Circle::draw()");
}
}
// 外观类
class ShapeMaker {
private final Circle circle;
private final Rectangle rectangle;
private final Square square;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle() {
circle.draw();
}
public void drawRectangle() {
rectangle.draw();
}
public void drawSquare() {
square.draw();
}
}
该示例里,Rectangle
、Square
和 Circle
是负责绘制不同图形的子系统类。ShapeMaker
作为外观类,实例化了这三个图形类,并针对每种图形绘制操作提供了单独的简化接口(drawCircle
、drawRectangle
、drawSquare
)。客户端在进行图形绘制时,只需调用 ShapeMaker
外观类提供的对应方法,无需关注具体图形绘制的细节,实现了对图形绘制子系统的简化使用和解耦。
2.5、桥接模式(Bridge Pattern)
1. 概述
桥接模式是一种结构型设计模式,其核心思想是将抽象部分与实现部分分离,使它们能够独立地变化。该模式通过组合关系而非继承关系来连接抽象与实现,从而提升代码的扩展性和维护性。
2. 模式结构
桥接模式主要包含以下几个部分:
- 抽象部分(Abstraction):定义抽象接口,并且持有一个指向实现部分对象的引用。
- 精确抽象部分(Refined Abstraction):对抽象部分进行扩展,添加具体的功能。
- 实现部分(Implementor):定义实现接口,给出基本操作的定义。
- 具体实现部分(Concrete Implementor):具体实现
Implementor
接口,提供具体的操作实现。
3. 模式原理
桥接模式运用组合关系替代继承关系,把抽象部分和实现部分分离开来,让它们可以独立地变化。具体表现为:
- 抽象和实现分离:引入一个实现接口,使抽象部分不直接依赖具体实现,而是依赖于接口。
- 独立变化:抽象部分和实现部分能够独立地进行变化和扩展,彼此互不影响。
- 运行时绑定:在运行时,可以动态地将抽象部分和具体实现部分组合起来。
4. UML 类图
Abstraction
+operation()
-Implementor: Implementor
RefinedAbstraction
+operation()
Implementor
+operationImpl()
ConcreteImplementorA
+operationImpl()
ConcreteImplementorB
+operationImpl()
5. 示例代码
示例一
// 实现接口
public interface Implementor {
void operation();
}
// 具体实现A
public class ConcreteImplementorA implements Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplementorA operation");
}
}
// 具体实现B
public class ConcreteImplementorB implements Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplementorB operation");
}
}
// 抽象类
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
// 扩展抽象类
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
implementor.operation(); // 委托给实现类
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Implementor implementorA = new ConcreteImplementorA();
Abstraction abstraction = new RefinedAbstraction(implementorA);
abstraction.operation();
Implementor implementorB = new ConcreteImplementorB();
abstraction = new RefinedAbstraction(implementorB);
abstraction.operation();
}
}
示例二
// 实现化角色
interface Color {
void applyColor();
}
// 具体实现化角色
class RedColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying red color");
}
}
class GreenColor implements Color {
@Override
public void applyColor() {
System.out.println("Applying green color");
}
}
// 抽象化角色
abstract class Shape {
protected final Color color;
public Shape(Color color) {
this.color = color;
}
abstract void draw();
}
// 扩展抽象化角色
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Drawing a circle. ");
color.applyColor();
}
}
class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}
@Override
void draw() {
System.out.print("Drawing a rectangle. ");
color.applyColor();
}
}
// 测试类
class BridgePatternDemo {
public static void main(String[] args) {
// 创建一个红色的圆形
Shape redCircle = new Circle(new RedColor());
// 创建一个绿色的矩形
Shape greenRectangle = new Rectangle(new GreenColor());
// 绘制红色的圆形
redCircle.draw();
// 绘制绿色的矩形
greenRectangle.draw();
}
}
6. 输出示例
Drawing a circle. Applying red color
Drawing a rectangle. Applying green color
7. 优点
- 分离抽象和实现:桥接模式将抽象和实现分离,提高了代码的扩展性和灵活性。
- 遵循开闭原则:可以独立地扩展抽象部分和实现部分,不会影响现有代码。
- 减少类的数量:通过组合而非继承,可以避免类的爆炸性增长。
8. 应用场景
- 多维度变化:当一个类存在多个独立变化的维度时,例如图形的形状和颜色,这些维度需要独立扩展和变化,可使用桥接模式将这些维度分离,使其能独立变化。比如有不同类型的图形(如圆形、矩形)和不同的颜色(如红色、绿色),将图形和颜色分离后,每增加一种图形或颜色,只需增加新的实现,无需修改现有代码。
- 运行时绑定:当需要在运行时动态地改变抽象部分和实现部分的组合关系时,可使用桥接模式。通过该模式,可以在运行时选择不同的实现,增强系统的灵活性。例如在绘图应用程序中,用户可以选择不同的绘图工具(如画笔、铅笔)和不同的绘图风格(如实线、虚线),使用桥接模式将绘图工具和绘图风格分离,用户就能在运行时动态选择不同的组合。
- 避免继承层次过多:当系统需要在多个维度上进行扩展,若使用继承会导致类的爆炸性增长,此时可使用桥接模式将这些维度分离,减少类的数量,避免继承层次过多。例如在图形库中,支持不同的图形(如圆形、矩形)和不同的绘图引擎(如 OpenGL、DirectX),使用桥接模式将图形和绘图引擎分离,每增加一种图形或绘图引擎,只需增加一个新的实现类。
9. 总结
桥接模式作为一种结构型设计模式,其目的是将抽象部分与其实现部分分离,使两者能够独立地变化。该模式通过组合关系替代继承关系,将抽象和实现解耦,让代码更具扩展性和维护性。其核心在于引入一个实现接口,使抽象部分不直接依赖具体实现,而是依赖于接口,进而实现独立扩展和运行时动态绑定的效果。
2.6、组合模式(Composite Pattern)
核心思想
组合模式的核心思想是把对象组合成树形结构,以此来表示部分 - 整体的层次结构,让客户端对单个对象和组合对象的使用具有一致性。
实现方式
通过定义一个组件接口,对叶子节点和容器节点进行统一处理。
将对象组合成树状结构,其中树的节点可以是单个对象或对象组合。这样,无论是操作单个对象还是对象组合,都可以使用统一的方式进行操作。组合模式通过定义一个共同的抽象类或接口来表示单个对象和对象组合,从而实现了透明的处理。
在组合模式中,通常有两种主要角色:
- 组件(Component): 这是一个抽象类或接口,定义了单个对象和对象组合共同的操作。它可以有一些默认实现,也可以有抽象方法需要在具体子类中实现。
- 叶子(Leaf): 继承自组件,表示单个对象。它没有子对象。
- 复合(Composite): 继承自组件,表示对象组合。它包含了一组子对象,这些子对象可以是叶子,也可以是复合。
应用场景
- 文件系统:可以对文件和文件夹进行统一处理。
- UI 容器组件:便于对 UI 中的各种组件进行统一管理和操作。
优点
- 一致性:能够对单个对象和组合对象进行一致的操作。
- 简化客户端代码:客户端代码可以统一处理叶子节点和容器节点,无需区分操作对象是单个对象还是组合对象。
Java 示例
示例一:通用的组件组合示例
import java.util.ArrayList;
import java.util.List;
// 组件接口
public interface Component {
void operation();
}
// 叶子节点
public class Leaf implements Component {
@Override
public void operation() {
System.out.println("Leaf operation");
}
}
// 容器节点
public class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
for (Component child : children) {
child.operation();
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Composite root = new Composite();
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
root.add(leaf1);
root.add(leaf2);
root.operation(); // 统一调用
}
}
在这个示例中,Component
是组件接口,Leaf
是叶子节点,Composite
是容器节点。Composite
类中包含一个 List
来存储子组件,通过 add
和 remove
方法管理子组件,operation
方法会递归调用子组件的 operation
方法。客户端代码创建了一个容器节点并添加了两个叶子节点,最后统一调用容器节点的 operation
方法。
示例二:员工组织架构示例
import java.util.ArrayList;
import java.util.List;
// 组件类
abstract class Employee {
protected String name;
protected int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public abstract void add(Employee e);
public abstract void remove(Employee e);
public abstract void print();
}
// 树叶类
class Developer extends Employee {
public Developer(String name, int salary) {
super(name, salary);
}
@Override
public void add(Employee e) {}
@Override
public void remove(Employee e) {}
@Override
public void print() {
System.out.println("Employee: [Name: " + name + ", Salary: " + salary + "]");
}
}
// 树枝类
class Manager extends Employee {
private final List<Employee> employees = new ArrayList<>();
public Manager(String name, int salary) {
super(name, salary);
}
@Override
public void add(Employee e) {
employees.add(e);
}
@Override
public void remove(Employee e) {
employees.remove(e);
}
@Override
public void print() {
System.out.println("Manager: [Name: " + name + ", Salary: " + salary + "]");
for (Employee employee : employees) {
employee.print();
}
}
}
在这个示例中,Employee
是抽象的组件类,Developer
是叶子节点,Manager
是容器节点。Manager
类中包含一个 List
来存储员工,通过 add
和 remove
方法管理员工,print
方法会打印自身信息并递归调用员工的 print
方法。这个示例模拟了一个公司的组织架构,通过组合模式可以方便地管理和展示不同层级的员工信息。
2.7、享元模式(Flyweight Pattern)
核心思想
享元模式运用共享技术,高效地支持大量细粒度的对象。它将对象的状态分为共享的内部状态(intrinsic state)和不共享的外部状态(extrinsic state),通过共享内部状态,减少对象的创建数量,从而节省内存并提高性能。
实现方式
把对象的共享部分(内部状态)和独享部分(外部状态)分开,将共享部分提取出来,通过享元工厂来管理和创建这些共享对象。当需要使用对象时,先从享元工厂中获取,如果不存在则创建并放入工厂中。
应用场景
- 棋类游戏:在棋类游戏中,棋子的颜色、形状等属性可以作为共享的内部状态,而棋子在棋盘上的位置则是外部状态。通过享元模式可以复用相同颜色和形状的棋子对象,减少内存开销。
- 字符串常量池:Java 中的字符串常量池就是享元模式的典型应用。当多个地方使用相同的字符串字面量时,会复用常量池中的同一个字符串对象,避免重复创建。
优点
- 节省内存:通过共享对象的内部状态,减少了大量重复对象的创建,从而显著节省内存空间。
- 提高性能:减少了对象创建和销毁的开销,提高了系统的性能和响应速度。
通用享元模式示例
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface Flyweight {
void operation(String extrinsicState);
}
// 具体享元类
class ConcreteFlyweight implements Flyweight {
private final String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("Intrinsic State: " + intrinsicState + ", Extrinsic State: " + extrinsicState);
}
}
// 享元工厂
class FlyweightFactory {
private final Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = flyweights.get(key);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("A");
Flyweight flyweight2 = factory.getFlyweight("B");
flyweight1.operation("1");
flyweight2.operation("2");
}
}
在这个示例中,Flyweight
是享元接口,定义了操作方法。ConcreteFlyweight
是具体享元类,包含内部状态 intrinsicState
。FlyweightFactory
是享元工厂,负责管理和创建享元对象。客户端通过工厂获取享元对象并调用其操作方法。
图形绘制享元模式示例
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface Shape {
void draw();
}
// 具体享元类
class Circle implements Shape {
private final String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ", radius :" + radius);
}
}
// 享元工厂类
class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
// 客户端代码
class ShapeClient {
public static void main(String[] args) {
Shape redCircle1 = ShapeFactory.getCircle("Red");
redCircle1.setX(10);
redCircle1.setY(20);
redCircle1.setRadius(5);
redCircle1.draw();
Shape redCircle2 = ShapeFactory.getCircle("Red");
redCircle2.setX(30);
redCircle2.setY(40);
redCircle2.setRadius(7);
redCircle2.draw();
Shape blueCircle = ShapeFactory.getCircle("Blue");
blueCircle.setX(50);
blueCircle.setY(60);
blueCircle.setRadius(9);
blueCircle.draw();
}
}
在这个示例中,Shape
是享元接口,Circle
是具体享元类,其中 color
是内部状态,x
、y
和 radius
是外部状态。ShapeFactory
是享元工厂,负责管理和创建 Circle
对象。客户端通过工厂获取不同颜色的 Circle
对象,并设置其外部状态后调用 draw
方法进行绘制。可以看到,相同颜色的 Circle
对象只会创建一次,实现了对象的共享。
3、行为型模式(Behavioral Patterns)
行为型设计模式专注于处理对象间的交互与职责分配,通过优化对象通信和协作方式,实现更灵活、易维护的软件架构。以下是 11 种常见的行为型设计模式详解:
3.1、策略模式(Strategy Pattern)
核心思想
策略模式的核心在于定义一组可相互替换的算法,将每个算法封装成独立的类,使它们能够独立于使用这些算法的客户端进行变化。通过这种方式,客户端可以根据不同的需求,在运行时动态地选择和切换具体的算法实现。
应用
JDK 中的应用:
- java.util.Comparator 是典型的策略模式。
Spring 中的应用:
- 事务管理(TransactionManager),支持编程式和声明式事务。
实现方式
定义一个策略接口,该接口声明了算法的抽象方法;创建多个具体策略类,每个类实现策略接口,封装具体的算法逻辑;同时,引入一个上下文类,该类持有一个策略对象的引用,并提供方法来设置和执行策略,从而实现客户端与具体策略的解耦。
应用场景
- 算法选择:在数据处理场景中,如排序、搜索等,可以将不同的排序算法(如冒泡排序、快速排序)或搜索算法(如线性搜索、二分搜索)封装为具体策略,根据数据规模和特点在运行时选择合适的算法。
- 游戏策略:游戏开发中,角色的攻击策略、移动策略、AI 行为等都可以通过策略模式实现,方便切换不同的游戏策略,增强游戏的多样性和可玩性。
- 数据处理:在处理数据时,对数据的加密、压缩等操作可以定义为不同的策略,根据实际需求选择对应的处理方式。
优点
- 灵活性:客户端能够在运行时动态选择和切换不同的策略,无需修改客户端代码,即可适应不同的业务需求。
- 易于扩展:新增策略时,只需创建一个新的具体策略类并实现策略接口,不会影响现有的代码结构,符合开闭原则。
通用策略示例
// 策略接口
public interface Strategy {
void execute();
}
// 具体策略A
public class ConcreteStrategyA implements Strategy {
@Override
public void execute() {
System.out.println("Executing strategy A");
}
}
// 具体策略B
public class ConcreteStrategyB implements Strategy {
@Override
public void execute() {
System.out.println("Executing strategy B");
}
}
// 上下文类
public class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setStrategy(new ConcreteStrategyA());
context.executeStrategy(); // 执行策略A
context.setStrategy(new ConcreteStrategyB());
context.executeStrategy(); // 执行策略B
}
}
在这个示例中,Strategy
接口定义了策略执行的抽象方法 execute
;ConcreteStrategyA
和 ConcreteStrategyB
是具体的策略类,分别实现了不同的策略逻辑;Context
类作为上下文,持有策略对象,并提供方法来执行策略。客户端通过设置不同的策略对象,实现了不同策略的动态切换和执行。
数学运算策略示例
// 策略接口
interface Strategy {
int doOperation(int num1, int num2);
}
// 具体策略类 - 加法
class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
// 上下文类
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
// 测试
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
}
}
在这个示例中,Strategy
接口定义了执行数学运算的抽象方法 doOperation
;OperationAdd
是具体的策略类,实现了加法运算的逻辑;Context
类作为上下文,持有策略对象,并提供方法来执行具体的运算策略。测试代码通过创建上下文对象并传入加法策略,实现了具体的加法运算,展示了策略模式在实际运算场景中的应用。
3.2、模板方法模式(Template Method Pattern)
核心思想
模板方法模式的核心在于在父类中定义一个算法的骨架(即模板方法),将算法中某些步骤的具体实现延迟到子类中。通过这种方式,子类可以在不改变整体算法结构的前提下,灵活地定制算法中特定步骤的行为,实现代码的复用与扩展。
应用
JDK 中的应用:
- java.util.AbstractList 和 java.util.AbstractMap
Spring 中的应用:
- JdbcTemplate 和 RestTemplate
实现方式
在抽象父类中定义一个或多个 final
修饰的模板方法,该方法包含了算法的固定执行流程,其中部分步骤通过抽象方法声明,由子类具体实现;子类继承抽象父类,并实现这些抽象方法,从而完成对算法特定步骤的定制。
应用场景
- Servlet 生命周期处理:在 Java Web 开发中,Servlet 的
service()
方法就是模板方法模式的典型应用。service()
方法定义了处理请求的整体流程,而具体的doGet()
、doPost()
等方法由子类实现,用于处理不同类型的 HTTP 请求。 - 游戏流程控制:游戏开发中,游戏的初始化、开始、结束等流程可以在抽象类中定义为模板方法,不同类型的游戏(如足球游戏、赛车游戏)通过继承抽象类,实现各自特定的初始化、游戏开始和结束逻辑。
优点
- 复用性:将算法中通用的逻辑和执行流程封装在父类的模板方法中,避免了子类重复编写相同代码,提高了代码的复用率。
- 灵活性:子类可以根据自身需求,重写父类的抽象方法,改变算法中特定步骤的实现方式,而无需修改算法的整体结构,增强了系统的扩展性和适应性。
基础算法示例
// 抽象类
public abstract class AbstractClass {
// 模板方法
public final void templateMethod() {
step1();
step2();
step3();
}
// 具体步骤1
private void step1() {
System.out.println("Step 1");
}
// 具体步骤2,留给子类实现
protected abstract void step2();
// 具体步骤3
private void step3() {
System.out.println("Step 3");
}
}
// 具体类A
public class ConcreteClassA extends AbstractClass {
@Override
protected void step2() {
System.out.println("ConcreteClassA Step 2");
}
}
// 具体类B
public class ConcreteClassB extends AbstractClass {
@Override
protected void step2() {
System.out.println("ConcreteClassB Step 2");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AbstractClass concreteClassA = new ConcreteClassA();
concreteClassA.templateMethod(); // 执行具体类A的模板方法
AbstractClass concreteClassB = new ConcreteClassB();
concreteClassB.templateMethod(); // 执行具体类B的模板方法
}
}
在该示例中,AbstractClass
作为抽象父类,templateMethod
是模板方法,定义了包含三个步骤的算法骨架。step1
和 step3
是已实现的具体步骤,step2
为抽象方法,需由子类实现。ConcreteClassA
和 ConcreteClassB
继承自 AbstractClass
,分别实现了 step2
方法,客户端通过调用子类实例的 templateMethod
方法,执行不同的算法步骤组合。
游戏流程示例
// 抽象类,定义模板方法和基本方法
abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
public final void play() {
initialize();
startPlay();
endPlay();
}
}
// 具体子类 - 足球游戏
class Football extends Game {
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
}
// 测试
public class TemplatePatternDemo {
public static void main(String[] args) {
Game footballGame = new Football();
footballGame.play();
}
}
此示例中,Game
抽象类的 play
方法是模板方法,定义了游戏从初始化到结束的整体流程,initialize
、startPlay
、endPlay
为抽象方法。Football
子类继承 Game
类,实现了这三个抽象方法,定制了足球游戏的具体流程。客户端通过调用 Football
实例的 play
方法,执行完整的足球游戏流程,体现了模板方法模式在实际场景中的应用。
3.3、观察者模式(Observer Pattern)
核心思想
观察者模式定义了对象之间一对多的依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会自动收到通知并进行更新,实现了数据变化与响应逻辑的分离。
应用
JDK 中的应用:
- java.util.Observer 和 java.util.Observable
- javax.swing.event.ChangeListener
Spring 中的应用:
- ApplicationEvent 和 ApplicationListener 是典型实现。
实现方式
通过定义观察者接口和被观察者接口(或抽象类)来构建通知机制。观察者接口声明接收更新的方法,被观察者接口则包含注册、移除观察者以及通知所有观察者的方法。具体的被观察者类实现被观察者接口,维护观察者列表并在状态变化时触发通知;具体观察者类实现观察者接口,定义接收到通知后的具体处理逻辑。
应用场景
- 事件驱动系统:在图形界面编程中,按钮点击、鼠标移动等事件可作为被观察者,注册的事件监听器就是观察者,事件触发时自动通知监听器执行对应操作。
- 消息订阅系统:新闻推送、社交媒体关注功能等场景下,发布者是被观察者,订阅用户是观察者,新内容发布时自动推送给所有订阅者。
优点
- 解耦:观察者和被观察者之间仅通过接口进行交互,彼此无需了解对方的具体实现细节,降低了代码耦合度,提升系统可维护性。
- 动态更新:被观察者状态变化时,自动遍历通知所有注册的观察者,确保数据更新的实时性和一致性,无需手动逐个调用观察者逻辑。
通用消息通知示例
import java.util.ArrayList;
import java.util.List;
// 观察者接口
public interface Observer {
void update(String message);
}
// 被观察者接口
public interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
// 具体被观察者
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// 具体观察者
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received: " + message);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("Observer1");
Observer observer2 = new ConcreteObserver("Observer2");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers("Hello Observers!");
}
}
在该示例中,Observer
接口定义了接收更新的 update
方法;Subject
接口规定了被观察者管理观察者和触发通知的方法。ConcreteSubject
作为具体被观察者,维护观察者列表并实现通知逻辑;ConcreteObserver
是具体观察者,在收到消息时打印通知内容。客户端通过注册观察者并触发被观察者通知,展示了完整的消息传递流程。
状态变更通知示例
import java.util.ArrayList;
import java.util.List;
// 主题接口(被观察者)
interface Subject {
void registerObserver(Observer o);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(int state);
}
// 具体主题类
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public void setState(int state) {
this.state = state;
notifyObservers();
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
}
// 具体观察者类
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(int state) {
System.out.println(name + " received update: state is now " + state);
}
}
// 测试
public class ObserverPatternDemo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer = new ConcreteObserver("Observer");
subject.registerObserver(observer);
subject.setState(10);
}
}
此示例聚焦于状态变更通知,Subject
接口和 Observer
接口分别定义被观察者和观察者的核心方法。ConcreteSubject
类在状态变更时自动通知所有观察者,ConcreteObserver
类接收状态更新并打印信息。客户端通过注册观察者并修改被观察者状态,体现了观察者模式在状态驱动场景下的应用。
3.4、迭代器模式(Iterator Pattern)
核心思想
迭代器模式旨在提供一种统一、安全的方式,让开发者可以访问容器对象中的各个元素,同时隐藏容器内部的存储和组织细节。通过这种方式,将元素遍历的逻辑从容器类中分离出来,使得容器类专注于数据存储,迭代器类专注于数据遍历。
实现方式
定义一个迭代器接口,规定迭代器需要实现的基本方法(如判断是否有下一个元素、获取下一个元素);再创建具体的迭代器类实现该接口,负责容器元素的实际遍历逻辑。同时,容器类提供获取迭代器的方法,使外部可以通过迭代器访问容器元素。
应用场景
- 集合类:Java 中的
List
、Set
等集合类广泛使用迭代器模式,通过Iterator
接口和具体实现类,方便开发者遍历集合元素。 - 数据库查询结果:在处理数据库查询返回的结果集时,可将结果集视为容器,使用迭代器模式按顺序遍历每一条数据记录,避免暴露数据库连接和结果集处理的底层细节。
优点
- 简化访问:提供统一的接口和操作方式来遍历容器元素,无论容器内部数据结构如何复杂,开发者都能以相同的方式进行遍历,降低了使用难度。
- 解耦:容器类和迭代器类职责分离,容器类无需关注遍历逻辑,迭代器类也不依赖容器的具体实现,提高了代码的可维护性和扩展性。
示例一:通用迭代器实现
import java.util.ArrayList;
import java.util.List;
// 迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
// 具体迭代器类
public class ConcreteIterator implements Iterator {
private List<Object> items;
private int position;
public ConcreteIterator(List<Object> items) {
this.items = items;
}
@Override
public boolean hasNext() {
return position < items.size();
}
@Override
public Object next() {
return items.get(position++);
}
}
// 聚合类
public class Aggregate {
private List<Object> items = new ArrayList<>();
public void add(Object item) {
items.add(item);
}
public Iterator iterator() {
return new ConcreteIterator(items);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Aggregate aggregate = new Aggregate();
aggregate.add("Item 1");
aggregate.add("Item 2");
Iterator iterator = aggregate.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
在该示例中,Iterator
接口定义了迭代器的核心方法;ConcreteIterator
是具体迭代器类,通过维护元素列表和当前位置,实现对列表元素的遍历;Aggregate
作为聚合类(容器类),负责存储元素,并提供获取迭代器的方法;客户端通过获取迭代器并调用其方法,实现对容器元素的遍历。
示例二:自定义容器与迭代器
// 迭代器接口
interface Iterator {
boolean hasNext();
Object next();
}
// 聚合接口
interface Container {
Iterator getIterator();
}
// 具体聚合类
class NameRepository implements Container {
private String[] names = {"Robert", "John", "Julie", "Lora"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {
private int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public Object next() {
if (hasNext()) {
return names[index++];
}
return null;
}
}
}
// 测试类
public class IteratorPatternDemo {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for (Iterator iter = namesRepository.getIterator(); iter.hasNext(); ) {
String name = (String) iter.next();
System.out.println("Name : " + name);
}
}
}
此示例中,先定义了 Iterator
迭代器接口和 Container
聚合接口;NameRepository
作为具体聚合类,实现了 Container
接口,并在内部定义了 NameIterator
具体迭代器类;客户端通过 NameRepository
获取迭代器,使用传统的 for
循环结合迭代器方法,完成对容器内字符串元素的遍历输出,更清晰地展示了迭代器模式在自定义容器中的应用。
3.5、责任链模式(Chain of Responsibility Pattern)
核心思想
责任链模式的核心在于让多个对象都有机会处理请求,以此避免请求的发送者和接收者之间产生耦合关系。它将这些处理对象连接成一条链,请求会沿着这条链依次传递,直至有一个对象能够处理该请求为止。
实现方式
定义一个处理请求的链,请求会逐步传递给链中的各个对象,每个对象都会判断自己是否能够处理该请求,如果可以则进行处理,若不能则将请求传递给链中的下一个对象,直到找到合适的处理者。
应用场景
- Servlet Filter:在 Java Web 开发中,Servlet Filter 是责任链模式的典型应用。多个 Filter 可以组成一个链,对请求和响应进行预处理和后处理,例如字符编码处理、权限验证等。
- 审批流程:在企业的审批流程中,一个请求可能需要经过多个部门或人员的审批,每个审批者都可以看作是责任链上的一个处理对象,请求会依次传递给各个审批者,直到审批流程结束。
优点
- 解耦:请求的发送者和接收者之间的耦合度降低,发送者只需将请求发送到责任链上,无需关心哪个对象会处理该请求。
- 灵活性:可以动态地添加、删除或修改责任链中的处理者,以适应不同的业务需求。
示例一:简单请求处理
// 处理者接口
public abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
// 具体处理者A
public class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("A")) {
System.out.println("Handler A handling request A");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 具体处理者B
public class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("B")) {
System.out.println("Handler B handling request B");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setNextHandler(handlerB);
handlerA.handleRequest("A"); // 处理请求A
handlerA.handleRequest("B"); // 处理请求B
handlerA.handleRequest("C"); // 无处理者
}
}
在这个示例中,Handler
是抽象处理者类,定义了设置下一个处理者和处理请求的方法。ConcreteHandlerA
和 ConcreteHandlerB
是具体处理者类,分别处理请求 A
和 B
,若无法处理则将请求传递给下一个处理者。客户端代码创建了处理者链,并发送不同的请求进行测试。
示例二:日志记录器责任链
// 抽象处理者类
abstract class AbstractLogger {
public static final int INFO = 1;
public static final int DEBUG = 2;
public static final int ERROR = 3;
protected int level;
protected AbstractLogger nextLogger;
public void setNextLogger(AbstractLogger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
// 具体处理者类 - 控制台日志记录器
class ConsoleLogger extends AbstractLogger {
public ConsoleLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
// 辅助方法:构建日志记录器责任链
public class ChainPatternDemo {
private static AbstractLogger getChainOfLoggers() {
AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
return consoleLogger;
}
public static void main(String[] args) {
AbstractLogger loggerChain = getChainOfLoggers();
loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
}
}
在这个示例中,AbstractLogger
是抽象处理者类,定义了日志级别和设置下一个日志记录器的方法,以及处理日志消息的逻辑。ConsoleLogger
是具体处理者类,负责将日志消息输出到控制台。ChainPatternDemo
类中的 getChainOfLoggers
方法用于构建日志记录器责任链,main
方法发送日志消息进行测试。
3.6、命令模式(Command Pattern)
核心思想
将请求封装为独立的对象,通过这种方式实现对客户的参数化请求,支持请求的队列化处理、撤销与重做等操作,极大地增强了系统的灵活性和扩展性。
实现方式
通过定义统一的命令接口,具体命令类实现该接口,并将请求逻辑封装其中。同时引入调用者和接收者角色,调用者负责触发命令执行,接收者则真正执行命令对应的操作,以此完成请求从发起端到执行端的传递。
命令模式提供了一种将请求封装成对象的方法,使得请求的发送者与请求的接收者之间不直接耦合。这通过引入以下角色实现:
- 命令(Command):抽象命令类,定义了执行命令的接口。它通常包含一个执行方法,以及可能的其他方法(例如,撤消)。
- 具体命令(Concrete Command):实现了抽象命令类的具体子类,将一个接收者与一个动作绑定。它实现了执行方法,该方法调用接收者的特定操作。
- 接收者(Receiver):执行实际工作的类。命令模式将命令传递给接收者,由接收者执行实际的操作。
- 调用者/请求者(Invoker):负责将命令传递给合适的接收者并触发命令的执行。它并不关心具体的命令细节。
- 客户端(Client):创建命令对象、接收者对象以及调用者对象,并将它们组织起来以实现特定的操作流程。
应用场景
- GUI 菜单操作:在图形界面应用中,菜单选项、按钮点击等操作可视为命令。将每个操作封装为命令对象,便于管理和扩展功能,例如实现操作的撤销、重做等。
- 事务管理:在数据库操作或其他需要保证数据一致性的场景中,将一系列操作封装为命令,通过命令的组合和管理,实现事务的提交、回滚等操作。
优点
- 解耦:命令模式有效分离了请求的发送者(调用者)和接收者,两者无需直接关联,降低了代码的耦合度,提升了系统的可维护性。
- 灵活性:可以动态地创建、修改、撤销请求,方便实现诸如操作队列、日志记录、批量执行等功能,满足多样化的业务需求。
基础示例
// 命令接口
public interface Command {
void execute();
}
// 具体命令类
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action(); // 将请求委托给接收者
}
}
// 接收者类
public class Receiver {
public void action() {
System.out.println("Receiver action");
}
}
// 调用者类
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void invoke() {
command.execute();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.invoke(); // 执行命令
}
}
在该示例中,Command
接口定义了命令执行方法;ConcreteCommand
是具体命令类,持有接收者实例,并在 execute
方法中调用接收者的操作;Receiver
负责实际执行操作;Invoker
作为调用者,接收命令对象并触发执行。客户端通过组装各角色,完成命令的发起与执行。
实际场景示例:遥控器控制灯开关
// 命令接口
interface Command {
void execute();
}
// 接收者类 - 灯
class Light {
public void switchOn() {
System.out.println("Light is turned on");
}
}
// 具体命令类 - 开灯命令
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.switchOn();
}
}
// 调用者类 - 遥控器
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// 测试类
public class CommandPatternDemo {
public static void main(String[] args) {
Light light = new Light();
RemoteControl remote = new RemoteControl();
remote.setCommand(new LightOnCommand(light));
remote.pressButton();
}
}
此示例模拟遥控器控制灯的场景,Light
是接收者,负责执行开灯操作;LightOnCommand
是具体命令类,封装了开灯请求;RemoteControl
作为调用者,通过设置命令并按下按钮触发执行,清晰展示了命令模式在实际场景中的应用。
3.7、备忘录模式(Memento Pattern)
核心思想
备忘录模式的核心在于,在不暴露对象内部状态细节的前提下,捕获对象的当前内部状态,并将其保存到对象外部。后续可根据需求将对象恢复到之前保存的状态,实现状态的回溯和恢复功能。
实现方式
通过定义备忘录类来专门保存对象的状态,同时引入发起人类和管理者类。发起人类负责创建备忘录对象以保存自身状态,也能从备忘录中恢复状态;管理者类负责管理备忘录对象,如保存和提供备忘录。
备忘录模式通过引入“备忘录”对象,允许在不暴露对象内部结构的情况下,捕获并存储对象的状态。同时,它还提供了一种将对象恢复到之前状态的方式。备忘录模式包括以下角色:
- Originator(发起人):这是需要被记录状态的对象。它创建一个备忘录对象,以存储当前状态,也可以从备忘录中恢复状态。
- Memento(备忘录):备忘录对象用于存储Originator的状态。通常,备忘录对象具有与原始对象相同的接口,但不会直接暴露其内部状态。
- Caretaker(负责人):负责管理备忘录对象。它可以存储多个备忘录对象,以便在需要时进行状态恢复。
应用场景
- 文本编辑器撤销功能:在文本编辑器中,用户的每一次操作(如输入、删除、修改等)都可以保存为一个备忘录状态。当用户执行撤销操作时,编辑器可以从备忘录中恢复到之前的状态。
- 游戏存档:游戏中玩家的进度、角色状态、关卡信息等都可以保存为备忘录。玩家在需要时可以读取存档,将游戏恢复到之前保存的状态继续进行。
优点
- 状态恢复:允许对象在特定时刻恢复到之前保存的状态,为用户提供了撤销、重试等操作的能力,增强了系统的容错性和用户体验。
- 封装性:备忘录模式将对象的状态保存和恢复逻辑封装起来,不暴露对象的内部状态结构,符合面向对象编程的封装原则,提高了代码的安全性和可维护性。
通用示例
// 备忘录类
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 发起人类
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
// 管理者类
public class Caretaker {
private Memento memento;
public void saveMemento(Memento memento) {
this.memento = memento;
}
public Memento retrieveMemento() {
return memento;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("State1");
caretaker.saveMemento(originator.saveStateToMemento());
originator.setState("State2");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(caretaker.retrieveMemento());
System.out.println("Restored State: " + originator.getState());
}
}
在这个示例中,Memento
类用于保存 Originator
对象的状态,Originator
类是发起者,能够保存和恢复自身状态,Caretaker
类负责管理备忘录对象。客户端代码演示了如何保存状态、修改状态以及恢复状态。
简化测试示例
// 备忘录类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 原发器类
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
// 测试类
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("State #1");
Memento memento = originator.saveStateToMemento();
originator.setState("State #2");
originator.getStateFromMemento(memento);
System.out.println("Current State: " + originator.getState());
}
}
此示例简化了管理者类的使用,直接在测试代码中操作备忘录对象。Originator
类同样具备保存和恢复状态的能力,通过创建备忘录对象并在需要时恢复状态,最终输出恢复后的状态。这个示例更侧重于展示备忘录模式的基本使用流程。
3.8、状态模式(State Pattern)
核心思想
状态模式的核心是允许对象在其内部状态发生改变时,动态地改变自身的行为,从外部看来,对象就好像修改了自身所属的类。通过这种方式,将对象的状态和行为进行解耦,使得状态的变化和行为的改变能够独立管理。
实现方式
定义一个状态接口,其中包含状态对应的行为方法;创建具体的状态类实现该接口,每个具体状态类负责实现特定状态下的行为逻辑;同时,创建一个上下文类,该类持有当前状态的引用,并提供方法来改变状态和触发状态对应的行为。
应用场景
- 游戏状态机:在游戏开发中,游戏角色或场景可能存在多种状态,如待机、移动、攻击等。状态模式可以方便地管理这些状态之间的切换以及每种状态下的行为表现。
- 自动售货机:自动售货机有空闲、投币、出货等不同状态,不同状态下对用户操作的响应不同。使用状态模式可以清晰地管理这些状态和相应的行为。
- 线程状态:线程具有新建、就绪、运行、阻塞、死亡等多种状态,状态模式可以很好地处理线程状态之间的转换以及不同状态下的线程行为。
优点
- 状态独立:每个具体状态类封装了自身的行为逻辑,状态之间相互独立,便于代码的维护和管理。
- 易于扩展:如果需要增加新的状态,只需创建一个新的具体状态类并实现状态接口,而无需修改现有的上下文类和其他状态类的代码。
状态切换示例
// 状态接口
public interface State {
void handle(Context context);
}
// 具体状态A
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("Handling state A");
context.setState(new ConcreteStateB()); // 切换到状态B
}
}
// 具体状态B
public class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("Handling state B");
context.setState(new ConcreteStateA()); // 切换到状态A
}
}
// 上下文类
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
context.request(); // 处理状态A
context.request(); // 处理状态B
}
}
在这个示例中,State
接口定义了状态处理方法 handle
。ConcreteStateA
和 ConcreteStateB
是具体的状态类,分别实现了 handle
方法,并且在方法中可以进行状态的切换。Context
类作为上下文,持有当前状态的引用,通过 request
方法触发当前状态的处理逻辑。客户端代码创建了一个上下文对象,并多次调用 request
方法,观察状态的切换和行为的改变。
游戏玩家状态示例
// 状态接口
interface State {
void doAction(Context context);
}
// 具体状态类 - 开始状态
class StartState implements State {
@Override
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
}
// 上下文类
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
}
// 测试
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println("Current state: " + context.getState().getClass().getSimpleName());
}
}
在这个示例中,State
接口定义了状态行为方法 doAction
。StartState
是具体的状态类,代表游戏玩家的开始状态,在 doAction
方法中输出当前状态信息并将上下文的状态设置为自身。Context
类作为上下文,提供了设置和获取状态的方法。测试代码创建了上下文对象和开始状态对象,调用开始状态的 doAction
方法,并输出当前的状态信息。
3.9、访问者模式(Visitor Pattern)
核心思想
访问者模式将作用于对象结构中元素的操作进行分离,使得在不修改对象元素类的前提下,能够定义新的操作。它把数据结构(对象元素)和作用于结构上的操作解耦,让操作的变化不会影响对象结构本身,反之亦然。
实现方式
定义访问者接口,该接口包含针对不同对象元素的访问方法;创建具体访问者类实现该接口,每个具体访问者类实现特定的操作逻辑。同时,对象元素类需提供 accept
方法,用于接收访问者并调用访问者对应的访问方法。此外,通过对象结构类管理元素集合,并提供统一的方法让访问者遍历元素。
应用场景
- 编译器语法树分析:在编译器中,语法树作为对象结构,不同的语法节点是元素。访问者模式可用于实现语法检查、代码优化等操作,新的分析操作可以通过新增访问者实现,而无需修改语法树结构。
- 复杂对象结构遍历:例如在图形编辑软件中,图形对象(如矩形、圆形等)构成对象结构,使用访问者模式可以方便地实现图形的统计、渲染优化等操作,添加新功能时仅需增加新的访问者类。
优点
- 扩展性:当需要增加新的操作时,只需创建新的具体访问者类实现访问者接口,无需修改对象结构和已有元素类的代码,符合开闭原则。
- 操作集中:将相关操作集中在访问者类中,使得同一类型的操作逻辑聚合,便于代码的维护和管理,也提高了代码的可读性。
通用示例
import java.util.ArrayList;
import java.util.List;
// 访问者接口
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
// 具体访问者A
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("Visitor A visiting Element A");
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("Visitor A visiting Element B");
}
}
// 具体访问者B
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("Visitor B visiting Element A");
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("Visitor B visiting Element B");
}
}
// 元素接口
public interface Element {
void accept(Visitor visitor);
}
// 具体元素A
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 具体元素B
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 对象结构
public class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void addElement(Element element) {
elements.add(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ObjectStructure structure = new ObjectStructure();
structure.addElement(new ConcreteElementA());
structure.addElement(new ConcreteElementB());
Visitor visitorA = new ConcreteVisitorA();
structure.accept(visitorA); // Visitor A visiting elements
Visitor visitorB = new ConcreteVisitorB();
structure.accept(visitorB); // Visitor B visiting elements
}
}
在该示例中,Visitor
接口定义了针对不同元素的访问方法;ConcreteVisitorA
和 ConcreteVisitorB
是具体访问者类,实现了具体的访问操作。Element
接口规定元素需实现 accept
方法,ConcreteElementA
和 ConcreteElementB
是具体元素类。ObjectStructure
管理元素集合,并提供方法让访问者遍历元素。客户端通过 ObjectStructure
调用不同访问者,实现对元素的不同操作。
电脑硬件价格统计示例
// 元素接口
interface ComputerPart {
void accept(ComputerPartVisitor visitor);
}
// 键盘类
class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor visitor) {
visitor.visit(this);
}
}
// 访问者接口
interface ComputerPartVisitor {
void visit(Keyboard keyboard);
}
// 价格统计访问者
class PriceVisitor implements ComputerPartVisitor {
private double totalPrice = 0;
@Override
public void visit(Keyboard keyboard) {
totalPrice += 50; // 假设键盘价格为 50
}
public double getTotalPrice() {
return totalPrice;
}
}
// 测试
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart keyboard = new Keyboard();
PriceVisitor priceVisitor = new PriceVisitor();
keyboard.accept(priceVisitor);
System.out.println("电脑硬件总价: " + priceVisitor.getTotalPrice());
}
}
在此示例中,ComputerPart
是元素接口,Keyboard
为具体硬件元素类。ComputerPartVisitor
是访问者接口,PriceVisitor
作为具体访问者,实现了统计键盘价格的操作。客户端通过 Keyboard
元素调用 PriceVisitor
访问者,完成价格统计功能,展示了访问者模式在实际场景中的应用。
3.10、中介者模式(Mediator Pattern)
核心思想
中介者模式通过引入一个中介者对象,封装多个对象之间的交互逻辑,降低对象之间的直接耦合度,使得这些对象可以独立变化而不影响彼此间的协作。各对象不再直接相互调用,而是将请求发送给中介者,由中介者统一协调处理。
实现方式
定义中介者接口,规定协调对象交互的方法;创建具体中介者类实现该接口,负责具体的交互逻辑处理。同时,参与交互的对象(同事类)持有中介者的引用,通过中介者进行通信,而非直接调用其他对象的方法。
应用场景
- 聊天室消息转发:在多人聊天系统中,每个用户是一个独立对象,通过聊天中介者来统一处理消息的接收、转发,避免用户对象之间的直接关联。
- MVC 架构中的控制器:控制器作为中介者,协调模型(Model)和视图(View)之间的交互,视图层的操作请求由控制器处理,再由控制器通知模型层更新数据,并决定视图的刷新,降低模型与视图的耦合度。
优点
- 降低耦合:将对象间复杂的交互逻辑集中在中介者中处理,避免对象之间形成网状依赖关系,使系统结构更加清晰。
- 易于维护:所有交互逻辑集中在中介者类,修改或扩展交互规则时,只需调整中介者的代码,无需改动多个对象的代码,提高维护效率。
基础示例
// 中介者接口
public interface Mediator {
void notify(Component sender, String event);
}
// 具体中介者类
public class ConcreteMediator implements Mediator {
private ComponentA componentA;
private ComponentB componentB;
public void setComponentA(ComponentA componentA) {
this.componentA = componentA;
}
public void setComponentB(ComponentB componentB) {
this.componentB = componentB;
}
@Override
public void notify(Component sender, String event) {
if (sender == componentA) {
componentB.handleEvent(event);
} else if (sender == componentB) {
componentA.handleEvent(event);
}
}
}
// 组件接口
public abstract class Component {
protected Mediator mediator;
public Component(Mediator mediator) {
this.mediator = mediator;
}
public abstract void handleEvent(String event);
}
// 具体组件A
public class ComponentA extends Component {
public ComponentA(Mediator mediator) {
super(mediator);
}
@Override
public void handleEvent(String event) {
System.out.println("ComponentA handling event: " + event);
}
public void triggerEvent(String event) {
mediator.notify(this, event);
}
}
// 具体组件B
public class ComponentB extends Component {
public ComponentB(Mediator mediator) {
super(mediator);
}
@Override
public void handleEvent(String event) {
System.out.println("ComponentB handling event: " + event);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
ComponentA componentA = new ComponentA(mediator);
ComponentB componentB = new ComponentB(mediator);
mediator.setComponentA(componentA);
mediator.setComponentB(componentB);
componentA.triggerEvent("Event A");
componentB.handleEvent("Event B");
}
}
在该示例中,Mediator
接口定义了协调组件交互的方法;ConcreteMediator
是具体中介者,负责处理 ComponentA
和 ComponentB
之间的交互。Component
是抽象组件类,持有中介者引用,具体组件 ComponentA
和 ComponentB
实现了处理事件的方法,并通过中介者触发事件,实现了组件间解耦的交互。
聊天室场景示例
import java.util.ArrayList;
import java.util.List;
// 中介者接口
interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// 具体中介者类
class ChatMediatorImpl implements ChatMediator {
private List<User> users = new ArrayList<>();
@Override
public void sendMessage(String message, User user) {
for (User u : users) {
if (u != user) {
u.receive(message);
}
}
}
@Override
public void addUser(User user) {
users.add(user);
}
}
// 同事类 - 用户
abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator med, String name) {
this.mediator = med;
this.name = name;
}
public abstract void send(String message);
public abstract void receive(String message);
}
// 具体用户类
class ConcreteUser extends User {
public ConcreteUser(ChatMediator med, String name) {
super(med, name);
}
@Override
public void send(String message) {
System.out.println(this.name + " sends: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + " receives: " + message);
}
}
// 测试类
public class MediatorPatternDemo {
public static void main(String[] args) {
ChatMediator mediator = new ChatMediatorImpl();
User user1 = new ConcreteUser(mediator, "John");
User user2 = new ConcreteUser(mediator, "Alice");
mediator.addUser(user1);
mediator.addUser(user2);
user1.send("Hello, everyone!");
}
}
在聊天室示例中,ChatMediator
接口定义了消息发送和用户添加方法;ChatMediatorImpl
是具体中介者,负责实现消息转发逻辑和管理用户列表。User
是抽象用户类,持有中介者引用,ConcreteUser
实现了发送和接收消息的方法。通过中介者,用户之间无需直接关联,即可实现消息的发送与接收,体现了中介者模式在实际场景中的应用。
3.11、解释器模式(Interpreter Pattern)
核心思想
解释器模式的核心在于为特定的语言定义其文法的表示形式,并构建一个解释器,利用该表示形式对语言中的句子进行解释。这种模式将语言的文法规则和解释逻辑分离,使得我们可以方便地对语言进行扩展和修改。
实现方式
通过定义解释器类和表达式类来实现。表达式类又可分为终结符表达式和非终结符表达式,终结符表达式表示文法中的基本元素,非终结符表达式则由多个终结符或非终结符表达式组合而成。解释器根据这些表达式对输入的句子进行解析和计算。
解释器模式通过定义一种语言文法的表示,并提供一种解释器来解释这种语言的语句。这样,你可以将语句表示为抽象语法树,然后通过解释器逐步执行和解释这个语法树。
- 抽象表达式(Abstract Expression):定义了一个抽象的解释方法,所有的具体表达式都需要实现这个接口。
- 终结符表达式(Terminal Expression):实现了抽象表达式接口,用于表示语言中的终结符(最小的语法单元)。
- 非终结符表达式(Non-terminal Expression):实现了抽象表达式接口,用于表示语言中的非终结符,通常由多个终结符和/或其他非终结符组成的组合。
- 上下文(Context):包含了需要被解释的信息,通常包括输入的语句和解释器。
- 解释器(Interpreter):包含了解释器模式的主要逻辑,它通过递归的方式对抽象语法树进行解释,实现了语言中各种语句的解释和执行。
应用场景
- 编译器:编译器需要对源代码进行词法分析、语法分析和语义分析,解释器模式可以用于实现语法分析阶段,将源代码解析为抽象语法树。
- SQL 解析器:SQL 解析器负责将用户输入的 SQL 语句解析为数据库可以执行的操作,解释器模式可以用于解析 SQL 语句的语法结构。
- 游戏规则引擎:在游戏开发中,游戏规则可以看作是一种特定的语言,解释器模式可以用于实现游戏规则的解析和执行。
优点
- 易于扩展:可以通过增加新的终结符和非终结符表达式来扩展语法,而无需修改现有的解释器逻辑。
- 灵活性:可以定义复杂的语言规则,并且可以根据不同的需求对规则进行定制和修改。
算术表达式解释器示例
import java.util.HashMap;
import java.util.Map;
// 表达式接口
public interface Expression {
int interpret(Map<String, Integer> context);
}
// 终结符表达式
public class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret(Map<String, Integer> context) {
return number;
}
}
// 非终结符表达式
public class PlusExpression implements Expression {
private Expression left;
private Expression right;
public PlusExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map<String, Integer> context) {
return left.interpret(context) + right.interpret(context);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Expression expression = new PlusExpression(new NumberExpression(5), new NumberExpression(3));
Map<String, Integer> context = new HashMap<>();
int result = expression.interpret(context);
System.out.println("Result: " + result); // 输出结果 8
}
}
在这个示例中,Expression
是表达式接口,定义了解释方法 interpret
。NumberExpression
是终结符表达式,表示一个具体的数字。PlusExpression
是非终结符表达式,表示加法运算。客户端代码创建了一个加法表达式,并调用其 interpret
方法进行计算,最终输出结果。
布尔表达式解释器示例
import java.util.HashMap;
import java.util.Map;
// 抽象表达式接口
interface Expression {
boolean interpret(Context context);
}
// 终结符表达式类,代表布尔变量
class VariableExpression implements Expression {
private String name;
public VariableExpression(String name) {
this.name = name;
}
@Override
public boolean interpret(Context context) {
return context.lookup(name);
}
}
// 上下文类
class Context {
private Map<String, Boolean> variables = new HashMap<>();
public void assign(String name, boolean value) {
variables.put(name, value);
}
public boolean lookup(String name) {
return variables.getOrDefault(name, false);
}
}
// 测试
public class InterpreterPatternSimpleDemo {
public static void main(String[] args) {
Context context = new Context();
context.assign("x", true);
Expression x = new VariableExpression("x");
boolean result = x.interpret(context);
System.out.println("表达式结果: " + result);
}
}
在这个示例中,Expression
是抽象表达式接口,定义了布尔表达式的解释方法 interpret
。VariableExpression
是终结符表达式,表示一个布尔变量。Context
类用于存储变量的值,提供了赋值和查找变量值的方法。测试代码创建了一个上下文对象并为变量 x
赋值,然后创建了一个布尔表达式并调用其 interpret
方法进行计算,最终输出表达式的结果。