设计模式指南 (Java 版)
设计模式是一个很容易让人头晕的话题。在这里,我尝试用最简单的方式来解释它们,让这些概念深深印在你的(也许还有我的)脑海里。
| 创建型设计模式 (Creational) | 结构型设计模式 (Structural) | 行为型设计模式 (Behavioral) |
|---|---|---|
| [简单工厂模式 (Simple Factory)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [适配器模式 (Adapter)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [责任链模式 (Chain of Responsibility)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
| [工厂方法模式 (Factory Method)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [桥接模式 (Bridge)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [命令模式 (Command)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
| [抽象工厂模式 (Abstract Factory)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [组合模式 (Composite)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [迭代器模式 (Iterator)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
| [生成器模式 (Builder)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [装饰器模式 (Decorator)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [中介者模式 (Mediator)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
| [原型模式 (Prototype)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [外观模式 (Facade)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [备忘录模式 (Memento)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
| [单例模式 (Singleton)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [享元模式 (Flyweight)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [观察者模式 (Observer)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
| [代理模式 (Proxy)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | [访问者模式 (Visitor)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | |
| [策略模式 (Strategy)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | ||
| [状态模式 (State)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) | ||
| [模板方法模式 (Template Method)](#创建型设计模式 (Creational) 结构型设计模式 (Structural) 行为型设计模式 (Behavioral) 简单工厂模式 (Simple Factory) 适配器模式 (Adapter) 责任链模式 (Chain of Responsibility) 工厂方法模式 (Factory Method) 桥接模式 (Bridge) 命令模式 (Command) 抽象工厂模式 (Abstract Factory) 组合模式 (Composite) 迭代器模式 (Iterator) 生成器模式 (Builder) 装饰器模式 (Decorator) 中介者模式 (Mediator) 原型模式 (Prototype) 外观模式 (Facade) 备忘录模式 (Memento) 单例模式 (Singleton) 享元模式 (Flyweight) 观察者模式 (Observer) 代理模式 (Proxy) 访问者模式 (Visitor) 策略模式 (Strategy) 状态模式 (State) 模板方法模式 (Template Method)) |
简介
设计模式是针对反复出现的问题的解决方案;即如何处理特定问题的指导方针。它们不是你可以直接插入应用程序并等待奇迹发生的类、包或库。相反,它们是在特定情况下如何解决特定问题的指导原则。
设计模式是针对反复出现的问题的解决方案;即如何处理特定问题的指导方针。
维基百科这样描述它们:
在软件工程中,软件设计模式是对软件设计中给定上下文内普遍存在的问题的通用可重用解决方案。它不是可以直接转换为源代码或机器码的成品设计,而是一个如何在许多不同情况下解决问题的描述或模板。
注意事项
- 设计模式并不是解决所有问题的"银弹"(万能药)。
- 不要强行套用设计模式;如果这样做,肯定会适得其反。
- 请记住,设计模式是针对 问题的解决方案,而不是寻找问题的解决方案;所以不要过度设计。
- 如果在正确的地方以正确的方式使用,它们可以成为救世主;否则,它们可能会让代码变得一团糟。
注:以下代码示例均已转换为 Java 语言编写,核心设计理念与原版完全一致。
设计模式的分类
创建型设计模式
通俗地说
创建型模式主要关注如何实例化一个对象或一组相关对象。
维基百科说
在软件工程中,创建型设计模式是处理对象创建机制的设计模式,试图以适合当前情况的方式创建对象。基本的对象创建形式可能会导致设计问题或增加设计的复杂性。创建型设计模式通过以某种方式控制对象的创建来解决这个问题。
简单工厂模式 (Simple Factory)
现实生活中的例子
想象一下,你正在盖房子,你需要门。你可以穿上木匠服,拿来木头、胶水、钉子和所有造门所需的工具,在自己家里动手做门;或者你也可以直接打电话给工厂,让他们把做好的门送过来,这样你就不需要学习任何关于造门的知识,也不用处理造门带来的杂乱工作。
通俗地说
简单工厂只是为客户端生成一个实例,而不向客户端暴露任何实例化逻辑。
维基百科说
在面向对象编程(OOP)中,工厂是用于创建其他对象的对象------从形式上讲,工厂是一个函数或方法,它通过某些方法调用返回不同原型或类的对象(假定为 "new")。
代码示例
首先,我们有一个门的接口及其实现类:
java
public interface Door {
float getWidth();
float getHeight();
}
public class WoodenDoor implements Door {
protected float width;
protected float height;
public WoodenDoor(float width, float height) {
this.width = width;
this.height = height;
}
@Override
public float getWidth() {
return this.width;
}
@Override
public float getHeight() {
return this.height;
}
}
然后我们有制造并返回门的工厂类:
java
public class DoorFactory {
public static Door makeDoor(float width, float height) {
return new WoodenDoor(width, height);
}
}
使用方式如下:
java
// 给我造一扇 100x200 的门
Door door = DoorFactory.makeDoor(100, 200);
System.out.println("Width: " + door.getWidth());
System.out.println("Height: " + door.getHeight());
// 给我造一扇 50x100 的门
Door door2 = DoorFactory.makeDoor(50, 100);
何时使用?
当创建一个对象不仅仅是简单的几个赋值操作,而是涉及一些逻辑时,将其放在专门的工厂中是有意义的,这样可以避免在各处重复编写相同的代码。
工厂方法模式 (Factory Method)
现实生活中的例子
考虑一下招聘经理的情况。一个人不可能面试所有职位。根据空缺的职位,她必须决定并将面试步骤委派给不同的人。
通俗地说
它提供了一种将实例化逻辑委派给子类的方法。
维基百科说
在基于类的编程中,工厂方法模式是一种创建型模式,它使用工厂方法来处理创建对象的问题,而无需指定将要创建的对象的确切类。这是通过调用工厂方法(在接口中指定并由子类实现,或者在基类中实现并由派生类选择性地重写)来创建对象,而不是通过调用构造函数来实现的。
代码示例
沿用上面的招聘经理例子。首先我们有一个面试官接口和它的一些实现:
java
public interface Interviewer {
void askQuestions();
}
public class Developer implements Interviewer {
@Override
public void askQuestions() {
System.out.println("Asking about design patterns!");
}
}
public class CommunityExecutive implements Interviewer {
@Override
public void askQuestions() {
System.out.println("Asking about community building");
}
}
现在让我们创建我们的 HiringManager(招聘经理):
java
public abstract class HiringManager {
// 工厂方法
protected abstract Interviewer makeInterviewer();
public void takeInterview() {
Interviewer interviewer = this.makeInterviewer();
interviewer.askQuestions();
}
}
现在任何子类都可以继承它并提供所需的面试官:
java
public class DevelopmentManager extends HiringManager {
@Override
protected Interviewer makeInterviewer() {
return new Developer();
}
}
public class MarketingManager extends HiringManager {
@Override
protected Interviewer makeInterviewer() {
return new CommunityExecutive();
}
}
使用方式如下:
java
HiringManager devManager = new DevelopmentManager();
devManager.takeInterview(); // 输出: Asking about design patterns!
HiringManager marketingManager = new MarketingManager();
marketingManager.takeInterview(); // 输出: Asking about community building
何时使用?
当类中存在一些通用处理逻辑,但所需的具体子类是在运行时动态决定时非常有用。换句话说,当客户端不知道它可能需要哪个确切的子类时使用。
抽象工厂模式 (Abstract Factory)
现实生活中的例子
扩展一下我们在简单工厂中的门的例子。根据你的需求,你可能会从木门商店买木门,从铁门商店买铁门,或者从相关商店买 PVC 门。此外,你可能需要具有不同专业技能的人来安装门,例如木门需要木匠,铁门需要焊工等。正如你所见,门和安装工之间存在依赖关系:木门需要木匠,铁门需要焊工。
通俗地说
工厂的工厂;一个将独立但相关/相互依赖的工厂组合在一起的工厂,而无需指定它们的具体类。
维基百科说
抽象工厂模式提供了一种封装一组具有共同主题的独立工厂的方法,而无需指定它们的具体类。
代码示例
转换上面门的例子。首先我们有 Door 接口及其实现:
java
public interface Door {
void getDescription();
}
public class WoodenDoor implements Door {
@Override
public void getDescription() {
System.out.println("I am a wooden door");
}
}
public class IronDoor implements Door {
@Override
public void getDescription() {
System.out.println("I am an iron door");
}
}
然后我们有针对每种门类型的安装专家:
java
public interface DoorFittingExpert {
void getDescription();
}
public class Welder implements DoorFittingExpert {
@Override
public void getDescription() {
System.out.println("I can only fit iron doors");
}
}
public class Carpenter implements DoorFittingExpert {
@Override
public void getDescription() {
System.out.println("I can only fit wooden doors");
}
}
现在我们有了抽象工厂,它可以让我们制造一系列相关的对象(即木门工厂会创建木门和木门安装专家,铁门工厂会创建铁门和铁门安装专家):
java
public interface DoorFactory {
Door makeDoor();
DoorFittingExpert makeFittingExpert();
}
// 木门工厂返回木匠和木门
public class WoodenDoorFactory implements DoorFactory {
@Override
public Door makeDoor() {
return new WoodenDoor();
}
@Override
public DoorFittingExpert makeFittingExpert() {
return new Carpenter();
}
}
// 铁门工厂获取铁门和相关的安装专家
public class IronDoorFactory implements DoorFactory {
@Override
public Door makeDoor() {
return new IronDoor();
}
@Override
public DoorFittingExpert makeFittingExpert() {
return new Welder();
}
}
使用方式如下:
java
DoorFactory woodenFactory = new WoodenDoorFactory();
Door door = woodenFactory.makeDoor();
DoorFittingExpert expert = woodenFactory.makeFittingExpert();
door.getDescription(); // 输出: I am a wooden door
expert.getDescription(); // 输出: I can only fit wooden doors
// 铁门工厂同理
DoorFactory ironFactory = new IronDoorFactory();
Door ironDoor = ironFactory.makeDoor();
DoorFittingExpert ironExpert = ironFactory.makeFittingExpert();
ironDoor.getDescription(); // 输出: I am an iron door
ironExpert.getDescription(); // 输出: I can only fit iron doors
正如你所看到的,木门工厂封装了 Carpenter(木匠)和 WoodenDoor(木门),铁门工厂封装了 IronDoor(铁门)和 Welder(焊工)。这帮助我们确保创建的每扇门都不会配错安装专家。
何时使用?
当存在相互关联的依赖关系,且涉及不那么简单的创建逻辑时使用。
生成器模式 (Builder)
现实生活中的例子
想象一下你在哈迪斯(Hardee's)快餐店点了一个特定套餐,比如"Big Hardee",他们会毫不犹豫地直接递给你;这就是简单工厂的例子。但在某些情况下,创建逻辑可能涉及更多步骤。例如,你想要一份定制的赛百味(Subway)套餐,在制作汉堡/三明治时有几种选择:你想要什么面包?你喜欢什么类型的酱汁?你想要什么奶酪?等等。在这种情况下,生成器模式就派上用场了。
通俗地说
允许你创建对象的不同变体,同时避免构造函数污染(参数过多)。当一个对象可能有多种变体,或者创建一个对象涉及很多步骤时非常有用。
维基百科说
生成器模式是一种对象创建型软件设计模式,旨在寻找解决"重叠构造函数反模式(telescoping constructor anti-pattern)"的方案。
说到这里,我稍微解释一下什么是"重叠构造函数反模式"。我们都曾见过类似下面这样的构造函数:
java
public Burger(int size, boolean cheese, boolean pepperoni, boolean tomato, boolean lettuce) {
}
正如你所见,构造函数参数的数量很快就会失控,并且很难理清参数的排列顺序。此外,如果你将来想添加更多选项,这个参数列表还会不断增长。这就被称为"重叠构造函数反模式"。
代码示例
明智的替代方案是使用生成器模式。首先,我们有想要制作的汉堡类:
java
public class Burger {
protected int size;
protected boolean cheese = false;
protected boolean pepperoni = false;
protected boolean lettuce = false;
protected boolean tomato = false;
public Burger(BurgerBuilder builder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.lettuce = builder.lettuce;
this.tomato = builder.tomato;
}
}
然后我们有生成器类:
java
public class BurgerBuilder {
public int size;
public boolean cheese = false;
public boolean pepperoni = false;
public boolean lettuce = false;
public boolean tomato = false;
public BurgerBuilder(int size) {
this.size = size;
}
public BurgerBuilder addPepperoni() {
this.pepperoni = true;
return this;
}
public BurgerBuilder addLettuce() {
this.lettuce = true;
return this;
}
public BurgerBuilder addCheese() {
this.cheese = true;
return this;
}
public BurgerBuilder addTomato() {
this.tomato = true;
return this;
}
public Burger build() {
return new Burger(this);
}
}
使用方式如下:
java
Burger burger = new BurgerBuilder(14)
.addPepperoni()
.addLettuce()
.addTomato()
.build();
何时使用?
当一个对象可能有多种变体,且需要避免构造函数参数膨胀时。它与工厂模式的主要区别在于:当创建是一步完成的过程时使用工厂模式;而当创建是多步骤的过程时使用生成器模式。
原型模式 (Prototype)
现实生活中的例子
还记得多莉(Dolly)吗?那只被克隆的羊!我们就不深究细节了,这里的关键点在于它完全是关于"克隆"的。
通俗地说
通过克隆现有对象来创建新对象。
维基百科说
原型模式是软件开发中的一种创建型设计模式。当要创建的对象的类型由一个原型实例决定时使用,通过克隆该原型实例来生成新对象。
简而言之,它允许你创建现有对象的副本并根据需要进行修改,而不必经历从头创建对象并进行繁琐设置的麻烦。
代码示例
在 Java 中,可以通过实现 Cloneable 接口并重写 clone() 方法轻松实现:
java
public class Sheep implements Cloneable {
protected String name;
protected String category;
public Sheep(String name) {
this(name, "Mountain Sheep");
}
public Sheep(String name, String category) {
this.name = name;
this.category = category;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setCategory(String category) {
this.category = category;
}
public String getCategory() {
return this.category;
}
@Override
public Sheep clone() {
try {
return (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
然后可以像下面这样克隆它:
java
Sheep original = new Sheep("Jolly");
System.out.println(original.getName()); // Jolly
System.out.println(original.getCategory()); // Mountain Sheep
// 克隆并修改所需属性
Sheep cloned = original.clone();
cloned.setName("Dolly");
System.out.println(cloned.getName()); // Dolly
System.out.println(cloned.getCategory()); // Mountain Sheep
你也可以在重写的 clone() 方法中自定义克隆行为(例如进行深拷贝)。
何时使用?
当需要一个与现有对象相似的对象,或者与克隆相比,直接创建对象的成本(资源或时间)非常高昂时使用。
单例模式 (Singleton)
现实生活中的例子
一个国家在同一时间只能有一位总统。每当职责召唤时,都必须由同一位总统采取行动。这里的总统就是单例。
通俗地说
确保某个特定类永远只创建一个对象。
维基百科说
在软件工程中,单例模式是一种将类的实例化限制为一个对象的软件设计模式。当整个系统只需要一个对象来协调操作时,这非常有用。
单例模式实际上常被认为是一种反模式,应避免过度使用。它不一定不好,也有一些合理的使用场景,但应谨慎使用,因为它会在应用程序中引入全局状态,在一处对其进行的更改可能会影响到其他区域,从而使调试变得相当困难。它的另一个缺点是会使代码紧密耦合,而且对单例进行单元测试(Mock)也会比较困难。
代码示例
要创建一个单例,需要将构造函数私有化,禁止克隆,禁止继承(使用 final),并创建一个静态变量来保存唯一实例:
java
public final class President {
private static President instance;
private President() {
// 隐藏构造函数
}
public static synchronized President getInstance() {
if (instance == null) {
instance = new President();
}
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// 禁止克隆
throw new CloneNotSupportedException("Cloning of this singleton is not allowed");
}
}
使用方式如下:
java
President president1 = President.getInstance();
President president2 = President.getInstance();
System.out.println(president1 == president2); // true
结构型设计模式
通俗地说
结构型模式主要关注对象的组合,或者换句话说,实体之间如何相互使用。另一种解释是,它们有助于回答"如何构建软件组件?"
维基百科说
在软件工程中,结构型设计模式是通过识别实现实体之间关系的简单方法来简化设计的设计模式。
适配器模式 (Adapter)
现实生活中的例子
想象一下你的存储卡里有一些照片,你需要把它们传输到电脑上。为了传输它们,你需要某种与电脑端口兼容的适配器,以便将存储卡连接到电脑。在这种情况下,读卡器就是一个适配器。
另一个例子是著名的电源适配器;三脚插头无法直接插入两孔插座,它需要使用电源适配器使其与两孔插座兼容。
还有一个例子是翻译官,将一个人说的话翻译给另一个人听。
通俗地说
适配器模式允许你将原本不兼容的对象包装在适配器中,使其与另一个类兼容。
维基百科说
在软件工程中,适配器模式是一种软件设计模式,它允许将现有类的接口用作另一个接口。它通常用于在不修改现有类源代码的情况下使其与其他类一起工作。
代码示例
考虑一个游戏,里面有一个猎人,他专门猎杀狮子。
首先我们有一个 Lion 接口,所有类型的狮子都必须实现它:
java
public interface Lion {
void roar();
}
public class AfricanLion implements Lion {
@Override
public void roar() {
}
}
public class AsianLion implements Lion {
@Override
public void roar() {
}
}
猎人期望猎杀任何实现了 Lion 接口的对象:
java
public class Hunter {
public void hunt(Lion lion) {
lion.roar();
}
}
现在假设我们必须在游戏中添加一只 WildDog(野狗),以便猎人也可以猎杀它。但我们不能直接这样做,因为狗有不同的接口。为了使其与我们的猎人兼容,我们必须创建一个兼容的适配器:
java
// 需要添加到游戏中的新类
public class WildDog {
public void bark() {
}
}
// 围绕野狗的适配器,使其与我们的游戏兼容
public class WildDogAdapter implements Lion {
protected WildDog dog;
public WildDogAdapter(WildDog dog) {
this.dog = dog;
}
@Override
public void roar() {
this.dog.bark();
}
}
现在就可以使用 WildDogAdapter 在我们的游戏中使用 WildDog 了:
java
WildDog wildDog = new WildDog();
WildDogAdapter wildDogAdapter = new WildDogAdapter(wildDog);
Hunter hunter = new Hunter();
hunter.hunt(wildDogAdapter);
桥接模式 (Bridge)
现实生活中的例子
考虑你有一个包含不同页面的网站,你应该允许用户更改主题。你会怎么做?为每个主题创建每个页面的多个副本,还是只创建单独的主题并根据用户的偏好加载它们?桥接模式允许你做第二种选择。
通俗地说
桥接模式的核心是"组合优于继承"。实现细节从一个层次结构被推送到具有独立层次结构的另一个对象中。
维基百科说
桥接模式是软件工程中使用的一种设计模式,旨在"将抽象与其实现解耦,使两者可以独立变化"。
代码示例
转换上面的网页(WebPage)例子。这里我们有 WebPage 层次结构:
java
public interface WebPage {
String getContent();
}
public class About implements WebPage {
protected Theme theme;
public About(Theme theme) {
this.theme = theme;
}
@Override
public String getContent() {
return "About page in " + this.theme.getColor();
}
}
public class Careers implements WebPage {
protected Theme theme;
public Careers(Theme theme) {
this.theme = theme;
}
@Override
public String getContent() {
return "Careers page in " + this.theme.getColor();
}
}
以及独立的主题(Theme)层次结构:
java
public interface Theme {
String getColor();
}
public class DarkTheme implements Theme {
@Override
public String getColor() {
return "Dark Black";
}
}
public class LightTheme implements Theme {
@Override
public String getColor() {
return "Off white";
}
}
public class AquaTheme implements Theme {
@Override
public String getColor() {
return "Light blue";
}
}
将两个层次结构结合使用:
java
Theme darkTheme = new DarkTheme();
WebPage about = new About(darkTheme);
WebPage careers = new Careers(darkTheme);
System.out.println(about.getContent()); // "About page in Dark Black"
System.out.println(careers.getContent()); // "Careers page in Dark Black"
组合模式 (Composite)
现实生活中的例子
每个组织都是由员工组成的。每个员工都有相同的特征,即有薪水、有一些职责、可能向某人汇报或不汇报、可能有下属或没有下属等。
通俗地说
组合模式让客户端以统一的方式处理单个对象和对象组合。
维基百科说
在软件工程中,组合模式是一种分区设计模式。组合模式描述了一组对象将与单个对象实例以相同的方式对待。组合的意图是将对象"组合"成树形结构以表示"部分-整体"的层次结构。实现组合模式使客户端能够统一对待单个对象和组合对象。
代码示例
沿用上面的员工例子。这里我们有不同的员工类型:
java
import java.util.List;
public interface Employee {
String getName();
void setSalary(float salary);
float getSalary();
List<String> getRoles();
}
public class Developer implements Employee {
protected float salary;
protected String name;
protected List<String> roles;
public Developer(String name, float salary) {
this.name = name;
this.salary = salary;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setSalary(float salary) {
this.salary = salary;
}
@Override
public float getSalary() {
return this.salary;
}
@Override
public List<String> getRoles() {
return this.roles;
}
}
public class Designer implements Employee {
protected float salary;
protected String name;
protected List<String> roles;
public Designer(String name, float salary) {
this.name = name;
this.salary = salary;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setSalary(float salary) {
this.salary = salary;
}
@Override
public float getSalary() {
return this.salary;
}
@Override
public List<String> getRoles() {
return this.roles;
}
}
然后我们有一个由几种不同类型员工组成的组织(Organization):
java
import java.util.ArrayList;
import java.util.List;
public class Organization {
protected List<Employee> employees = new ArrayList<>();
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
public float getNetSalaries() {
float netSalary = 0;
for (Employee employee : this.employees) {
netSalary += employee.getSalary();
}
return netSalary;
}
}
使用方式如下:
java
// 准备员工
Employee john = new Developer("John Doe", 12000);
Employee jane = new Designer("Jane Doe", 15000);
// 将他们添加到组织中
Organization organization = new Organization();
organization.addEmployee(john);
organization.addEmployee(jane);
System.out.println("Net salaries: " + organization.getNetSalaries()); // Net salaries: 27000.0
装饰器模式 (Decorator)
现实生活中的例子
想象一下你经营着一家提供多种服务的汽车维修店。现在你如何计算要收取的账单?你先选择一项基础服务,然后动态地不断将所提供服务的价格累加进去,直到得出最终费用。这里的每种附加服务都是一个装饰器。
通俗地说
装饰器模式允许你通过将对象包装在装饰器类的对象中,在运行时动态更改对象的行为。
维基百科说
在面向对象编程中,装饰器模式是一种设计模式,它允许静态或动态地向单个对象添加行为,而不会影响同一类的其他对象的行为。装饰器模式通常有助于遵循"单一职责原则",因为它允许在具有独特关注领域的类之间划分功能。
代码示例
让我们以咖啡为例。首先,我们有一个实现咖啡接口的简单咖啡:
java
public interface Coffee {
int getCost();
String getDescription();
}
public class SimpleCoffee implements Coffee {
@Override
public int getCost() {
return 10;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
我们希望使代码具有可扩展性,以便在需要时允许添加选项来修改它。让我们制作一些配料(装饰器):
java
public class MilkCoffee implements Coffee {
protected Coffee coffee;
public MilkCoffee(Coffee coffee) {
this.coffee = coffee;
}
@Override
public int getCost() {
return this.coffee.getCost() + 2;
}
@Override
public String getDescription() {
return this.coffee.getDescription() + ", milk";
}
}
public class WhipCoffee implements Coffee {
protected Coffee coffee;
public WhipCoffee(Coffee coffee) {
this.coffee = coffee;
}
@Override
public int getCost() {
return this.coffee.getCost() + 5;
}
@Override
public String getDescription() {
return this.coffee.getDescription() + ", whip";
}
}
public class VanillaCoffee implements Coffee {
protected Coffee coffee;
public VanillaCoffee(Coffee coffee) {
this.coffee = coffee;
}
@Override
public int getCost() {
return this.coffee.getCost() + 3;
}
@Override
public String getDescription() {
return this.coffee.getDescription() + ", vanilla";
}
}
现在让我们来调制一杯咖啡:
java
Coffee someCoffee = new SimpleCoffee();
System.out.println(someCoffee.getCost()); // 10
System.out.println(someCoffee.getDescription()); // Simple coffee
someCoffee = new MilkCoffee(someCoffee);
System.out.println(someCoffee.getCost()); // 12
System.out.println(someCoffee.getDescription()); // Simple coffee, milk
someCoffee = new WhipCoffee(someCoffee);
System.out.println(someCoffee.getCost()); // 17
System.out.println(someCoffee.getDescription()); // Simple coffee, milk, whip
someCoffee = new VanillaCoffee(someCoffee);
System.out.println(someCoffee.getCost()); // 20
System.out.println(someCoffee.getDescription()); // Simple coffee, milk, whip, vanilla
外观模式 (Facade)
现实生活中的例子
你是如何打开电脑的?你说"按电源按钮"!你之所以这么认为,是因为你正在使用电脑在外部提供的简单接口,而在内部,它必须做很多事情才能实现开机。这个通向复杂子系统的简单接口就是一个外观(Facade)。
通俗地说
外观模式为复杂的子系统提供了一个简化的接口。
维基百科说
外观是一个对象,它为更大的代码体(例如类库)提供简化的接口。
代码示例
沿用上面的电脑例子。这里我们有复杂的 Computer 类:
java
public class Computer {
public void getElectricShock() {
System.out.println("Ouch!");
}
public void makeSound() {
System.out.println("Beep beep!");
}
public void showLoadingScreen() {
System.out.println("Loading..");
}
public void bam() {
System.out.println("Ready to be used!");
}
public void closeEverything() {
System.out.println("Bup bup bup buzzzz!");
}
public void sooth() {
System.out.println("Zzzzz");
}
public void pullCurrent() {
System.out.println("Haaah!");
}
}
这里我们有外观类:
java
public class ComputerFacade {
protected Computer computer;
public ComputerFacade(Computer computer) {
this.computer = computer;
}
public void turnOn() {
this.computer.getElectricShock();
this.computer.makeSound();
this.computer.showLoadingScreen();
this.computer.bam();
}
public void turnOff() {
this.computer.closeEverything();
this.computer.pullCurrent();
this.computer.sooth();
}
}
现在使用外观类:
java
ComputerFacade computer = new ComputerFacade(new Computer());
computer.turnOn(); // Ouch! Beep beep! Loading.. Ready to be used!
computer.turnOff(); // Bup bup bup buzzzz! Haaah! Zzzzz
享元模式 (Flyweight)
现实生活中的例子
你曾经在路边摊喝过现煮的茶吗?他们通常会煮比你点的多出好几杯的茶,并把剩下的留给其他顾客,以节省资源(如煤气等)。享元模式的核心就是"共享"。
通俗地说
它通过与相似对象尽可能多地共享数据,来最大限度地减少内存使用或计算开销。
维基百科说
在计算机编程中,享元是一种软件设计模式。享元是一个通过与其他相似对象共享尽可能多的数据来最小化内存使用的对象;当简单的重复表示会占用不可接受的内存量时,这是一种大量使用对象的方法。
代码示例
转换上面的茶摊例子。首先,我们有茶的类型(享元)和泡茶师(工厂):
java
import java.util.HashMap;
import java.util.Map;
// 任何将被缓存的东西都是享元。
// 这里的茶类型就是享元。
public class KarakTea {
}
// 充当工厂并保存茶的实例
public class TeaMaker {
protected Map<String, KarakTea> availableTea = new HashMap<>();
public KarakTea make(String preference) {
if (!availableTea.containsKey(preference)) {
availableTea.put(preference, new KarakTea());
}
return availableTea.get(preference);
}
}
然后我们有负责接单和上茶的 TeaShop(茶馆):
java
import java.util.HashMap;
import java.util.Map;
public class TeaShop {
protected Map<Integer, KarakTea> orders = new HashMap<>();
protected TeaMaker teaMaker;
public TeaShop(TeaMaker teaMaker) {
this.teaMaker = teaMaker;
}
public void takeOrder(String teaType, int table) {
this.orders.put(table, this.teaMaker.make(teaType));
}
public void serve() {
for (Map.Entry<Integer, KarakTea> entry : this.orders.entrySet()) {
System.out.println("Serving tea to table# " + entry.getKey());
}
}
}
使用方式如下:
java
TeaMaker teaMaker = new TeaMaker();
TeaShop shop = new TeaShop(teaMaker);
shop.takeOrder("less sugar", 1);
shop.takeOrder("more milk", 2);
shop.takeOrder("without sugar", 5);
shop.serve();
// Serving tea to table# 1
// Serving tea to table# 2
// Serving tea to table# 5
代理模式 (Proxy)
现实生活中的例子
你曾经用过门禁卡通过一扇门吗?打开那扇门有多种选择,既可以使用门禁卡打开,也可以按下一个绕过安全验证的按钮打开。门的主要功能是打开,但在它上面添加了一个代理以增加一些安全控制功能。让我用下面的代码示例更好地解释它。
通俗地说
使用代理模式,一个类代表另一个类的功能。
维基百科说
代理,以其最一般的形式,是一个充当其他事物接口的类。代理是客户端为了访问幕后真正提供服务的对象而调用的包装器或代理对象。代理的使用可以仅仅是转发给真实对象,也可以提供额外的逻辑。在代理中可以提供额外的功能,例如当真实对象的操作耗费资源时进行缓存,或者在调用真实对象的操作之前检查前置条件。
代码示例
沿用上面的安全门例子。首先我们有门的接口和门的实现:
java
public interface Door {
void open();
void close();
}
public class LabDoor implements Door {
@Override
public void open() {
System.out.println("Opening lab door");
}
@Override
public void close() {
System.out.println("Closing the lab door");
}
}
然后我们有一个代理来保护我们想要的任何门:
java
public class SecuredDoor {
protected Door door;
public SecuredDoor(Door door) {
this.door = door;
}
public void open(String password) {
if (authenticate(password)) {
this.door.open();
} else {
System.out.println("Big no! It ain't possible.");
}
}
public boolean authenticate(String password) {
return "$ecr@t".equals(password);
}
public void close() {
this.door.close();
}
}
使用方式如下:
java
SecuredDoor door = new SecuredDoor(new LabDoor());
door.open("invalid"); // Big no! It ain't possible.
door.open("$ecr@t"); // Opening lab door
door.close(); // Closing lab door
另一个例子是某种数据映射器(Data-Mapper)的实现。例如,在 Java 中使用动态代理(Dynamic Proxy)围绕数据库操作类编写代理:所有方法调用都会被代理到原始类并按原样返回结果,但在执行 find 或 findOne 时,数据会被映射到所需的类对象并返回该对象,而不是原始的数据库游标(Cursor)。
行为型设计模式
通俗地说
它关注对象之间的职责分配。它们与结构型模式的不同之处在于,它们不仅指定结构,还概述了它们之间消息传递/通信的模式。或者换句话说,它们有助于回答"如何在软件组件中运行行为?"
维基百科说
在软件工程中,行为型设计模式是识别对象之间常见通信模式并实现这些模式的设计模式。通过这样做,这些patterns增加了执行这种通信的灵活性。
责任链模式 (Chain of Responsibility)
现实生活中的例子
例如,你的账户中设置了三种支付方式(
A、B和C),每种方式都有不同的余额。A有 100 美元,B有 300 美元,C有 1000 美元,支付优先级选择为先A后B再C。你尝试购买价值 210 美元的商品。使用责任链模式,首先会检查账户A是否可以支付, if 可以,则完成支付并中断链条。如果不行,请求将向前移动到账户B检查余额,如果足够则中断链条,否则请求将继续向前传递,直到找到合适的处理者。这里的A、B和C就是链条的环节,整个现象就是责任链。
通俗地说
它有助于构建一个对象链。请求从一端进入,并从一个对象传递到另一个对象,直到找到合适的处理者。
维基百科说
在面向对象设计中,责任链模式是由命令对象源和一系列处理对象组成的设计模式。每个处理对象都包含定义它可以处理的命令对象类型的逻辑;其余的则传递给链中的下一个处理对象。
代码示例
转换上面的账户例子。首先,我们有一个基础账户类(包含将账户链接在一起的逻辑)和一些具体账户类:
java
public abstract class Account {
protected Account successor;
protected float balance;
public void setNext(Account account) {
this.successor = account;
}
public void pay(float amountToPay) throws Exception {
if (canPay(amountToPay)) {
System.out.printf("Paid %s using %s%n", amountToPay, this.getClass().getSimpleName());
} else if (this.successor != null) {
System.out.printf("Cannot pay using %s. Proceeding ..%n", this.getClass().getSimpleName());
this.successor.pay(amountToPay);
} else {
throw new Exception("None of the accounts have enough balance");
}
}
public boolean canPay(float amount) {
return this.balance >= amount;
}
}
public class Bank extends Account {
public Bank(float balance) {
this.balance = balance;
}
}
public class Paypal extends Account {
public Paypal(float balance) {
this.balance = balance;
}
}
public class Bitcoin extends Account {
public Bitcoin(float balance) {
this.balance = balance;
}
}
现在让我们使用上面定义的环节(即 Bank、Paypal、Bitcoin)准备责任链:
java
// 准备如下所示的责任链
// bank -> paypal -> bitcoin
//
// 第一优先级:银行 (bank)
// 如果银行无法支付,则尝试 Paypal
// 如果 Paypal 无法支付,则尝试比特币 (bitcoin)
Account bank = new Bank(100); // 余额 100 的银行账户
Account paypal = new Paypal(200); // 余额 200 的 Paypal 账户
Account bitcoin = new Bitcoin(300); // 余额 300 的比特币账户
bank.setNext(paypal);
paypal.setNext(bitcoin);
// 尝试使用第一优先级(银行)进行支付
try {
bank.pay(259);
} catch (Exception e) {
System.out.println(e.getMessage());
}
// 输出结果:
// ==============
// Cannot pay using Bank. Proceeding ..
// Cannot pay using Paypal. Proceeding ..
// Paid 259.0 using Bitcoin
命令模式 (Command)
现实生活中的例子
一个常见的例子是你在餐厅点餐。你(即
Client客户端)让服务员(即Invoker调用者)端来一些食物(即Command命令),服务员只是将请求转发给厨师(即Receiver接收者),厨师知道烹饪什么以及如何烹饪。另一个例子是你(即
Client)使用遥控器(Invoker)打开(即Command)电视(即Receiver)。
通俗地说
允许你将操作封装在对象中。此模式背后的核心思想是提供将客户端与接收者解耦的方法。
维基百科说
在面向对象编程中,命令模式是一种行为型设计模式,其中对象用于封装在稍后时间执行操作或触发事件所需的所有信息。这些信息包括方法名称、拥有该方法的对象以及方法参数的值。
代码示例
首先,我们有包含所有可执行操作具体实现的接收者(Receiver):
java
// 接收者 (Receiver)
public class Bulb {
public void turnOn() {
System.out.println("Bulb has been lit");
}
public void turnOff() {
System.out.println("Darkness!");
}
}
然后我们有一个每个命令都要实现的接口,以及一组具体命令:
java
public interface Command {
void execute();
void undo();
void redo();
}
// 命令 (Command)
public class TurnOn implements Command {
protected Bulb bulb;
public TurnOn(Bulb bulb) {
this.bulb = bulb;
}
@Override
public void execute() {
this.bulb.turnOn();
}
@Override
public void undo() {
this.bulb.turnOff();
}
@Override
public void redo() {
this.execute();
}
}
public class TurnOff implements Command {
protected Bulb bulb;
public TurnOff(Bulb bulb) {
this.bulb = bulb;
}
@Override
public void execute() {
this.bulb.turnOff();
}
@Override
public void undo() {
this.bulb.turnOn();
}
@Override
public void redo() {
this.execute();
}
}
然后我们有一个 Invoker(调用者),客户端将与其交互以处理任何命令:
java
// 调用者 (Invoker)
public class RemoteControl {
public void submit(Command command) {
command.execute();
}
}
最后让我们看看如何在客户端中使用它:
java
Bulb bulb = new Bulb();
Command turnOn = new TurnOn(bulb);
Command turnOff = new TurnOff(bulb);
RemoteControl remote = new RemoteControl();
remote.submit(turnOn); // Bulb has been lit
remote.submit(turnOff); // Darkness!
命令模式还可用于实现基于事务的系统。一旦执行命令,你就持续维护命令的历史记录。如果最终命令成功执行,一切顺利;否则只需遍历历史记录并对所有已执行的命令依次执行 undo(撤销)即可。
迭代器模式 (Iterator)
现实生活中的例子
老式收音机是迭代器的一个好例子,用户可以从某个频道开始,然后使用"下一个"或"上一个"按钮遍历相应的频道。或者以 MP3 播放器或电视机为例,你可以按"下一个"和"上一个"按钮来浏览连续的频道,换句话说,它们都提供了一个遍历各自频道、歌曲或广播电台的接口。
通俗地说
它提供了一种访问对象元素而不暴露其底层表示的方法。
维基百科说
在面向对象编程中,迭代器模式是一种设计模式,其中使用迭代器遍历容器并访问容器的元素。迭代器模式将算法与容器解耦;在某些情况下,算法必然是特定于容器的,因此无法解耦。
代码示例
在 Java 中,可以通过实现标准的 java.util.Iterator 接口轻松实现(甚至可以直接支持增强 for 循环)。转换上面的广播电台例子,首先我们有 RadioStation:
java
public class RadioStation {
protected float frequency;
public RadioStation(float frequency) {
this.frequency = frequency;
}
public float getFrequency() {
return this.frequency;
}
}
然后我们有电台列表(实现 Iterable<RadioStation> 接口):
java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class StationList implements Iterable<RadioStation> {
protected List<RadioStation> stations = new ArrayList<>();
public void addStation(RadioStation station) {
this.stations.add(station);
}
public void removeStation(RadioStation toRemove) {
float toRemoveFrequency = toRemove.getFrequency();
this.stations.removeIf(station -> station.getFrequency() == toRemoveFrequency);
}
public int count() {
return this.stations.size();
}
@Override
public Iterator<RadioStation> iterator() {
return new StationIterator();
}
private class StationIterator implements Iterator<RadioStation> {
private int counter = 0;
@Override
public boolean hasNext() {
return counter < stations.size();
}
@Override
public RadioStation next() {
return stations.get(counter++);
}
}
}
使用方式如下:
java
StationList stationList = new StationList();
stationList.addStation(new RadioStation(89));
stationList.addStation(new RadioStation(101));
stationList.addStation(new RadioStation(102));
stationList.addStation(new RadioStation(103.2f));
for (RadioStation station : stationList) {
System.out.println(station.getFrequency());
}
stationList.removeStation(new RadioStation(89)); // 将移除 89 频道
中介者模式 (Mediator)
现实生活中的例子
一个常见的例子是,当你用手机与某人通话时,你和他们之间有一个网络运营商,你的对话是通过它传递的,而不是直接发送的。在这种情况下,网络运营商就是中介者。
通俗地说
中介者模式添加一个第三方对象(称为中介者)来控制两个对象(称为同事)之间的交互。它有助于减少彼此通信的类之间的耦合度,因为现在它们不再需要了解彼此的实现细节。
维基百科说
在软件工程中,中介者模式定义了一个封装一组对象如何交互的对象。由于该模式可以改变程序的运行行为,因此被认为是一种行为型模式。
代码示例
下面是聊天室(即中介者)与用户(即同事)互相发送消息的最简单示例。
首先,我们有中介者,即聊天室:
java
import java.text.SimpleDateFormat;
import java.util.Date;
public interface ChatRoomMediator {
void showMessage(User user, String message);
}
// 中介者
public class ChatRoom implements ChatRoomMediator {
@Override
public void showMessage(User user, String message) {
String time = new SimpleDateFormat("MMM dd, yy HH:mm").format(new Date());
String sender = user.getName();
System.out.println(time + " [" + sender + "]: " + message);
}
}
然后我们有用户,即同事类:
java
public class User {
protected String name;
protected ChatRoomMediator chatMediator;
public User(String name, ChatRoomMediator chatMediator) {
this.name = name;
this.chatMediator = chatMediator;
}
public String getName() {
return this.name;
}
public void send(String message) {
this.chatMediator.showMessage(this, message);
}
}
使用方式:
java
ChatRoomMediator mediator = new ChatRoom();
User john = new User("John Doe", mediator);
User jane = new User("Jane Doe", mediator);
john.send("Hi there!");
jane.send("Hey!");
// 输出结果:
// Feb 14, 26 10:58 [John Doe]: Hi there!
// Feb 14, 26 10:58 [Jane Doe]: Hey!
备忘录模式 (Memento)
现实生活中的例子
以计算器(即发起人 Originator)为例,每当你执行某些计算时,最后一次计算结果都会保存在内存中(即备忘录 Memento),以便你可以回到该状态,并可能使用某些操作按钮(即负责人 Caretaker)将其恢复。
通俗地说
备忘录模式的核心是捕获并存储对象的当前状态,以便日后能够以平滑的方式将其恢复。
维基百科说
备忘录模式是一种软件设计模式,它提供了将对象恢复到其先前状态(通过回滚撤销)的能力。
通常在需要提供某种"撤销(Undo)"功能时非常有用。
代码示例
让我们以一个不断保存状态的文本编辑器为例,如果你愿意,可以随时恢复它。
首先,我们有能够保存编辑器状态的备忘录对象:
java
public class EditorMemento {
protected String content;
public EditorMemento(String content) {
this.content = content;
}
public String getContent() {
return this.content;
}
}
然后我们有将使用备忘录对象的编辑器(即发起人):
java
public class Editor {
protected String content = "";
public void type(String words) {
this.content = this.content + " " + words;
}
public String getContent() {
return this.content;
}
public EditorMemento save() {
return new EditorMemento(this.content);
}
public void restore(EditorMemento memento) {
this.content = memento.getContent();
}
}
使用方式如下:
java
Editor editor = new Editor();
// 输入一些内容
editor.type("This is the first sentence.");
editor.type("This is second.");
// 保存状态以便日后恢复:This is the first sentence. This is second.
EditorMemento saved = editor.save();
// 再输入一些内容
editor.type("And this is third.");
// 输出:保存前的内容
System.out.println(editor.getContent()); // This is the first sentence. This is second. And this is third.
// 恢复到上次保存的状态
editor.restore(saved);
System.out.println(editor.getContent()); // This is the first sentence. This is second.
观察者模式 (Observer)
现实生活中的例子
一个很好的例子是求职者,他们订阅了某些招聘网站,每当有匹配的职位空缺时,他们就会收到通知。
通俗地说
定义对象之间的一种依赖关系,以便每当一个对象改变其状态时,所有依赖它的对象都会收到通知。
维基百科说
观察者模式是一种软件设计模式,其中称为"主题(Subject)"的对象维护一个依赖它的对象列表(称为"观察者 Observers"),并在任何状态更改时自动通知它们(通常通过调用它们的方法之一)。
代码示例
转换上面的求职例子。首先我们有在发布职位时需要被通知的求职者:
java
public class JobPost {
protected String title;
public JobPost(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
}
public interface Observer {
void onJobPosted(JobPost job);
}
public class JobSeeker implements Observer {
protected String name;
public JobSeeker(String name) {
this.name = name;
}
@Override
public void onJobPosted(JobPost job) {
// 处理新职位发布通知
System.out.println("Hi " + this.name + "! New job posted: " + job.getTitle());
}
}
然后我们有求职者将要订阅的招聘机构(发布者):
java
import java.util.ArrayList;
import java.util.List;
public interface Observable {
void attach(Observer observer);
void addJob(JobPost jobPosting);
}
public class EmploymentAgency implements Observable {
protected List<Observer> observers = new ArrayList<>();
protected void notifyObservers(JobPost jobPosting) {
for (Observer observer : this.observers) {
observer.onJobPosted(jobPosting);
}
}
@Override
public void attach(Observer observer) {
this.observers.add(observer);
}
@Override
public void addJob(JobPost jobPosting) {
this.notifyObservers(jobPosting);
}
}
使用方式如下:
java
// 创建订阅者
Observer johnDoe = new JobSeeker("John Doe");
Observer janeDoe = new JobSeeker("Jane Doe");
// 创建发布者并添加订阅者
Observable jobPostings = new EmploymentAgency();
jobPostings.attach(johnDoe);
jobPostings.attach(janeDoe);
// 添加新职位,查看订阅者是否收到通知
jobPostings.addJob(new JobPost("Software Engineer"));
// 输出:
// Hi John Doe! New job posted: Software Engineer
// Hi Jane Doe! New job posted: Software Engineer
访问者模式 (Visitor)
现实生活中的例子
考虑有人去迪拜旅游。他们只需要一种方式(即签证)进入迪拜。抵达后,他们可以自己去参观迪拜的任何地方,而无需请求许可或做任何跑腿工作;只要告诉他们一个地方,他们就可以去参观。访问者模式就能让你做到这一点,它帮助你添加可供参观的地方,以便他们可以尽可能多地参观而无需费力修改原有结构。
通俗地说
访问者模式允许你在不修改对象的前提下,向对象添加进一步的操作。
维基百科说
在面向对象编程和软件工程中,访问者设计模式是一种将算法与其所操作的对象结构分离的方法。这种分离的实际结果是能够在不修改现有对象结构的情况下向其添加新操作。这是遵循"开闭原则(Open/Closed Principle)"的一种方式。
代码示例
让我们以动物园模拟为例,其中有几种不同种类的动物,我们必须让它们发出声音。让我们使用访问者模式来编写:
java
// 被访问者 (Visitee)
public interface Animal {
void accept(AnimalOperation operation);
}
// 访问者 (Visitor)
public interface AnimalOperation {
void visitMonkey(Monkey monkey);
void visitLion(Lion lion);
void visitDolphin(Dolphin dolphin);
}
然后我们有动物的具体实现:
java
public class Monkey implements Animal {
public void shout() {
System.out.println("Ooh oo aa aa!");
}
@Override
public void accept(AnimalOperation operation) {
operation.visitMonkey(this);
}
}
public class Lion implements Animal {
public void roar() {
System.out.println("Roaaar!");
}
@Override
public void accept(AnimalOperation operation) {
operation.visitLion(this);
}
}
public class Dolphin implements Animal {
public void speak() {
System.out.println("Tuut tuttu tuutt!");
}
@Override
public void accept(AnimalOperation operation) {
operation.visitDolphin(this);
}
}
让我们实现具体的访问者(Speak):
java
public class Speak implements AnimalOperation {
@Override
public void visitMonkey(Monkey monkey) {
monkey.shout();
}
@Override
public void visitLion(Lion lion) {
lion.roar();
}
@Override
public void visitDolphin(Dolphin dolphin) {
dolphin.speak();
}
}
使用方式如下:
java
Animal monkey = new Monkey();
Animal lion = new Lion();
Animal dolphin = new Dolphin();
AnimalOperation speak = new Speak();
monkey.accept(speak); // Ooh oo aa aa!
lion.accept(speak); // Roaaar!
dolphin.accept(speak); // Tuut tuttu tuutt!
我们本可以通过为动物建立继承层次结构来简单地实现这一点,但那样的话,每当必须为动物添加新动作时,我们就必须修改动物类。但现在我们无需修改它们。例如,假设我们被要求为动物添加跳跃(Jump)行为,我们只需创建一个新的访问者即可添加该行为:
java
public class Jump implements AnimalOperation {
@Override
public void visitMonkey(Monkey monkey) {
System.out.println("Jumped 20 feet high! on to the tree!");
}
@Override
public void visitLion(Lion lion) {
System.out.println("Jumped 7 feet! Back on the ground!");
}
@Override
public void visitDolphin(Dolphin dolphin) {
System.out.println("Walked on water a little and disappeared");
}
}
使用方式:
java
AnimalOperation jump = new Jump();
monkey.accept(speak); // Ooh oo aa aa!
monkey.accept(jump); // Jumped 20 feet high! on to the tree!
lion.accept(speak); // Roaaar!
lion.accept(jump); // Jumped 7 feet! Back on the ground!
dolphin.accept(speak); // Tuut tuttu tuutt!
dolphin.accept(jump); // Walked on water a little and disappeared
策略模式 (Strategy)
现实生活中的例子
考虑排序的例子, we 实现了冒泡排序,但随着数据量开始增长,冒泡排序变得非常慢。为了解决这个问题,我们实现了快速排序。但现在虽然快速排序算法在大型数据集上表现更好,但对于小型数据集却很慢(相对开销大)。为了解决这个问题,我们实施了一种策略:对于小型数据集使用冒泡排序,对于大型数据集使用快速排序。
通俗地说
策略模式允许你根据情况切换算法或策略。
维基百科说
在计算机编程中,策略模式(也称为政策模式 Policy Pattern)是一种行为型软件设计模式,它使算法的行为能够在运行时进行选择。
代码示例
转换上面的排序例子。首先,我们有策略接口和不同的策略实现:
java
public interface SortStrategy {
int[] sort(int[] dataset);
}
public class BubbleSortStrategy implements SortStrategy {
@Override
public int[] sort(int[] dataset) {
System.out.println("Sorting using bubble sort");
// 执行排序逻辑
return dataset;
}
}
public class QuickSortStrategy implements SortStrategy {
@Override
public int[] sort(int[] dataset) {
System.out.println("Sorting using quick sort");
// 执行排序逻辑
return dataset;
}
}
然后我们有将要使用策略的客户端类:
java
public class Sorter {
protected SortStrategy sorterSmall;
protected SortStrategy sorterBig;
public Sorter(SortStrategy sorterSmall, SortStrategy sorterBig) {
this.sorterSmall = sorterSmall;
this.sorterBig = sorterBig;
}
public int[] sort(int[] dataset) {
if (dataset.length > 5) {
return this.sorterBig.sort(dataset);
} else {
return this.sorterSmall.sort(dataset);
}
}
}
使用方式如下:
java
int[] smallDataset = {1, 3, 4, 2};
int[] bigDataset = {1, 4, 3, 2, 8, 10, 5, 6, 9, 7};
Sorter sorter = new Sorter(new BubbleSortStrategy(), new QuickSortStrategy());
sorter.sort(smallDataset); // 输出: Sorting using bubble sort
sorter.sort(bigDataset); // 输出: Sorting using quick sort
状态模式 (State)
现实生活中的例子
想象一下你正在使用某个绘图应用程序,你选择了画笔进行绘制。现在,画笔会根据所选颜色改变其行为,即如果你选择了红色,它将用红色绘制;如果选择了蓝色,它将用蓝色绘制,依此类推。
通俗地说
它允许你在状态发生变化时改变类的行为。
维基百科说
状态模式是一种以面向对象的方式实现状态机的行为型软件设计模式。在状态模式中,状态机通过将每个单独的状态实现为状态模式接口的派生类,并通过调用模式超类中定义的方法来实现状态转换。
状态模式可以解释为一种策略模式,它能够通过调用模式接口中定义的方法来切换当前策略。
代码示例
让我们以电话为例。首先,我们有状态接口和一些具体状态实现:
java
public interface PhoneState {
PhoneState pickUp() throws Exception;
PhoneState hangUp() throws Exception;
PhoneState dial() throws Exception;
}
// 状态实现
public class PhoneStateIdle implements PhoneState {
@Override
public PhoneState pickUp() {
return new PhoneStatePickedUp();
}
@Override
public PhoneState hangUp() throws Exception {
throw new Exception("already idle");
}
@Override
public PhoneState dial() throws Exception {
throw new Exception("unable to dial in idle state");
}
}
public class PhoneStatePickedUp implements PhoneState {
@Override
public PhoneState pickUp() throws Exception {
throw new Exception("already picked up");
}
@Override
public PhoneState hangUp() {
return new PhoneStateIdle();
}
@Override
public PhoneState dial() {
return new PhoneStateCalling();
}
}
public class PhoneStateCalling implements PhoneState {
@Override
public PhoneState pickUp() throws Exception {
throw new Exception("already picked up");
}
@Override
public PhoneState hangUp() {
return new PhoneStateIdle();
}
@Override
public PhoneState dial() throws Exception {
throw new Exception("already dialing");
}
}
然后我们有 Phone 类,它在不同行为调用时更改状态:
java
public class Phone {
private PhoneState state;
public Phone() {
this.state = new PhoneStateIdle();
}
public void pickUp() throws Exception {
this.state = this.state.pickUp();
}
public void hangUp() throws Exception {
this.state = this.state.hangUp();
}
public void dial() throws Exception {
this.state = this.state.dial();
}
}
使用方式如下,它将调用相关的状态方法:
java
Phone phone = new Phone();
try {
phone.pickUp();
phone.dial();
} catch (Exception e) {
System.out.println(e.getMessage());
}
模板方法模式 (Template Method)
现实生活中的例子
假设我们正在盖房子。建造的步骤可能如下所示:
- 准备房屋地基
- 砌墙
- 加盖屋顶
- 添加其他楼层
这些步骤的顺序永远不能改变(即你不能在砌墙之前先盖屋顶等),但每个步骤的具体实现都可以修改,例如墙壁可以用木头、聚酯材料或石头制成。
通俗地说
模板方法定义了如何执行某种算法的骨架,但将这些步骤的具体实现推迟到子类中。
维基百科说
在软件工程中,模板方法模式是一种行为型设计模式,它在操作中定义算法的程序骨架,将一些步骤推迟到子类中。它允许子类在不改变算法结构的情况下重新定义算法的某些步骤。
代码示例
想象一下,我们有一个构建工具,可以帮助我们测试(test)、代码检查(lint)、组装(assemble)并将应用程序部署(deploy)到测试服务器上。
首先,我们有一个指定构建算法骨架的基类:
java
public abstract class Builder {
// 模板方法 (使用 final 防止子类修改算法骨架顺序)
public final void build() {
this.test();
this.lint();
this.assemble();
this.deploy();
}
public abstract void test();
public abstract void lint();
public abstract void assemble();
public abstract void deploy();
}
然后我们可以有针对不同平台的具体实现:
java
public class AndroidBuilder extends Builder {
@Override
public void test() {
System.out.println("Running android tests");
}
@Override
public void lint() {
System.out.println("Linting the android code");
}
@Override
public void assemble() {
System.out.println("Assembling the android build");
}
@Override
public void deploy() {
System.out.println("Deploying android build to server");
}
}
public class IosBuilder extends Builder {
@Override
public void test() {
System.out.println("Running ios tests");
}
@Override
public void lint() {
System.out.println("Linting the ios code");
}
@Override
public void assemble() {
System.out.println("Assembling the ios build");
}
@Override
public void deploy() {
System.out.println("Deploying ios build to server");
}
}
使用方式如下:
java
Builder androidBuilder = new AndroidBuilder();
androidBuilder.build();
// 输出:
// Running android tests
// Linting the android code
// Assembling the android build
// Deploying android build to server
Builder iosBuilder = new IosBuilder();
iosBuilder.build();
// 输出:
// Running ios tests
// Linting the ios code
// Assembling the ios build
// Deploying ios build to server
总结
以上就是全部内容了。我会继续完善这份指南,所以你可能会想要关注(watch)或收藏(star)这个仓库以便日后查阅。此外,我还计划撰写关于架构模式的同类指南,敬请期待。
附录:设计模式在 JDK 与 Spring 中的经典应用
本附录在前文 23 种设计模式的概念和示例基础上,进一步对照 JDK 标准库与 Spring 框架中的常见实现。阅读时需要注意:设计模式描述的是一种设计意图和协作关系,并不意味着某个类只要出现了相似的方法或结构,就一定是该模式的标准实现。部分框架示例属于模式思想的体现、局部应用或近似对应,具体应结合源码中的职责分配和对象协作关系判断。
一、创建型模式(Creational)
创建型模式主要关注对象如何创建,以及如何将对象创建过程与使用过程分离。
1. 简单工厂模式(Simple Factory)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.text.DateFormat.getDateInstance() |
根据日期格式样式和地区信息,返回相应的日期格式化对象。调用方不必直接选择具体实现类。 |
| JDK | javax.crypto.KeyGenerator.getInstance() |
根据算法名称等信息获取相应的密钥生成器实现。具体实现由安全提供者机制参与选择。 |
| Spring | BeanFactory.getBean() |
调用方根据 Bean 名称或类型从容器中获取对象,Bean 的创建和管理由容器负责。 |
原理说明
简单工厂将对象创建逻辑集中到一个工厂入口中。客户端只需要提供必要参数,工厂根据参数、配置或内部规则决定创建哪一种具体对象,并将抽象类型或共同接口返回给客户端。
它解决的主要问题是:客户端不必在业务代码中散布大量 new 操作,也不必了解具体实现类的构造细节。以后增加或替换实现时,可以优先修改工厂内部逻辑,而不是逐个修改所有调用位置。
JDK 示例理解
DateFormat.getDateInstance() 是静态工厂式入口。调用方可以指定日期样式和地区信息,例如:
java
DateFormat format = DateFormat.getDateInstance(
DateFormat.SHORT,
Locale.CHINA
);
String result = format.format(new Date());
调用方依赖 DateFormat 抽象,而不是自行判断并实例化某个具体格式化实现。KeyGenerator.getInstance("AES") 也体现了通过统一入口按算法获取实现的思想。
Spring 示例理解
java
MyService service = beanFactory.getBean("myService", MyService.class);
调用方通过 BeanFactory 获取对象,而不是直接执行 new MyService()。不过,BeanFactory 本身是 IoC 容器的核心接口,功能远超一个只负责分支创建对象的简单工厂;将 getBean() 视为简单工厂思想的体现更准确,不宜认为整个 BeanFactory 只是一种简单工厂。
适用场景与注意事项
当对象创建包含条件判断、配置选择、依赖组装或统一初始化逻辑时,可以考虑简单工厂。若产品种类持续增加,工厂内部可能出现大量 if-else 或 switch,此时可以进一步考虑工厂方法、抽象工厂,或者使用 Spring 容器管理对象。
2. 工厂方法模式(Factory Method)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.util.Collection.iterator() |
集合接口将迭代器创建入口交给具体集合实现,具体集合返回适合自身数据结构的迭代器。 |
| JDK | java.net.URLStreamHandlerFactory |
工厂接口负责为协议创建相应的 URLStreamHandler,具体工厂决定处理器实现。 |
| Spring | FactoryBean.getObject() |
FactoryBean 通过 getObject() 提供由工厂创建的对象,容器负责管理该工厂 Bean。 |
| Spring | ApplicationContext 的各类子类实现 |
不同上下文实现可以在统一抽象流程中提供不同的资源、环境或上下文实现细节;是否构成典型工厂方法,需要结合具体扩展点判断。 |
原理说明
工厂方法模式定义创建产品的接口,但不在统一父类中写死具体产品类型,而是由子类或具体工厂实现创建方法。客户端面向产品接口编程,具体创建哪一种产品由工厂实现决定。
它与简单工厂的区别在于:简单工厂通常由一个工厂对象根据参数集中选择产品;工厂方法则把创建行为分散到不同的工厂实现中,通常通过继承或接口实现来扩展产品创建方式。
JDK 示例理解
Collection.iterator() 由具体集合类型提供适合其内部结构的迭代器。例如,ArrayList 和 LinkedList 都可以通过 iterator() 返回 Iterator,但具体迭代器实现和遍历方式由集合自身决定。
java
Collection<String> values = new ArrayList<>();
values.add("A");
values.add("B");
Iterator<String> iterator = values.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
调用方只依赖 Iterator 接口,不需要知道集合内部如何创建迭代器。
Spring 示例理解
FactoryBean<T> 是 Spring 中非常典型的工厂扩展点:
java
public class ClientFactoryBean implements FactoryBean<Client> {
@Override
public Client getObject() {
return new Client();
}
@Override
public Class<?> getObjectType() {
return Client.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
容器管理 ClientFactoryBean,而从容器中按名称获取时,通常得到的是 getObject() 创建的产品对象。若要获取工厂本身,可以在 Bean 名称前使用 & 前缀。
需要区分:FactoryBean 是 Spring 提供的工厂 Bean 机制;它的 getObject() 体现工厂方法思想,但并不意味着所有使用 FactoryBean 的场景都必须严格套入 GoF 工厂方法模式的类图。
适用场景与注意事项
当创建逻辑需要由不同实现分别负责,并且希望新增产品时尽量不修改既有客户端代码,可以使用工厂方法。代价是通常会增加具体工厂类的数量。
3. 抽象工厂模式(Abstract Factory)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | javax.xml.parsers.DocumentBuilderFactory |
创建与 XML 解析相关的 DocumentBuilder,将具体解析器实现的选择交给工厂机制。 |
| JDK | javax.xml.transform.TransformerFactory |
创建 Transformer 等 XML 转换相关对象,具体实现可由提供者机制决定。 |
| Spring | BeanFactory 体系 |
从较高层次看,IoC 容器能够管理并提供多种 Bean;但 BeanFactory 并不是 GoF 抽象工厂模式的严格标准实现,这里主要对应其统一创建和提供对象的思想。 |
原理说明
抽象工厂提供一组创建相关产品的接口。一个具体工厂负责创建同一产品族中的多个产品,客户端通过抽象工厂获取产品,不必知道具体产品类。
关键不在于"工厂可以创建很多对象",而在于这些对象之间存在产品族关系。例如,Windows 风格工厂创建 Windows 按钮和 Windows 文本框,Mac 风格工厂创建 Mac 按钮和 Mac 文本框。使用同一个具体工厂,可以保证创建出来的产品属于同一套风格或兼容体系。
示例
java
interface Button {
void render();
}
interface TextField {
void render();
}
interface UiFactory {
Button createButton();
TextField createTextField();
}
class WindowsUiFactory implements UiFactory {
public Button createButton() {
return new WindowsButton();
}
public TextField createTextField() {
return new WindowsTextField();
}
}
客户端依赖 UiFactory,并从同一个工厂取得相关产品。切换产品族时,替换具体工厂即可。
Spring 示例理解
Spring IoC 容器可以按照 Bean 定义创建、装配并提供各种对象,因此从"统一提供对象"的宽泛角度看,与抽象工厂的目标存在相似之处。但 BeanFactory 并不是只围绕一个固定产品族定义多个创建方法的典型抽象工厂。学习时应将它作为容器化对象创建的类比,而不是严格的模式归类。
适用场景与注意事项
当系统需要创建多组相互关联、需要保持兼容的对象时,抽象工厂比较合适。增加新的产品族通常较容易;但如果频繁增加产品种类,所有具体工厂都可能需要增加对应创建方法。
4. 生成器模式(Builder)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.lang.StringBuilder / StringBuffer |
通过连续调用追加操作逐步构造字符串内容。它们体现了逐步构建结果的思想,但不是 GoF Builder 模式最典型的"复杂产品生成器"结构。 |
| JDK | java.util.stream.Stream.Builder |
通过 add() 逐步添加元素,再调用 build() 构造 Stream。 |
| Spring | BeanDefinitionBuilder |
通过链式调用逐步设置 Bean 定义的属性,最后取得构建结果。 |
| Spring | UriComponentsBuilder / MockMvcBuilders |
通过多个配置步骤构建 URI 组件或 MockMvc 测试对象。 |
原理说明
生成器模式将复杂对象的构建过程拆分为多个步骤。客户端可以按需要设置不同属性,最后调用 build() 得到构建完成的对象。
它主要解决构造函数参数过多、可选参数组合复杂以及构造过程不易阅读的问题。与工厂模式相比,工厂更侧重"选择并创建哪一种对象",Builder 更侧重"如何一步步配置并构造一个对象"。
JDK 示例理解
java
Stream.Builder<String> builder = Stream.builder();
builder.add("Java");
builder.add("Spring");
builder.add("JDK");
Stream<String> stream = builder.build();
stream.forEach(System.out::println);
Stream.Builder 将元素收集过程与最终 Stream 的创建分开。调用方可以逐步添加元素,最后统一构建。
Spring 示例理解
java
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(MyService.class);
builder.addPropertyValue("name", "demo");
BeanDefinition beanDefinition = builder.getBeanDefinition();
BeanDefinitionBuilder 将 Bean 定义的构造过程封装为一系列配置步骤。UriComponentsBuilder 则可以逐步设置协议、主机、路径和查询参数,最后生成 URI。
适用场景与注意事项
当对象创建过程包含多个可选步骤,或者构造参数很多且需要提高可读性时,可以使用 Builder。Builder 不一定需要单独的 Director 类;现代 Java 项目中常见的是链式 Builder。构建完成后若希望对象不可变,应在 build() 阶段完成必要校验和防御性拷贝。
5. 原型模式(Prototype)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.lang.Object.clone() |
提供对象克隆的基础机制,具体类需要实现 Cloneable 并合理重写 clone()。 |
| JDK | java.util.ArrayList.clone() |
创建一个新的 ArrayList 对象,并复制列表内部元素引用;属于浅拷贝。 |
| Spring | @Scope("prototype") |
每次向容器请求该 Bean 时,容器通常创建一个新的 Bean 实例;它不是对已有 Bean 调用 clone()。 |
原理说明
原型模式通过复制已有对象来创建新对象。客户端不必了解复杂的初始化过程,只需要提供一个原型,再通过复制得到新的对象。
JDK 示例理解
java
List<String> original = new ArrayList<>();
original.add("Java");
@SuppressWarnings("unchecked")
List<String> copy = (List<String>) ((ArrayList<String>) original).clone();
System.out.println(original == copy); // false
System.out.println(original.get(0) == copy.get(0)); // true
列表对象本身不同,但其中的字符串引用被复制。ArrayList.clone() 因而是浅拷贝示例。如果元素是可变对象,修改元素对象可能同时影响原列表和副本。
Spring 示例理解
java
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TaskContext {
}
每次通过容器获取 TaskContext,Spring 都会创建一个新的实例。需要特别注意:Spring Prototype Scope 的含义是"每次从容器获取时创建新实例",并不表示容器复制某个既有对象。Prototype Bean 注入 Singleton Bean 时,默认也不会在每次调用时自动重新注入;若需要每次获取新对象,应使用 ObjectProvider、Provider 或方法注入等方式。
适用场景与注意事项
当对象初始化成本较高、对象结构复杂且新对象与已有对象高度相似时,可以考虑原型模式。使用克隆时必须明确浅拷贝和深拷贝的边界,尤其要检查集合、数组和可变成员对象。
6. 单例模式(Singleton)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.lang.Runtime.getRuntime() |
通过静态入口获取当前 Java 运行时环境对应的 Runtime 实例。 |
| JDK | java.awt.Desktop.getDesktop() |
获取当前桌面环境对应的 Desktop 对象;其可用性受桌面环境支持情况影响。 |
| Spring | IoC 容器默认 Bean 作用域 | 默认 singleton 作用域通常在一个 Spring IoC 容器内维护一个 Bean 实例,并通过单例注册表缓存。 |
原理说明
单例模式限制某个类在指定范围内只存在一个实例,并提供统一访问入口。经典实现通常包含私有构造方法、静态实例以及获取实例的方法。
JDK 示例理解
java
Runtime runtime1 = Runtime.getRuntime();
Runtime runtime2 = Runtime.getRuntime();
System.out.println(runtime1 == runtime2); // true
调用方通过 getRuntime() 获取运行时对象,而不是直接创建新的 Runtime 实例。
Spring 示例理解
Spring 默认 Bean Scope 为 singleton。例如:
java
@Component
public class UserService {
}
在同一个 Spring IoC 容器中,多次按名称或类型获取该 Bean,通常得到同一个实例。这里的"单例"是容器级单例,不是 JVM 全局单例:两个独立的 Spring 容器可以分别创建自己的 UserService 实例。
Spring 单例 Bean 的共享还意味着需要关注线程安全。若 Bean 中保存可变的请求级状态,多个线程可能同时访问并造成数据竞争。
适用场景与注意事项
当一个对象确实需要在某个明确范围内共享时,可以考虑单例。不要为了减少 new 就把所有类设计成单例。全局状态会增加耦合、影响测试隔离,也可能带来并发安全问题。
二、结构型模式(Structural)
结构型模式主要关注类和对象如何组合,以形成更灵活、更易维护的结构。
7. 适配器模式(Adapter)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.io.InputStreamReader |
将字节输入流适配为字符输入流,使字符读取 API 能够使用底层字节流。 |
| JDK | java.util.Arrays.asList() |
将数组以 List 视图的形式提供给调用方,属于接口适配式用法;返回列表具有固定长度,并非普通可增删的 ArrayList。 |
| Spring | HandlerAdapter |
Spring MVC 通过适配器统一调用不同类型的处理器,例如 RequestMappingHandlerAdapter。 |
| Spring | AdvisorAdapter |
将不同类型的 Advice 适配为 Spring AOP 可调用的 MethodInterceptor。 |
原理说明
适配器模式将一个类原本不兼容的接口转换成客户端期望的接口。它通常不需要修改原有类,而是在两者之间增加一个适配层。
JDK 示例理解
java
InputStream input = new FileInputStream("data.txt");
Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8);
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
底层 InputStream 读取字节,调用方希望按字符读取。InputStreamReader 负责字符解码并提供 Reader 接口,因此是典型的对象适配器。
Arrays.asList(array) 则将数组包装成 List 形式供调用方使用。它与适配器思想相近,但不能把它理解成一个可自由增删元素的普通列表。
Spring 示例理解
Spring MVC 中,DispatcherServlet 不直接假定所有 Handler 都具有同一种调用方式,而是通过 HandlerAdapter 找到能够支持当前 Handler 的适配器,再由适配器执行处理逻辑。这样可以兼容不同类型的 Handler。
AdvisorAdapter 则解决 AOP 中不同 Advice 类型与拦截器调用链之间的接口转换问题。
适用场景与注意事项
当需要接入旧系统、第三方组件或历史接口,而又不希望大幅修改原有代码时,可以使用适配器。适配器负责接口转换,不应在其中塞入过多与转换无关的业务逻辑。
8. 桥接模式(Bridge)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.sql.DriverManager 与各数据库 Driver |
JDBC 通过统一驱动接口与具体数据库驱动实现协作,体现抽象与实现分离的思想;具体桥接关系还涉及 JDBC SPI 和驱动管理机制。 |
| Spring | org.springframework.messaging.MessageChannel 与不同消息传输实现 |
消息通道抽象与具体通道实现分离,允许上层消息处理逻辑与底层传输方式相对独立。 |
原理说明
桥接模式将抽象部分与实现部分分离,使它们可以独立变化。它通常使用组合关系,让抽象对象持有实现对象,而不是通过不断增加子类来组合所有变化维度。
JDK 示例理解
JDBC 提供统一的数据库访问接口,不同数据库厂商提供各自的 Driver 实现。应用程序面向 JDBC API 编程,具体数据库驱动负责底层数据库连接和协议细节。这样,应用层逻辑不必为每种数据库都维护一套完全独立的调用接口。
Spring 示例理解
MessageChannel 描述消息发送通道的抽象能力,具体实现可以采用不同的消息传输或处理方式。上层组件依赖通道抽象,而不是绑定到某个具体传输实现。
适用场景与注意事项
当系统存在两个或多个独立变化维度,例如"消息类型"和"传输方式"、"页面类型"和"主题"时,可以考虑桥接模式。若变化维度很少,直接使用普通接口和组合可能更简单。
9. 组合模式(Composite)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.awt.Container 与 Component |
容器可以包含其他组件,容器和叶子组件通过共同的组件抽象参与界面树结构。 |
| JDK | java.util.HashMap.putAll(Map) |
putAll 是批量复制映射内容的操作,并非组合模式的典型实现。它不构成"叶子与组合对象统一递归处理"的树形结构,因此不宜作为组合模式的标准例子。 |
| Spring | CompositeCacheManager |
将多个 CacheManager 组合起来,按配置顺序查找可用缓存。 |
| Spring | HandlerMethodArgumentResolverComposite |
将多个参数解析器组合起来,对外提供统一的参数解析器入口。 |
原理说明
组合模式将对象组织成树形结构,使客户端能够以统一方式处理单个对象和对象组合。组合对象内部可以继续包含叶子对象或其他组合对象。
JDK 示例理解
在 AWT 中,Component 表示组件,Container 是可以容纳其他组件的组件。窗口中可以包含面板,面板又可以包含按钮、文本框等组件。客户端可以在统一组件体系中操作不同层级的对象。
Spring 示例理解
HandlerMethodArgumentResolverComposite 内部维护多个参数解析器。调用 supportsParameter() 时,它会寻找能够处理当前参数的解析器;执行解析时,再由匹配的解析器完成具体工作。多个解析器被组合成一个统一入口。
CompositeCacheManager 则组合多个缓存管理器,使上层代码通过统一的 CacheManager 接口访问缓存。
适用场景与注意事项
当业务数据天然具有树形层级,例如组织架构、文件目录、菜单、页面组件或规则树时,可以使用组合模式。需要明确叶子节点和组合节点允许执行哪些操作,避免为了接口统一而给叶子节点提供没有意义的方法。
10. 装饰器模式(Decorator)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.io.BufferedInputStream、DataInputStream 等 I/O 包装流 |
包装已有输入流,在不修改原流类的情况下增加缓冲、数据类型读取等能力。 |
| JDK | Collections.synchronizedList() |
返回带同步访问行为的 List 包装对象,体现对原对象进行包装并增强行为的思想。 |
| Spring 相关 Servlet API | HttpServletRequestWrapper |
Servlet API 提供的请求包装基类,可通过包装请求对象扩展或调整请求行为;Spring Web 应用中经常使用它,但它本身属于 Servlet API。 |
| Spring | TransactionAwareCacheDecorator |
在 Cache 外层增加事务感知行为,使缓存操作与 Spring 事务同步机制协作。 |
| Spring | BeanWrapper |
主要用于统一封装 JavaBean 属性访问和类型转换等能力,不是典型装饰器模式;不建议将其作为装饰器的标准例子。 |
原理说明
装饰器模式通过包装对象,在不修改原对象类、不影响其他同类对象的情况下,动态增加额外职责。装饰器通常与被包装对象实现同一接口,并持有被包装对象的引用。
JDK 示例理解
java
InputStream input = new FileInputStream("data.bin");
InputStream buffered = new BufferedInputStream(input);
DataInputStream data = new DataInputStream(buffered);
int value = data.readInt();
这里的包装关系可以理解为:
FileInputStream 提供基础文件读取能力,BufferedInputStream 增加缓冲能力,DataInputStream 增加读取 Java 基本数据类型的能力。每一层都在已有流的基础上扩展功能。
Spring 示例理解
TransactionAwareCacheDecorator 包装一个 Cache,并增加事务感知行为。业务代码仍然面向 Cache 接口使用缓存,但具体操作会考虑 Spring 事务同步机制。
HttpServletRequestWrapper 则允许开发者包装原始请求,并重写部分方法,例如调整请求头或参数读取行为。
适用场景与注意事项
当需要为对象动态叠加功能,并且不希望通过继承产生大量子类时,可以使用装饰器。需要注意包装层次过多会增加调试难度;还应明确关闭、释放等生命周期操作是否需要向内层对象传递。
11. 外观模式(Facade)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.net.URL |
为 URL 表示、连接打开和数据读取提供相对统一的入口;它并不完全等同于一个独立的复杂子系统外观。 |
| Spring | JdbcTemplate |
封装 JDBC 操作中的连接获取、Statement 创建、异常处理和资源释放等重复流程,为调用方提供更简单的数据库访问接口。 |
原理说明
外观模式为复杂子系统提供一个简化入口。客户端只需要与外观对象交互,不必了解子系统内部多个对象之间的调用顺序和协作细节。
外观模式通常不要求隐藏所有底层接口。需要更细粒度控制的调用方仍然可以直接使用子系统接口。
Spring 示例理解
传统 JDBC 代码需要处理连接获取、Statement 创建、执行 SQL、处理异常、关闭 ResultSet 和 Statement、释放连接等步骤。JdbcTemplate 将这些模板化流程封装起来:
java
List<User> users = jdbcTemplate.query(
"select id, name from user",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))
);
调用方主要关注 SQL 和结果映射,而连接管理、资源释放及异常转换等通用工作由模板完成。
适用场景与注意事项
当调用方需要操作一组复杂子系统,但大多数场景只需要少量常用功能时,可以提供外观类。外观应降低使用复杂度,而不是把所有业务逻辑都集中成一个难以维护的"万能类"。
12. 享元模式(Flyweight)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.lang.Integer.valueOf() |
对部分整数值使用缓存对象,常见缓存范围为 -128 到 127;具体缓存范围可以通过 JVM 配置影响。 |
| JDK | String 常量池 |
对字符串字面量及显式调用 intern() 的字符串进行规范化和共享。 |
| Spring | IoC 容器中的单例缓存池 | 单例 Bean 在同一个容器内被缓存并复用,体现共享对象、减少重复创建的思想;但单例作用域与经典享元模式并不完全等同。 |
原理说明
享元模式通过共享大量对象中相同的部分,减少对象数量和内存占用。通常将状态分为内部状态和外部状态:内部状态可以共享,外部状态由调用方在使用时传入。
JDK 示例理解
java
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // 通常为 true
Integer.valueOf() 对缓存范围内的整数复用对象。不要使用 == 比较包装类型的数值含义;业务数值比较应使用 equals() 或拆箱后的数值比较。
字符串常量池也体现共享思想:
java
String a = "java";
String b = "java";
System.out.println(a == b); // true
Spring 示例理解
Spring 单例 Bean 在同一容器内创建后会被缓存,后续获取时复用实例。这与享元模式都涉及对象共享,但二者关注点不同:单例强调限定作用域内只有一个实例,享元强调将可共享状态抽取出来以减少大量相似对象的内存消耗。
适用场景与注意事项
当系统需要创建大量结构相似的对象,且对象中存在可共享的数据时,可以考虑享元模式。共享对象应尽量保持无状态或只保存不可变的内部状态;请求级、用户级等外部状态不应错误地放入共享对象中。
13. 代理模式(Proxy)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.lang.reflect.Proxy |
通过动态代理在运行时创建接口代理对象,将方法调用交给 InvocationHandler 处理。 |
| Spring | Spring AOP | 通过代理对象增强目标对象方法调用,可用于事务、日志、权限、缓存等横切关注点。默认情况下,目标类实现接口时通常可以使用 JDK 动态代理;也可以配置使用 CGLIB。 |
原理说明
代理模式为目标对象提供一个替代入口。客户端访问代理对象,由代理对象决定是否、何时以及如何调用真实对象,并可以在调用前后增加额外处理。
常见代理类型包括远程代理、虚拟代理、保护代理和智能引用代理。Spring AOP 中常见的是在方法调用前后织入增强逻辑。
JDK 示例理解
java
interface UserService {
void save();
}
class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用户");
}
}
class LogHandler implements InvocationHandler {
private final Object target;
LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
try {
return method.invoke(target, args);
} finally {
System.out.println("方法执行后");
}
}
}
通过 Proxy.newProxyInstance() 创建代理对象后,调用接口方法会进入 InvocationHandler.invoke()。
Spring 示例理解
Spring AOP 会为符合条件的 Bean 创建代理对象。调用方拿到的可能不是目标对象本身,而是代理对象。代理负责执行拦截器链,再决定何时调用目标方法。
需要注意:Spring AOP 的代理调用通常针对通过代理对象发生的方法调用。目标对象内部通过 this 调用自身另一个方法时,通常不会再次经过代理,因此不能简单认为同一个类中的所有方法调用都会触发 AOP 增强。
适用场景与注意事项
当需要在不修改目标业务类的前提下增加权限校验、事务管理、日志、监控、缓存或远程访问等能力时,可以使用代理。应明确代理类型、代理边界以及自调用等限制。
三、行为型模式(Behavioral)
行为型模式主要关注对象之间如何分配职责,以及对象之间如何协作、传递消息和控制流程。
14. 责任链模式(Chain of Responsibility)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK / Java Web API | javax.servlet.Filter 与 FilterChain |
多个过滤器按配置顺序组成处理链,一个过滤器可以处理请求、继续传递或终止链路。Servlet API 属于 Java Web 规范,不属于 JDK 核心类库。 |
| JDK | java.util.logging.Logger |
Logger 的父子层级和 Handler 处理机制具有链式传播特征,但它不是最标准的 GoF 责任链示例。 |
| Spring MVC | HandlerInterceptor |
多个拦截器可以按顺序参与请求处理,在前置处理阶段决定是否继续执行。 |
| Spring AOP | MethodInterceptor |
多个拦截器通过 MethodInvocation.proceed() 形成调用链。 |
原理说明
责任链模式将多个处理者连接成一条链。请求从链的一端进入,每个处理者判断自己是否处理请求;如果不处理,就将请求交给下一个处理者。请求也可能在某个环节被拦截或终止。
Java Web 示例理解
Servlet Filter 的典型处理形式如下:
java
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 请求进入当前过滤器时执行
chain.doFilter(request, response);
// 后续过滤器及目标资源处理完成后,可执行后置逻辑
}
过滤器可以在 chain.doFilter() 前后执行逻辑,也可以选择不调用它,从而终止后续链路。
Spring 示例理解
Spring MVC 的拦截器可以在请求处理前后执行逻辑。多个拦截器按注册顺序参与处理;某个拦截器返回 false 时,可以阻止后续处理。
Spring AOP 的 MethodInterceptor 通过 proceed() 将控制权交给调用链中的下一个拦截器或目标方法。每个拦截器可以在 proceed() 前后增加逻辑。
适用场景与注意事项
当一个请求可能由多个处理者按顺序判断或处理时,可以使用责任链,例如过滤器、权限校验、风控规则、审批流程和异常处理。需要明确链路顺序、终止条件以及无人处理时的默认行为。
15. 命令模式(Command)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.lang.Runnable |
将一段待执行任务封装为对象,交给线程或线程池执行,体现命令封装思想;但 Runnable 本身没有撤销、接收者等完整命令模式结构。 |
| Spring | JdbcTemplate 中的 StatementCallback 等回调接口 |
将 JDBC 操作封装为回调对象,由模板在统一资源管理流程中调用,体现将操作交给调用方封装的思想。 |
原理说明
命令模式将请求封装成对象,从而使请求的发起者与真正执行操作的对象解耦。命令对象可以保存操作所需的参数和接收者,也可以被排队、记录、重试或撤销。
经典命令模式通常包含四个角色:命令接口、具体命令、接收者和调用者。客户端负责组装这些对象。
JDK 示例理解
java
Runnable command = () -> System.out.println("执行任务");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(command);
executor.shutdown();
线程池是调用者,Runnable 是封装任务的命令对象,任务内部的业务对象或代码逻辑承担具体执行工作。该例体现命令封装思想,但没有完整展示撤销和接收者分离结构。
Spring 示例理解
JdbcTemplate.execute(StatementCallback<T>) 接收回调对象,由模板负责连接和 Statement 的生命周期管理,再调用回调执行具体数据库操作。调用方描述要执行的操作,模板控制通用执行流程。
适用场景与注意事项
当需要将操作排队、延迟执行、记录历史、重试或撤销时,命令模式很有价值。若只是简单调用一个方法,没有解耦、排队或扩展需求,直接调用通常更清晰。
16. 迭代器模式(Iterator)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.util.Iterator 与 Enumeration |
提供统一的元素遍历接口,隐藏集合或容器内部的数据组织方式。 |
| Spring | CompositeIterator |
将多个迭代器组合起来,按顺序遍历多个数据源中的元素。 |
原理说明
迭代器模式提供一种顺序访问聚合对象元素的方法,同时不暴露聚合对象的内部表示。集合负责提供迭代器,迭代器负责维护遍历位置和遍历规则。
JDK 示例理解
java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
客户端只依赖 Iterator 的 hasNext() 和 next(),不需要知道集合内部是数组、链表还是其他结构。增强 for 循环也依赖 Iterable / Iterator 机制。
Enumeration 是较早的遍历接口,常见于早期集合和 API 中;现代 Java 集合通常优先使用 Iterator。
Spring 示例理解
组合迭代器可以将多个底层迭代器串联起来,对外表现为一个统一的迭代入口。这样调用方不需要分别遍历每个来源。
适用场景与注意事项
当需要遍历不同内部结构的数据集合,并希望遍历逻辑与集合实现解耦时,可以使用迭代器。遍历过程中修改集合需要遵守具体集合的并发修改规则,不能假设所有 Iterator 都支持安全删除或并发修改。
17. 中介者模式(Mediator)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.util.Timer |
Timer 统一调度多个 TimerTask,体现集中协调任务的思想,但并不是典型的对象间交互中介者。 |
| Spring MVC | DispatcherServlet |
作为前端控制器协调 HandlerMapping、HandlerAdapter、ViewResolver 等组件,集中组织请求处理流程。 |
原理说明
中介者模式通过引入一个中介对象,封装一组对象之间的交互关系。原本需要彼此直接通信的对象,改为与中介者通信,从而减少对象之间的直接依赖。
Spring 示例理解
一次典型 Spring MVC 请求由 DispatcherServlet 统一协调:它根据请求查找 Handler,选择合适的 HandlerAdapter 执行处理,处理完成后再根据结果处理视图或响应内容。各组件不需要彼此硬编码所有交互关系。
DispatcherServlet 同时也是前端控制器模式的典型应用。它体现了中介者式的集中协调思想,但更准确的描述是"前端控制器,并具有中介者式协调职责",不应把它只归类为中介者模式。
适用场景与注意事项
当多个对象之间存在复杂的网状依赖,修改一个对象可能牵动多个对象时,可以引入中介者统一协调。中介者本身也可能变得臃肿,因此应避免把所有业务规则都塞进一个中央对象。
18. 备忘录模式(Memento)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.io.Serializable |
序列化可以保存对象状态并在之后重建对象,但它不是备忘录模式的直接实现;备忘录强调由发起人控制状态快照及恢复边界。 |
| Spring Web Flow | 状态保存与恢复机制 | Web Flow 会保存流程执行状态,以支持流程继续执行等能力;具体类和机制应以所使用的 Spring Web Flow 版本为准。 |
原理说明
备忘录模式在不暴露对象内部实现细节的前提下,捕获并保存对象某一时刻的状态,以便之后恢复。经典结构包括发起人(Originator)、备忘录(Memento)和负责人(Caretaker)。
示例理解
java
class EditorMemento {
private final String content;
EditorMemento(String content) {
this.content = content;
}
String getContent() {
return content;
}
}
class Editor {
private String content = "";
void write(String text) {
content += text;
}
EditorMemento save() {
return new EditorMemento(content);
}
void restore(EditorMemento memento) {
content = memento.getContent();
}
}
Editor 是发起人,负责创建和恢复状态快照;EditorMemento 保存状态;外部负责人可以保存备忘录引用,但不应随意修改其内部状态。
Spring 示例理解
Spring Web Flow 的流程执行涉及状态保存和恢复,以支持多步骤 Web 流程。它与备忘录模式的关联在于保存流程某一时点的状态,并在后续请求中恢复流程执行上下文。具体实现细节应结合对应版本源码理解,不应仅凭某个类名推断其完整模式结构。
适用场景与注意事项
当业务需要撤销、回滚、历史版本、流程恢复或编辑器快照时,可以考虑备忘录模式。保存状态时要考虑内存占用、敏感数据保护、快照一致性以及深拷贝边界。
19. 观察者模式(Observer)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.util.EventListener 事件监听体系 |
通过监听器注册和事件分发实现发布者与监听者之间的通知关系。 |
| JDK | java.beans.PropertyChangeListener |
当 JavaBean 属性发生变化时,可以通知注册的属性变化监听器。 |
| Spring | ApplicationEvent、ApplicationListener 与 @EventListener |
Spring 应用事件机制支持事件发布和监听者订阅,监听器在事件发布后被调用。 |
原理说明
观察者模式定义对象之间的一对多依赖关系。当被观察对象状态发生变化时,它会通知所有已注册的观察者。发布者不必直接了解每个观察者的具体业务实现。
JDK 示例理解
java
PropertyChangeSupport support = new PropertyChangeSupport(this);
support.addPropertyChangeListener(event ->
System.out.println(event.getPropertyName() + " changed")
);
support.firePropertyChange("name", "old", "new");
PropertyChangeSupport 负责维护监听器并分发属性变化事件,监听器收到通知后执行自己的逻辑。
Spring 示例理解
java
@Component
class OrderService {
private final ApplicationEventPublisher publisher;
OrderService(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
void createOrder() {
// 创建订单
publisher.publishEvent(new OrderCreatedEvent(this));
}
}
@Component
class OrderListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 处理订单创建事件
}
}
发布者通过 ApplicationEventPublisher 发布事件,监听者通过 ApplicationListener 或 @EventListener 接收事件。发布者不需要直接调用具体监听器。
Spring 事件默认在发布线程中同步处理;如果需要异步执行,需要结合异步事件监听配置。事件机制也不等同于可靠消息队列,不能默认具备跨进程投递、持久化和失败重试能力。
适用场景与注意事项
当一个业务动作完成后,需要通知多个相互独立的模块时,可以使用观察者模式,例如订单创建后通知积分、库存、消息或审计模块。应控制监听器异常对发布流程的影响,并明确事件是同步还是异步、是否需要可靠投递。
20. 访问者模式(Visitor)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.nio.file.FileVisitor |
遍历文件树时,针对目录进入、文件访问、失败和目录结束等不同节点事件执行不同处理。 |
| Spring | BeanDefinitionVisitor |
遍历 BeanDefinition 中的属性值、构造参数等元数据,并对其中的字符串或对象值执行访问和转换操作。 |
原理说明
访问者模式将作用于对象结构的操作从对象结构本身分离出来。访问者为不同元素类型提供不同处理方法,元素通过 accept(visitor) 将自身交给访问者处理。
它适合对象结构相对稳定、但需要不断增加针对该结构的操作的场景。
JDK 示例理解
java
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(
Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("文件:" + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(
Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("目录:" + dir);
return FileVisitResult.CONTINUE;
}
});
FileVisitor 为文件和目录遍历过程中的不同事件提供独立回调方法,调用方可以针对不同节点类型定义不同处理逻辑。这体现了访问者式分派思想。
Spring 示例理解
BeanDefinitionVisitor 用于访问 Bean 定义元数据中的属性值、构造参数等内容,并对其中的字符串或嵌套值进行处理,例如解析占位符。它将"如何遍历 Bean 定义结构"和"访问到某类值时如何处理"分开。
适用场景与注意事项
当对象结构比较稳定,但需要不断增加统计、导出、校验、转换等操作时,可以考虑访问者模式。若元素类型经常变化,访问者接口也需要频繁修改,此时模式的维护成本会增加。
21. 策略模式(Strategy)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.util.Comparator |
将排序规则封装为独立策略,排序算法可以使用不同 Comparator。 |
| JDK | ThreadPoolExecutor 的拒绝策略 |
RejectedExecutionHandler 允许通过不同实现决定线程池无法接收任务时的处理方式。 |
| Spring | InstantiationStrategy |
定义 Bean 实例化策略,Spring 提供不同实现处理普通实例化或方法注入等场景。 |
| Spring | Resource 接口的不同资源实现 |
ClassPathResource、UrlResource 等提供不同资源定位和读取方式;它们体现面向接口的多实现设计,是否严格归为策略模式需结合调用上下文判断。 |
原理说明
策略模式定义一组可互换的算法或行为,将每种策略封装为独立实现。使用方依赖统一策略接口,在运行时选择合适的策略,而不必在业务代码中写大量条件分支。
JDK 示例理解
java
List<String> names = Arrays.asList("Tom", "Alexander", "Li");
names.sort(Comparator.comparingInt(String::length));
Comparator 将排序规则独立出来。同一个集合可以根据不同业务需求使用不同 Comparator,而不必修改集合类本身。
线程池拒绝策略也是典型例子。AbortPolicy、CallerRunsPolicy、DiscardPolicy 和 DiscardOldestPolicy 对任务无法提交时采取不同处理方式,调用方可以按业务需求选择。
Spring 示例理解
InstantiationStrategy 将 Bean 实例化行为抽象为策略接口,具体策略负责不同的实例化方式。Spring 在创建 Bean 时根据具体场景使用相应策略。
Resource 的不同实现分别处理类路径、文件系统、URL 等资源来源。它们展示了统一抽象下的多种实现方式;从设计角度看可以与策略思想联系起来,但不能仅凭"接口有多个实现"就断定一定是策略模式。
适用场景与注意事项
当同一任务存在多种算法或处理方式,并且需要根据配置、运行环境或业务条件切换时,可以使用策略模式。策略选择逻辑也应保持清晰;如果策略数量很少且永远不会变化,简单条件判断可能更直接。
22. 状态模式(State)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| Java EE 相关技术 | javax.faces.lifecycle.Lifecycle |
JSF 生命周期由多个阶段组成,体现流程阶段和状态推进思想;它不是 JDK 核心类库中的通用状态模式实现。 |
| Spring 生态 | Spring StateMachine | 提供状态、事件、状态转换、守卫和动作等抽象,用于管理复杂状态流转。 |
原理说明
状态模式将对象在不同状态下的行为封装到不同状态类中,使对象的行为随着内部状态变化而变化。通常由上下文对象持有当前状态对象,并将请求委托给当前状态处理。
它与策略模式在结构上相似,但意图不同:策略模式通常由外部选择算法;状态模式通常由对象内部状态决定行为,并可能在行为执行后切换到另一个状态。
Spring 示例理解
Spring StateMachine 将状态和状态转换显式建模。例如订单可以有待支付、已支付、已取消、已完成等状态;事件触发后,状态机根据当前状态、事件和守卫条件决定是否执行转换,并可在转换过程中执行动作。
适用场景与注意事项
当对象具有多个状态,且不同状态下允许的操作和行为明显不同,状态转换规则也较复杂时,可以考虑状态模式或状态机。简单的两三个状态不一定需要引入完整状态机框架。状态转换应明确合法路径、非法事件处理和并发更新规则。
23. 模板方法模式(Template Method)
经典应用
| 类型 | 应用 | 说明 |
|---|---|---|
| JDK | java.util.AbstractList、AbstractMap、AbstractSet |
抽象集合类提供部分通用操作,并将某些基础操作留给子类实现,体现模板方法思想。 |
| Java Web API | HttpServlet 的 service() 方法 |
service() 根据请求类型分派到 doGet()、doPost() 等方法,子类通过重写钩子方法定制行为;Servlet API 不属于 JDK 核心类库。 |
| Spring | AbstractApplicationContext.refresh() |
容器刷新过程由父类定义总体步骤,并在多个扩展点调用子类或组件提供的具体实现。 |
| Spring | JdbcTemplate、RestTemplate、RedisTemplate |
通过模板封装重复的执行流程,并将具体操作或回调交给调用方;其中部分采用回调模板形式,不完全等同于经典继承式模板方法。 |
原理说明
模板方法模式在父类中定义算法的整体骨架,将部分步骤延迟到子类实现。子类可以重写特定步骤,但通常不能改变算法的整体执行顺序。
模板方法的重点是固定流程、开放局部扩展点。它可以减少重复代码,并让不同子类遵循相同的处理步骤。
JDK 示例理解
AbstractList 提供了许多基于基础操作实现的通用方法。具体列表实现可以提供必要的基础能力,继承得到的通用操作则复用抽象类中的算法骨架。
HttpServlet 的 service() 会根据 HTTP 请求方法调用相应的 doGet()、doPost() 等方法。开发者通常重写需要处理的方法,而不必自行实现整个请求分派流程。
Spring 示例理解
AbstractApplicationContext.refresh() 定义了 Spring 容器刷新的总体流程,包括准备刷新、获取 BeanFactory、准备 BeanFactory、执行 BeanFactory 后置处理器、注册 Bean 后置处理器、初始化消息源和事件广播器、实例化非懒加载单例等步骤。具体子类或扩展组件在预留环节参与执行。
JdbcTemplate、RestTemplate 和 RedisTemplate 等模板类则封装资源获取、执行和释放等重复流程。它们常通过回调接口将差异化操作交给调用方,因此更准确地说是模板化流程或回调模板设计,其中不一定全部采用经典的"父类方法调用子类钩子方法"结构。
适用场景与注意事项
当多个业务流程整体步骤相同,只有个别步骤不同,可以使用模板方法。应谨慎开放钩子方法,避免子类通过大量重写破坏父类约定。若差异点需要在运行时自由组合,策略模式或组合方式可能比继承更灵活。
四、模式对照表中的易混淆点
设计模式的判断应以意图、职责和对象协作关系为依据,而不是只看类名或某个 API 是否具有相似特征。下面列出本表中容易产生误解的对应关系:
| 对照项 | 更准确的理解 |
|---|---|
BeanFactory.getBean() 与简单工厂 |
体现统一获取对象的工厂思想,但 BeanFactory 是 IoC 容器核心接口,职责远超简单工厂。 |
BeanFactory 与抽象工厂 |
容器可以提供多种 Bean,但它不是严格意义上围绕多个相关产品族定义创建方法的 GoF 抽象工厂。 |
@Scope("prototype") 与原型模式 |
Prototype Scope 表示每次从容器获取时创建新实例,不表示克隆某个已有 Bean。 |
| Spring singleton 与单例模式 | Spring 单例是每个 IoC 容器内的单例,不是整个 JVM 或整个分布式系统的全局单例。 |
HashMap.putAll() 与组合模式 |
putAll() 是批量复制映射内容,不具备组合模式所要求的树形部分---整体结构。 |
BeanWrapper 与装饰器 |
BeanWrapper 主要封装 JavaBean 属性访问和类型转换,不是典型装饰器。 |
Serializable 与备忘录模式 |
序列化能够保存和恢复对象数据,但备忘录模式强调状态快照的封装和恢复边界。 |
DispatcherServlet 与中介者 |
它首先是 Spring MVC 的前端控制器,同时承担集中协调多个组件的职责,体现中介者式思想。 |
Resource 多种实现与策略模式 |
多实现结构体现可替换性,但是否为策略模式要看调用方是否在运行时选择并使用可替换算法。 |
JdbcTemplate 等模板类与模板方法 |
它们体现固定流程和扩展点;部分实现使用回调组合,不一定是经典继承式模板方法。 |
Runnable 与命令模式 |
Runnable 将任务封装成对象,但不包含完整命令模式所需的所有角色和撤销机制。 |
java.util.Timer 与中介者 |
Timer 主要负责定时调度任务,具有集中协调特征,但不是典型的同事对象交互中介者。 |
五、如何在实际 Java 项目中选择设计模式
学习设计模式的目的不是在代码中尽可能多地使用模式,而是识别重复出现的设计问题,并选择成本合适的解决方式。
- 先识别变化点。 如果变化的是对象创建方式,优先考虑工厂;如果变化的是算法,优先考虑策略;如果变化的是对象状态及其允许行为,考虑状态模式。
- 先面向抽象,再考虑模式。 接口和组合已经能解决的问题,不必为了模式名称增加额外类。
- 注意模式之间的边界。 代理与装饰器都可能包装对象,但代理更强调控制访问,装饰器更强调动态增加职责;策略与状态结构相似,但触发策略选择和状态转换的主体不同。
- 优先控制复杂度。 一个模式如果引入大量工厂、接口、包装层或状态类,却没有降低变化成本,就可能是过度设计。
- 结合框架源码理解。 Spring 中一个类可能同时体现多种设计思想。应沿着调用链观察对象由谁创建、由谁持有、请求如何传递、扩展点在哪里,而不是只记忆"某个类等于某个模式"。
设计模式是针对反复出现的问题提供的可复用设计经验,不是可以直接套用的代码模板。只有当模式能够降低耦合、隔离变化、改善扩展性或提升可读性时,它才真正发挥了价值。