🎨 深入浅出:探索设计模式的奥秘 🛠️
在软件工程的世界里,设计模式是解决常见问题的蓝图。
它们帮助开发者通过已验证的方法高效地解决复杂设计问题,从而加速开发过程并提高软件的质量和可维护性。
经典的 GoF(Gang of Four)《设计模式》一书定义了 23 种设计模式,分为创建型(5种)、结构型(7种)、行为型(11种)三大类。本文额外收录了实践中常用的简单工厂模式,共计 24 种。
设计模式详细分类
| 类别 |
设计模式 |
英文名 |
创建型模式
(★GoF 5种 + 简单工厂) |
单例模式 |
Singleton |
| 简单工厂模式 (非 GoF) |
Simple Factory |
| 工厂方法模式 |
Factory Method |
| 抽象工厂模式 |
Abstract Factory |
| 建造者模式 |
Builder |
| 原型模式 |
Prototype |
| 结构型模式 |
适配器模式 |
Adapter |
| 桥接模式 |
Bridge |
| 组合模式 |
Composite |
| 装饰者模式 |
Decorator |
| 外观模式 |
Facade |
| 享元模式 |
Flyweight |
| 代理模式 |
Proxy |
| 行为型模式 |
责任链模式 |
Chain of Responsibility |
| 观察者模式 |
Observer |
| 模板方法模式 |
Template Method |
| 命令模式 |
Command |
| 状态模式 |
State |
| 策略模式 |
Strategy |
| 迭代器模式 |
Iterator |
| 中介者模式 |
Mediator |
| 访问者模式 |
Visitor |
| 备忘录模式 |
Memento |
| 解释器模式 |
Interpreter |
设计模式的分类
设计模式大致分为三大类:创建型模式、结构型模式和行为型模式。每类模式通过不同的角度解决软件设计中的特定问题。
创建型模式
创建型模式关注对象的创建机制,帮助创建对象的同时隐藏创建逻辑,以提高系统的灵活性和可重用性。这类模式主要包括:
- 单例模式:确保一个类只有一个实例,并提供全局访问点。
- 简单工厂模式(非 GoF,常用编程惯用法):定义一个工厂类来创建对象,客户端无需了解具体类的创建过程。
- 工厂方法模式:通过子类决定实例化哪一个类,使一个类的实例化延迟到子类。
- 抽象工厂模式:提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。
- 建造者模式:将一个复杂对象的构建与其表示分离,使同样的构建过程可以创建不同的表示。
- 原型模式:通过复制现有的实例来创建新的实例,而不是通过新建。
示例:在游戏开发中,建造者模式可以用来创建复杂的角色对象,玩家可以选择不同的装备、技能等,建造者模式可以帮助逐步构建角色的各个部分。
结构型模式
结构型模式处理对象之间的关系,使得即使在复杂的系统中也能轻松地管理和维护。这类模式主要包括:
- 适配器模式:将一个类的接口转换成客户端期望的另一个接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。
- 桥接模式:将抽象部分与实现部分分离,使它们可以独立变化。
- 组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
- 装饰者模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更为灵活。
- 外观模式:为子系统中的一组接口提供一个统一的高层接口,使得子系统更易使用。
- 享元模式:运用共享技术有效地支持大量细粒度的对象。
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
示例:在图形编辑软件中,装饰者模式可以用来动态地添加图形的边框、阴影等效果,而无需改变图形类的代码。
行为型模式
行为型模式专注于对象之间的通信,为对象间的交互提供更灵活的沟通机制。这类模式主要包括:
- 责任链模式:为请求创建一条处理链,使多个对象都有机会处理该请求,从而避免请求的发送者和接收者之间的耦合。
- 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。
- 模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下重定义算法的某些步骤。
- 命令模式:将请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、日志记录和撤销操作。
- 状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来好像修改了它的类。
- 策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。
- 迭代器模式:提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该对象的内部表示。
- 中介者模式:用一个中介对象来封装一系列对象的交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散。
- 访问者模式:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- 备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
示例:在消息系统中,观察者模式可以用来实现订阅与通知机制,当发布者发布新消息时,所有订阅者都会收到通知并更新内容。
设计模式在现实世界的应用
让我们通过几个例子,看看这些设计模式如何在现实世界中被应用来解决具体的软件设计问题。
🏗️ 单例模式在数据库连接中的应用
在许多应用中,管理对数据库的单一连接是至关重要的。单例模式确保全局只有一个数据库连接实例,减少了资源消耗,并保证了连接管理的一致性。例如,在一个Web应用中,所有的数据库操作都通过同一个数据库连接实例进行,避免了多次创建和销毁连接的开销。
// 单例模式示例(Java)—— 双重检查锁定(推荐写法)
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private Connection connection;
private DatabaseConnection() {
// 初始化数据库连接
}
public static DatabaseConnection getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (DatabaseConnection.class) {
if (instance == null) { // 第二次检查(加锁)
instance = new DatabaseConnection();
}
}
}
return instance;
}
public Connection getConnection() {
return connection;
}
}
🌉 桥接模式简化多平台UI开发
开发跨平台应用时,桥接模式允许将用户界面(UI)与业务逻辑分离,使得两者可以独立变化而不互相影响,从而简化了开发。例如,一个跨平台的绘图应用,可以使用桥接模式将不同平台的绘图API(如Windows的GDI和macOS的Quartz)与业务逻辑分离,方便维护和扩展。
// 桥接模式示例(C#)
// 实现部分接口:定义绘图API
public interface IDrawingAPI {
void DrawCircle(double x, double y, double radius);
}
// 抽象部分:图形基类(持有实现部分的引用)
public abstract class Shape {
protected IDrawingAPI drawingAPI;
protected Shape(IDrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
public abstract void Draw();
}
// 扩展抽象:圆形
public class Circle : Shape {
private double x, y, radius;
public Circle(double x, double y, double radius, IDrawingAPI drawingAPI) : base(drawingAPI) {
this.x = x;
this.y = y;
this.radius = radius;
}
public override void Draw() {
drawingAPI.DrawCircle(x, y, radius);
}
}
📦 策略模式在支付系统中的灵活性
在线支付系统需要支持多种支付方法。策略模式允许在运行时选择最适合的支付策略,提高了系统的灵活性和可扩展性。例如,用户可以选择使用信用卡、PayPal或比特币进行支付,系统通过策略模式动态切换支付方式,而无需修改核心支付逻辑。
// 策略模式示例(Python)
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Using credit card to pay {amount} dollars.")
class PayPalPayment(PaymentStrategy):
def pay(self, amount):
print(f"Using PayPal to pay {amount} dollars.")
class BitcoinPayment(PaymentStrategy):
def pay(self, amount):
print(f"Using Bitcoin to pay {amount} dollars.")
class ShoppingCart:
def __init__(self):
self.items = []
self.amount = 0
def add_item(self, item, price):
self.items.append(item)
self.amount += price
def checkout(self, payment_strategy: PaymentStrategy):
payment_strategy.pay(self.amount)
# 使用示例
cart = ShoppingCart()
cart.add_item("Book", 30)
cart.add_item("Pen", 5)
# 用户选择支付方式
payment = PayPalPayment()
cart.checkout(payment)
更多设计模式的实际应用
除了上述示例,设计模式在实际开发中有广泛的应用。以下是一些常见的设计模式及其应用场景:
🔄 观察者模式在实时系统中的应用
在实时系统中,如股票交易平台,观察者模式可以用来实现实时数据更新。当股票价格变化时,所有订阅该股票的用户都会立即收到更新通知,从而做出相应的交易决策。
// 观察者模式示例(JavaScript)
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} received data: ${data}`);
}
}
// 使用示例
const subject = new Subject();
const observer1 = new Observer("Observer1");
const observer2 = new Observer("Observer2");
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify("Stock price updated to $150");
🔐 代理模式在延迟加载与访问控制中的应用
代理模式为对象提供替身以控制对它的访问。常见类型包括虚代理(延迟加载)和保护代理(权限控制)。以下示例展示了虚代理:在图片加载场景中,代理对象延迟真实图片的创建,直到需要显示时才加载,从而提升系统性能。
// 代理模式示例(Java)
// Image.java
interface Image {
void display();
}
// RealImage.java
class RealImage implements Image {
private String filename;
public RealImage(String fname) {
filename = fname;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
// ProxyImage.java
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String fname) {
filename = fname;
realImage = null;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
Image image = new ProxyImage("test_image.jpg");
// 图片将在这里加载
image.display();
// 图片不会重新加载
image.display();
}
}
🏭 工厂方法模式在日志系统中的应用
工厂方法模式将对象的创建交给子类决定,使一个类的实例化延迟到子类。在日志系统中,可以根据不同环境创建不同类型的日志记录器。
// 工厂方法模式示例(Java)
interface Logger {
void log(String message);
}
class FileLogger implements Logger {
public void log(String message) {
System.out.println("[File] " + message);
}
}
class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("[Console] " + message);
}
}
// 工厂接口
interface LoggerFactory {
Logger createLogger();
}
class FileLoggerFactory implements LoggerFactory {
public Logger createLogger() { return new FileLogger(); }
}
class ConsoleLoggerFactory implements LoggerFactory {
public Logger createLogger() { return new ConsoleLogger(); }
}
// 使用示例
LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.log("Application started.");
🔌 适配器模式在第三方接口集成中的应用
集成第三方服务时,适配器模式可以将不同的第三方接口转换为系统内部统一的接口,降低耦合度,方便后续替换服务商。
// 适配器模式示例(Java)
// 统一的消息发送接口
interface MessageSender {
void send(String phone, String message);
}
// 已有的第三方短信服务(接口不兼容)
class ThirdPartySmsService {
public void sendSms(String phoneNumber, String content) {
System.out.println("Sending to " + phoneNumber + ": " + content);
}
}
// 适配器:将第三方接口适配为统一接口
class SmsServiceAdapter implements MessageSender {
private ThirdPartySmsService service;
public SmsServiceAdapter(ThirdPartySmsService service) {
this.service = service;
}
@Override
public void send(String phone, String message) {
service.sendSms(phone, message);
}
}
// 使用示例
MessageSender sender = new SmsServiceAdapter(new ThirdPartySmsService());
sender.send("13800138000", "Hello!");
📝 模板方法模式在数据处理流程中的应用
模板方法模式在父类中定义算法骨架,将具体步骤延迟到子类实现。在数据处理场景中,读取、处理、输出的整体流程是固定的,但每个步骤的具体实现可以不同。
// 模板方法模式示例(Java)
abstract class DataProcessor {
// 模板方法:定义算法骨架(final 防止子类修改流程)
public final void process() {
readData();
processData();
writeData();
}
protected abstract void readData();
protected abstract void processData();
protected abstract void writeData();
}
class CsvDataProcessor extends DataProcessor {
protected void readData() { System.out.println("Reading CSV..."); }
protected void processData() { System.out.println("Processing CSV..."); }
protected void writeData() { System.out.println("Writing CSV..."); }
}
class JsonDataProcessor extends DataProcessor {
protected void readData() { System.out.println("Reading JSON..."); }
protected void processData() { System.out.println("Processing JSON..."); }
protected void writeData() { System.out.println("Writing JSON..."); }
}
// 使用示例
DataProcessor processor = new CsvDataProcessor();
processor.process();
🎀 装饰者模式在数据流处理中的应用
Java I/O 库是装饰者模式的经典应用。通过层层包装,可以为基础数据流动态添加缓冲、加密、压缩等功能,而无需修改原有类。
// 装饰者模式示例(Java)
interface DataSource {
void writeData(String data);
String readData();
}
class FileDataSource implements DataSource {
private String filename;
public FileDataSource(String filename) { this.filename = filename; }
public void writeData(String data) { System.out.println("Writing to " + filename); }
public String readData() { return "raw data from " + filename; }
}
// 装饰者基类
abstract class DataSourceDecorator implements DataSource {
protected DataSource wrappee;
public DataSourceDecorator(DataSource source) { this.wrappee = source; }
public void writeData(String data) { wrappee.writeData(data); }
public String readData() { return wrappee.readData(); }
}
class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) { super(source); }
public void writeData(String data) {
System.out.println("Encrypting data...");
super.writeData(data);
}
}
class CompressionDecorator extends DataSourceDecorator {
public CompressionDecorator(DataSource source) { super(source); }
public void writeData(String data) {
System.out.println("Compressing data...");
super.writeData(data);
}
}
// 使用示例:文件 → 加密 → 压缩
DataSource source = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource("output.dat")
)
);
source.writeData("Important data");
🔨 建造者模式在复杂对象构建中的应用
建造者模式将复杂对象的构建与表示分离,使同样的构建过程可以创建不同的表示。在构建拥有大量可选参数的对象时尤其有用,避免了构造器参数爆炸问题。
// 建造者模式示例(Java)
class Computer {
private String cpu;
private String ram;
private String storage;
private String gpu;
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.gpu = builder.gpu;
}
@Override
public String toString() {
return "Computer [CPU=" + cpu + ", RAM=" + ram +
", Storage=" + storage + ", GPU=" + gpu + "]";
}
static class Builder {
private String cpu;
private String ram;
private String storage;
private String gpu;
public Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public Builder storage(String val) { this.storage = val; return this; }
public Builder gpu(String val) { this.gpu = val; return this; }
public Computer build() { return new Computer(this); }
}
}
// 使用示例:链式调用,清晰直观
Computer gaming = new Computer.Builder("i9-13900K", "32GB")
.storage("2TB SSD")
.gpu("RTX 4090")
.build();
System.out.println(gaming);
🎮 命令模式在撤销重做中的应用
命令模式将请求封装为对象,从而支持撤销(Undo)、重做(Redo)和请求队列等功能。在文本编辑器、绘图工具等需要操作历史的场景中非常实用。
// 命令模式示例(Java)
interface Command {
void execute();
void undo();
}
class Light {
public void on() { System.out.println("Light is ON"); }
public void off() { System.out.println("Light is OFF"); }
}
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) { this.light = light; }
public void execute() { light.on(); }
public void undo() { light.off(); }
}
class RemoteControl {
private Command lastCommand;
public void pressButton(Command cmd) {
cmd.execute();
lastCommand = cmd;
}
public void pressUndo() {
if (lastCommand != null) lastCommand.undo();
}
}
// 使用示例
Light light = new Light();
RemoteControl remote = new RemoteControl();
remote.pressButton(new LightOnCommand(light)); // Light is ON
remote.pressUndo(); // Light is OFF
⛓️ 责任链模式在请求过滤中的应用
责任链模式将请求沿着处理链传递,每个处理者可以选择处理请求或将其传给下一个处理者。Java Servlet 中的 Filter、Spring 中的 Interceptor 以及 Netty 中的 ChannelPipeline 都是责任链模式的典型应用。
// 责任链模式示例(Java)
abstract class Handler {
protected Handler next;
public Handler setNext(Handler next) {
this.next = next;
return next;
}
public abstract void handle(String request);
}
class AuthHandler extends Handler {
public void handle(String request) {
if ("unauthorized".equals(request)) {
System.out.println("AuthHandler: Access denied.");
return;
}
System.out.println("AuthHandler: Passed.");
if (next != null) next.handle(request);
}
}
class LogHandler extends Handler {
public void handle(String request) {
System.out.println("LogHandler: Logging [" + request + "]");
if (next != null) next.handle(request);
}
}
class BusinessHandler extends Handler {
public void handle(String request) {
System.out.println("BusinessHandler: Processing [" + request + "]");
}
}
// 使用示例:构建处理链 Auth → Log → Business
Handler chain = new AuthHandler();
chain.setNext(new LogHandler()).setNext(new BusinessHandler());
chain.handle("valid-request");
设计模式背后的设计原则(SOLID)
设计模式的核心思想建立在面向对象设计原则之上,掌握这些原则有助于更好地理解和运用设计模式:
| 原则 |
全称 |
核心思想 |
| S - 单一职责原则 |
Single Responsibility Principle |
一个类应该只有一个引起变化的原因,只负责一项职责。 |
| O - 开闭原则 |
Open/Closed Principle |
对扩展开放,对修改关闭。通过抽象和多态实现功能扩展。 |
| L - 里氏替换原则 |
Liskov Substitution Principle |
子类对象应能替换父类对象出现的任何地方,且不影响程序正确性。 |
| I - 接口隔离原则 |
Interface Segregation Principle |
使用多个专门的接口,而不是一个臃肿的总接口。 |
| D - 依赖倒置原则 |
Dependency Inversion Principle |
高层模块不依赖低层模块,二者都依赖抽象;抽象不依赖细节。 |
其他重要设计原则:
- 迪米特法则(最少知识原则):一个对象应对其他对象有尽可能少的了解,只与直接朋友通信。
- 合成复用原则:优先使用组合/聚合,而非继承来实现复用。
- DRY 原则(Don't Repeat Yourself):避免重复代码,通过抽象和封装消除冗余。
- KISS 原则(Keep It Simple, Stupid):尽量保持简单,避免不必要的复杂性。
设计模式选用指南
选择合适的设计模式需要经验积累,以下是一些实用建议:
- 不要为了模式而模式:先理解问题本质,过度设计比没有设计更糟糕。
- 从简单开始:当代码出现重复、高耦合、难以扩展时,再考虑引入模式进行重构。
- 组合使用:实际项目中多种模式往往配合使用,如工厂方法 + 策略模式,观察者 + 中介者模式等。
- 关注意图而非结构:不同模式可能结构相似,理解其设计意图比记住结构更重要。
常见模式对比与辨析
以下是一些容易混淆的设计模式对比,帮助在实际开发中做出正确选择:
| 对比维度 |
模式 A |
模式 B |
核心区别 |
| 创建型 |
工厂方法模式 |
抽象工厂模式 |
工厂方法创建单一产品;抽象工厂创建一族相关产品。 |
| 创建型 |
建造者模式 |
工厂模式 |
工厂关注"创建什么";建造者关注"如何一步步构建"。 |
| 结构型 |
适配器模式 |
装饰者模式 |
适配器改变接口使其兼容;装饰者不改变接口但增强功能。 |
| 结构型 |
代理模式 |
装饰者模式 |
代理控制访问(可能阻止);装饰者增强功能(不会阻止)。 |
| 行为型 |
策略模式 |
状态模式 |
策略由客户端主动选择算法;状态由对象内部状态自动切换行为。 |
| 行为型 |
观察者模式 |
中介者模式 |
观察者是一对多广播通知;中介者是多对多集中协调。 |
| 行为型 |
命令模式 |
策略模式 |
命令封装"做什么"(支持撤销/排队);策略封装"怎么做"(算法互换)。 |
结论
深入理解和正确应用设计模式可以显著提高软件开发的效率和质量。设计模式不仅提供了解决特定问题的标准方法,还促进了代码的可读性、可维护性和可扩展性。希望本文能够帮助你在软件设计和开发的旅程中,更加自信和从容地应对各种挑战。
探索结束,但学习之路永无止境。💡