1设计模式

导论

面向对象的威力与挑战

面向对象组合继承接口

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第1章:导论(Introduction to Design Patterns)

一、导读

1.1 本章学习目标

第1章是《设计模式:可复用面向对象软件的基础》的开篇,GoF(Gang of Four)在本章中介绍了面向对象设计的核心思想和设计模式的由来。本章为理解后续所有模式奠定了理论基础。

本章的核心学习目标包括:

  • 理解面向对象设计的核心原则:封装、继承、多态
  • 掌握组合与继承的权衡:为什么优先使用组合而非继承
  • 理解接口的重要性:定义对象间的契约
  • 掌握"编程到接口而不是实现":这一核心设计原则
  • 了解设计模式的分类:创建型、结构型、行为型
  • 理解设计模式的价值:复用经过验证的设计方案

1.2 为什么本章如此重要

设计模式不是具体的代码,而是解决问题的通用方案。GoF 在开篇就强调:学习设计模式的目的是培养一种"设计思维"——能够识别常见的设计问题,并应用经过验证的解决方案。

本章之所以重要,是因为它建立了理解所有设计模式的理论框架。不理解面向对象的核心原则,就无法理解为什么设计模式要这样设计。正如建筑学中的"形式追随功能",设计模式中的每个结构都有其设计原则的支撑。

1.3 核心问题引导

面向对象设计的三大特性是什么?它们各自解决什么问题?

为什么 GoF 说"优先使用组合而非继承"?

"编程到接口而不是实现"到底意味着什么?

设计模式的三大分类各自解决什么问题?

设计模式与框架有什么区别?

如何判断一个设计方案是否是一个好的"模式"?


二、核心概念详解

2.1 面向对象设计的核心

2.1.1 封装(Encapsulation)

封装是将数据和操作数据的方法绑定在一起,同时隐藏内部实现细节。封装的好处是降低耦合——调用者只需要知道接口,不需要了解实现。

java
// 好的封装
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 警告:继承是一种白盒复用——子类的实现与父类紧密耦合。父类的任何改变都可能影响子类。

java
// 继承的问题:紧耦合
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)

多态允许不同类型的对象对同一消息做出不同的响应。多态的好处是解耦——调用者不需要知道对象的具体类型。

java
// 多态的威力
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 反复强调的一个核心原则是:优先使用组合而非继承。

继承的问题:

  • 继承是"白盒复用"——子类看到了父类的内部实现
  • 继承关系在编译时就确定了,无法在运行时改变
  • 父类的改变会影响所有子类
  • 继承层次过深会导致类爆炸

组合的优势:

  • 组合是"黑盒复用"——只使用组件的接口
  • 组合关系在运行时可以动态改变
  • 组件的改变不影响使用它的对象(只要接口不变)
  • 组合更加灵活,易于扩展
java
// 继承方式:类爆炸问题
// 如果需要不同颜色、不同形状的窗口:
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 提出的最重要的设计原则之一。它的含义是:

  • 声明变量类型时,不要声明具体的类,而应声明接口
  • 不要基于具体类编程,而应基于接口编程
  • 不要硬编码具体的实现,而应通过接口来交互
java
// 差的实现:基于具体类编程
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 案例:一个简单的文本编辑器

以下展示如何用设计模式思维设计一个文本编辑器:

java
// 使用策略模式来处理不同的格式化算法
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),识别其中使用了哪些设计模式。


1/6导论
0%