第1章:导论(Introduction to Design Patterns)
一、导读
1.1 本章学习目标
第1章是《设计模式:可复用面向对象软件的基础》的开篇,GoF(Gang of Four)在本章中介绍了面向对象设计的核心思想和设计模式的由来。本章为理解后续所有模式奠定了理论基础。
本章的核心学习目标包括:
- 理解面向对象设计的核心原则:封装、继承、多态
- 掌握组合与继承的权衡:为什么优先使用组合而非继承
- 理解接口的重要性:定义对象间的契约
- 掌握"编程到接口而不是实现":这一核心设计原则
- 了解设计模式的分类:创建型、结构型、行为型
- 理解设计模式的价值:复用经过验证的设计方案
1.2 为什么本章如此重要
设计模式不是具体的代码,而是解决问题的通用方案。GoF 在开篇就强调:学习设计模式的目的是培养一种"设计思维"——能够识别常见的设计问题,并应用经过验证的解决方案。
本章之所以重要,是因为它建立了理解所有设计模式的理论框架。不理解面向对象的核心原则,就无法理解为什么设计模式要这样设计。正如建筑学中的"形式追随功能",设计模式中的每个结构都有其设计原则的支撑。
1.3 核心问题引导
面向对象设计的三大特性是什么?它们各自解决什么问题?
为什么 GoF 说"优先使用组合而非继承"?
"编程到接口而不是实现"到底意味着什么?
设计模式的三大分类各自解决什么问题?
设计模式与框架有什么区别?
如何判断一个设计方案是否是一个好的"模式"?
二、核心概念详解
2.1 面向对象设计的核心
2.1.1 封装(Encapsulation)
封装是将数据和操作数据的方法绑定在一起,同时隐藏内部实现细节。封装的好处是降低耦合——调用者只需要知道接口,不需要了解实现。
// 好的封装
public class BankAccount {
private double balance; // 隐藏内部状态
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
this.balance += amount;
}
public double getBalance() {
return this.balance;
}
// 内部实现可以改变,不影响外部调用者
}2.1.2 继承(Inheritance)
继承允许一个类(子类)获得另一个类(父类)的属性和方法。继承的好处是代码复用和建立类型层次。
但 GoF 警告:继承是一种白盒复用——子类的实现与父类紧密耦合。父类的任何改变都可能影响子类。
// 继承的问题:紧耦合
public class Animal {
public void eat() { System.out.println("Eating..."); }
public void sleep() { System.out.println("Sleeping..."); }
}
public class Dog extends Animal {
public void bark() { System.out.println("Woof!"); }
}
// 如果 Animal 的 eat() 方法改变,Dog 也会受到影响
// 而且 Dog 必须继承 Animal 的所有行为,即使它不需要某些行为2.1.3 多态(Polymorphism)
多态允许不同类型的对象对同一消息做出不同的响应。多态的好处是解耦——调用者不需要知道对象的具体类型。
// 多态的威力
public interface Shape {
double area();
}
public class Circle implements Shape {
private double radius;
public double area() { return Math.PI * radius * radius; }
}
public class Rectangle implements Shape {
private double width, height;
public double area() { return width * height; }
}
// 调用者不需要知道具体类型
public double totalArea(Shape[] shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area(); // 多态调用
}
return total;
}2.2 组合 vs 继承
GoF 反复强调的一个核心原则是:优先使用组合而非继承。
继承的问题:
- 继承是"白盒复用"——子类看到了父类的内部实现
- 继承关系在编译时就确定了,无法在运行时改变
- 父类的改变会影响所有子类
- 继承层次过深会导致类爆炸
组合的优势:
- 组合是"黑盒复用"——只使用组件的接口
- 组合关系在运行时可以动态改变
- 组件的改变不影响使用它的对象(只要接口不变)
- 组合更加灵活,易于扩展
// 继承方式:类爆炸问题
// 如果需要不同颜色、不同形状的窗口:
class RedCircleWindow extends CircleWindow { /* 红色 */ }
class BlueCircleWindow extends CircleWindow { /* 蓝色 */ }
class RedSquareWindow extends SquareWindow { /* 红色 */ }
class BlueSquareWindow extends SquareWindow { /* 蓝色 */ }
// 每增加一种颜色或形状,类的数量就翻倍
// 组合方式:灵活且可扩展
class Window {
private Shape shape;
private Color color;
Window(Shape shape, Color color) {
this.shape = shape;
this.color = color;
}
void draw() {
shape.draw();
color.apply();
}
}
// 可以任意组合形状和颜色
Window w1 = new Window(new Circle(), new Red());
Window w2 = new Window(new Square(), new Blue());2.3 编程到接口而不是实现
这是 GoF 提出的最重要的设计原则之一。它的含义是:
- 声明变量类型时,不要声明具体的类,而应声明接口
- 不要基于具体类编程,而应基于接口编程
- 不要硬编码具体的实现,而应通过接口来交互
// 差的实现:基于具体类编程
public class OrderProcessor {
private ArrayList<Order> orders = new ArrayList<>(); // 硬编码了 ArrayList
public void addOrder(Order order) {
orders.add(order);
}
}
// 好的实现:基于接口编程
public class OrderProcessor {
private List<Order> orders; // 使用接口 List
public OrderProcessor(List<Order> orders) {
this.orders = orders; // 可以在运行时选择不同的实现
}
}
// 使用
OrderProcessor p1 = new OrderProcessor(new ArrayList<>());
OrderProcessor p2 = new OrderProcessor(new LinkedList<>());2.4 设计模式的分类
GoF 将 23 个设计模式分为三大类:
2.4.1 创建型模式(Creational Patterns)
创建型模式封装了对象创建的过程。它们的目标是:将对象的创建与使用分离,使系统设计更加灵活。
- Factory Method:定义创建对象的接口,让子类决定实例化哪个类
- Abstract Factory:创建一系列相关对象,而不需要指定具体类
- Builder:分步骤构建复杂对象
- Prototype:通过复制现有对象创建新对象
- Singleton:确保一个类只有一个实例
2.4.2 结构型模式(Structural Patterns)
结构型模式关注如何组合类和对象来形成更大的结构。它们的目标是:在不改变现有类的情况下,改变其行为或接口。
- Adapter:让接口不兼容的类可以协同工作
- Bridge:将抽象部分与实现部分分离
- Composite:将对象组合成树形结构
- Decorator:动态地给对象添加职责
- Facade:为子系统提供统一的接口
- Flyweight:共享细粒度对象
- Proxy:为其他对象提供代理以控制访问
2.4.3 行为型模式(Behavioral Patterns)
行为型模式关注对象之间的通信和职责分配。它们的目标是:使对象之间的交互更加灵活。
- Chain of Responsibility:将请求沿处理者链传递
- Command:将请求封装为对象
- Iterator:顺序访问聚合对象的元素
- Mediator:用中介对象封装一系列对象的交互
- Observer:定义对象间的一对多依赖关系
- State:允许对象在内部状态改变时改变行为
- Strategy:定义一系列算法并封装起来
- Template Method:定义算法骨架,让子类实现细节
- Visitor:在不修改类的情况下定义新操作
2.5 如何描述一个设计模式
GoF 为每个模式定义了统一的描述格式:
- 模式名称:一个词或两个词,概括模式的本质
- 意图:模式解决什么问题?
- 别名:模式的其他名称
- 动机:一个具体的场景,展示问题和解决方案
- 适用性:什么情况下使用这个模式?
- 结构:模式的类/对象结构图
- 参与者:模式中涉及的类/对象
- 协作:参与者如何协作
- 效果:模式带来的好处和代价
- 实现:实现模式的技术和技巧
- 代码示例:具体的代码实现
- 已知应用:模式在实际系统中的应用
- 相关模式:与其他模式的关系
三、实战案例
3.1 案例:一个简单的文本编辑器
以下展示如何用设计模式思维设计一个文本编辑器:
// 使用策略模式来处理不同的格式化算法
interface Formatter {
String format(String text);
}
class PlainTextFormatter implements Formatter {
public String format(String text) { return text; }
}
class HtmlFormatter implements Formatter {
public String format(String text) { return "<p>" + text + "</p>"; }
}
class MarkdownFormatter implements Formatter {
public String format(String text) { return "**" + text + "**"; }
}
// 使用观察者模式来通知视图更新
interface Observer {
void update(String content);
}
class TextView implements Observer {
public void update(String content) {
System.out.println("Display: " + content);
}
}
// 使用命令模式来支持撤销操作
interface Command {
void execute();
void undo();
}
class InsertCommand implements Command {
private Document doc;
private String text;
private int position;
public void execute() { doc.insert(position, text); }
public void undo() { doc.delete(position, text.length()); }
}
// 组合使用多个模式
class TextEditor {
private Document document;
private Formatter formatter;
private List<Observer> observers;
private Stack<Command> undoStack;
void setFormatter(Formatter formatter) {
this.formatter = formatter; // 策略模式:运行时切换算法
}
void executeCommand(Command command) {
command.execute();
undoStack.push(command); // 命令模式:支持撤销
notifyObservers();
}
void undo() {
if (!undoStack.isEmpty()) {
undoStack.pop().undo();
notifyObservers();
}
}
void addObserver(Observer observer) {
observers.add(observer); // 观察者模式:通知更新
}
void notifyObservers() {
String content = formatter.format(document.getContent());
for (Observer observer : observers) {
observer.update(content);
}
}
}四、本章小结
第1章建立了设计模式的理论基础:
面向对象三大特性:封装、继承、多态各有其用途和局限
优先使用组合而非继承:组合更灵活、耦合更低
编程到接口而不是实现:依赖抽象而非具体实现
设计模式三大分类:创建型、结构型、行为型
模式的描述格式:统一的格式便于学习和交流
本章的核心思想是:设计模式是经过验证的面向对象设计经验的总结。学习设计模式不是记住代码结构,而是培养一种设计思维。
五、思考与练习
思考题
"优先使用组合而非继承"是否意味着完全不用继承?
设计模式与编程语言有什么关系?不同语言需要不同的模式吗?
如何判断一个设计方案是否是一个好的"模式"?
设计模式是否会过度设计?如何避免?
编程练习
找到一个使用继承的场景,尝试用组合来替代。
找到一段"编程到实现"的代码,重构为"编程到接口"。
分析一个你熟悉的框架(如 Spring),识别其中使用了哪些设计模式。