第1章:策略模式(Strategy Pattern)
一、导读
1.1 本章学习目标
第1章介绍设计模式中最基础也最实用的模式之一——策略模式。策略模式的核心思想是:定义一系列算法,把它们封装起来,并且使它们可以互相替换。本章通过一个生动的鸭子模拟器的故事,让你直观理解策略模式的精髓。
本章的核心学习目标包括:
- 理解策略模式的意图:将变化的行为封装为独立的策略类。
- 掌握"组合优于继承"的原则:理解为什么继承不是解决行为变化的好方法。
- 学会使用接口编程:面向接口而非实现编程。
- 理解行为委托:将行为委托给策略对象。
- 掌握策略模式的适用场景:何时使用策略模式。
1.2 为什么第一章如此重要
策略模式是理解设计模式的起点。它展示了设计模式的核心思想:找出变化点,将变化封装起来。这个思想贯穿所有设计模式。
Head First 系列以独特的教学方式著称——通过故事、对话、谜题来传达知识。本章通过一个鸭子模拟器的故事,让你在轻松的氛围中理解策略模式的设计动机和实现方式。
1.3 核心问题引导
为什么继承不适合解决行为变化的问题?
什么是"组合优于继承"?
策略模式中的三个角色是什么?
如何实现策略的运行时切换?
策略模式有哪些实际应用场景?
二、核心概念详解
1.1 问题:鸭子模拟器的设计挑战
假设你在开发一个鸭子模拟器,最初的需求很简单:
// 初始设计
public abstract class Duck {
public void quack() {
System.out.println("Quack");
}
public void swim() {
System.out.println("All ducks swim");
}
public abstract void display();
}
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("I'm a Mallard Duck");
}
}
public class RubberDuck extends Duck {
@Override
public void quack() {
System.out.println("Squeak"); // 橡皮鸭不会嘎嘎叫
}
@Override
public void display() {
System.out.println("I'm a Rubber Duck");
}
}现在需求变了——需要鸭子能飞。最简单的做法是在基类中添加 fly() 方法:
public abstract class Duck {
public void quack() { System.out.println("Quack"); }
public void swim() { System.out.println("All ducks swim"); }
public void fly() { System.out.println("Flying..."); } // 新增
public abstract void display();
}问题来了:橡皮鸭不会飞!但因为它继承了 Duck,所以也会飞了。
1.2 继承方案的困境
为了解决橡皮鸭不能飞的问题,你可能会在子类中覆盖 fly() 方法:
public class RubberDuck extends Duck {
@Override
public void fly() {
// 什么都不做——橡皮鸭不会飞
}
}但这样做有几个问题:
代码重复:如果还有其他不会飞的鸭子,每个都要覆盖 fly()。
运行时行为不可知:编译时无法知道哪些鸭子会飞。
变化难以追踪:鸭子的行为(飞、叫)经常变化,每次变化都需要修改多个类。
1.3 策略模式的解决方案
策略模式的核心思想是:将变化的行为抽取出来,封装为独立的类。
// 定义行为接口
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
// 实现具体行为
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying with wings!");
}
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("I can't fly.");
}
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("Quack!");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("Squeak!");
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("...silence...");
}
}
// 使用组合代替继承
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {}
public abstract void display();
public void performFly() {
flyBehavior.fly(); // 委托给行为对象
}
public void performQuack() {
quackBehavior.quack(); // 委托给行为对象
}
public void swim() {
System.out.println("All ducks swim.");
}
// 运行时切换行为
public void setFlyBehavior(FlyBehavior fb) {
this.flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
this.quackBehavior = qb;
}
}
// 具体鸭子
public class MallardDuck extends Duck {
public MallardDuck() {
flyBehavior = new FlyWithWings();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I'm a Mallard Duck");
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Squeak();
}
@Override
public void display() {
System.out.println("I'm a Rubber Duck");
}
}
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("I'm a Model Duck");
}
}1.4 策略模式的结构
策略模式包含三个角色:
策略接口(Strategy):定义所有具体策略的公共接口。
具体策略(Concrete Strategy):实现策略接口的具体算法。
上下文(Context):持有一个策略对象的引用,将请求委托给策略对象。
┌─────────────────┐
│ Context │
│─────────────────│
│ - strategy │──────┐
│─────────────────│ │
│ + request() │ │
└─────────────────┘ │
│
┌─────────────────┐ │
│ <<interface>> │◄─────┘
│ Strategy │
│─────────────────│
│ + algorithm() │
└─────────────────┘
▲
│
┌───────┴───────┐
│ ConcreteStratA │ ConcreteStratB
│───────────────│
│ + algorithm() │
└───────────────┘1.5 运行时动态切换策略
策略模式的一个强大优势是可以在运行时切换行为:
public class DuckSimulator {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.display(); // I'm a Model Duck
model.performFly(); // I can't fly.
// 运行时切换飞行策略
model.setFlyBehavior(new FlyWithWings());
model.performFly(); // I'm flying with wings!
}
}1.6 设计原则
本章体现了两个重要的设计原则:
找出变化点,将它们封装起来:将鸭子的飞行和叫声行为封装为独立的策略类。
组合优于继承:使用组合(持有行为对象的引用)代替继承(在子类中覆盖方法)。
三、实战案例
3.1 支付策略
// 策略接口
public interface PaymentStrategy {
PaymentResult pay(Order order);
}
// 具体策略
public class CreditCardPayment implements PaymentStrategy {
@Override
public PaymentResult pay(Order order) {
// 信用卡支付逻辑
return new PaymentResult(true, "Paid with credit card");
}
}
public class AlipayPayment implements PaymentStrategy {
@Override
public PaymentResult pay(Order order) {
// 支付宝支付逻辑
return new PaymentResult(true, "Paid with Alipay");
}
}
public class WechatPayPayment implements PaymentStrategy {
@Override
public PaymentResult pay(Order order) {
// 微信支付逻辑
return new PaymentResult(true, "Paid with WeChat Pay");
}
}
// 上下文
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public PaymentResult executePayment(Order order) {
return strategy.pay(order);
}
}
// 使用
PaymentContext context = new PaymentContext(new CreditCardPayment());
PaymentResult result = context.executePayment(order);
// 运行时切换
context.setStrategy(new AlipayPayment());
result = context.executePayment(order);3.2 排序策略
// 策略接口
public interface SortStrategy {
<T extends Comparable<T>> List<T> sort(List<T> list);
}
// 具体策略
public class QuickSort implements SortStrategy {
@Override
public <T extends Comparable<T>> List<T> sort(List<T> list) {
// 快速排序实现
List<T> result = new ArrayList<>(list);
quickSort(result, 0, result.size() - 1);
return result;
}
private <T extends Comparable<T>> void quickSort(List<T> list, int low, int high) {
if (low < high) {
int pivot = partition(list, low, high);
quickSort(list, low, pivot - 1);
quickSort(list, pivot + 1, high);
}
}
private <T extends Comparable<T>> int partition(List<T> list, int low, int high) {
T pivot = list.get(high);
int i = low - 1;
for (int j = low; j < high; j++) {
if (list.get(j).compareTo(pivot) <= 0) {
i++;
Collections.swap(list, i, j);
}
}
Collections.swap(list, i + 1, high);
return i + 1;
}
}
public class MergeSort implements SortStrategy {
@Override
public <T extends Comparable<T>> List<T> sort(List<T> list) {
// 归并排序实现
if (list.size() <= 1) return list;
int mid = list.size() / 2;
List<T> left = sort(list.subList(0, mid));
List<T> right = sort(list.subList(mid, list.size()));
return merge(left, right);
}
private <T extends Comparable<T>> List<T> merge(List<T> left, List<T> right) {
List<T> result = new ArrayList<>();
int i = 0, j = 0;
while (i < left.size() && j < right.size()) {
if (left.get(i).compareTo(right.get(j)) <= 0) {
result.add(left.get(i++));
} else {
result.add(right.get(j++));
}
}
while (i < left.size()) result.add(left.get(i++));
while (j < right.size()) result.add(right.get(j++));
return result;
}
}
// 上下文
public class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public <T extends Comparable<T>> List<T> sort(List<T> list) {
return strategy.sort(list);
}
}
// 使用
Sorter sorter = new Sorter(new QuickSort());
List<Integer> sorted = sorter.sort(List.of(5, 2, 8, 1, 9, 3));
// 对于小数据集,切换到插入排序
sorter.setStrategy(new InsertionSort());
sorted = sorter.sort(List.of(5, 2, 8));四、本章小结
第1章通过鸭子模拟器的故事,讲解了策略模式的核心知识:
策略模式的意图:定义一系列算法,把它们封装起来,使它们可以互相替换。
组合优于继承:使用组合代替继承来应对行为变化。
面向接口编程:策略接口定义了所有策略的公共行为。
运行时切换:可以在运行时动态更换策略。
设计原则:找出变化点,封装变化。
策略模式是最简单也最实用的设计模式之一。当你发现代码中有大量的 if-else 或 switch-case 来选择不同的行为时,考虑使用策略模式。
五、思考与练习
思考题
策略模式和 if-else 相比有什么优势?
策略模式会增加类的数量,这是否是一个问题?
策略模式和状态模式有什么区别?
在什么场景下应该使用策略模式?
策略模式如何与工厂模式配合使用?
编程练习
实现导航策略:设计一个导航系统,支持多种路线规划策略(最短路径、最快路径、避开高速)。
实现压缩策略:设计一个文件压缩工具,支持多种压缩算法(ZIP、GZIP、7Z)。
实现验证策略:设计一个表单验证系统,支持多种验证规则(邮箱、手机号、密码强度)。