策略模式
by Head First 设计模式:
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换。此模式让算法的变化独立于使用算法的"客户"。
by Dive into Design Patterns:
Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.
设计原则 #
- 找出应用之中可以变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 客户的行为可以抽象为接口的,不必让客户去实现接口。如果这样做,客户想改变行为需要不断地去编写 实现。这样的行为,可以理解为针对实现编程。
- 鉴于此,可以在别处实现接口,客户只需要根据接口来选择合适的行为,这样做客户的代码更简洁且便于 维护。
- 多用组合,少用继承
- 将两(多)个类组合起来使用,就是组合(composition),这样比使用继承好的一点是:系统的弹性 更大,并且可以避免使用继承不得不出现的无意义重写(override)一些需要规避掉的方法。
UML简图 #
classDiagram direction UD class Charactor{ << Abstract >> +WeaponBehavior weaponBehavior +fight()* +setWeapon(WeaponBeahvior) } Charactor <|-- King: Is A class King King: +fight() class Queen Queen: +fight() Charactor <|-- Queen: Is A class Knight Knight: +fight() Charactor <|-- Knight: Is A class WeaponBehavior Charactor *--> WeaponBehavior : Has A << Interface >> WeaponBehavior WeaponBehavior: +useWeapon()* calss SwordBehavior WeaponBehavior <|.. SwordBehavior : Implement SwordBehavior: +useWeapon() calss AxeBehavior WeaponBehavior <|.. AxeBehavior : Implement AxeBehavior: +useWeapon() class KnifeBehavior WeaponBehavior <|.. KnifeBehavior: Implement KnifeBehavior: +useWeapon()
通过UML图,可以看到策略模式使用接口概括性地定义功能。比如游戏里的角色,都可以攻击,根据定位不同,使用不同的武器。所以可以把武器行为定义为接口。
不同的角色,根据抽象基类设置不同的武器行为,可以完成行为。 这就是一个典型的策略模式的应用场景。
示例代码 #
本次的示例中,演示了“🦆”这个大类,会游泳,会叫,但是并不是所有的🦆都会飞~这些都是鸭子的能力。将鸭子的能力抽象为接口,然后通过基类给鸭子实例分配能力,这就是所谓“策略”。
功能接口 #
这些接口定义了不同实例的可变功能(行为),这些行为可能有多种实现。比如攻击(远程/近战),支付(微信/支付宝)等。本例以🦆的行为为例,简单地演示:
”飞“的行为
1public interface FlyBehavior {
2
3 void fly();
4}
5
6public class FlyWithWings implements FlyBehavior{
7
8 @Override
9 public void fly() {
10 System.out.println("Yes! I can fly with wings!");
11 }
12}
1public interface QuarkBehavior {
2 void quark();
3}
4
5public class Quark implements QuarkBehavior{
6 @Override
7 public void quark() {
8 System.out.println("Quark!");
9 }
10}
抽象基类及其实现 #
1public abstract class Duck {
2
3 // 变化的部分
4 protected FlyBehavior flyBehavior;
5 protected QuarkBehavior quarkBehavior;
6
7 // 不变的部分
8 public abstract void swim();
9 public abstract void display();
10
11 // fly and quark
12 // 由于并不是所有的"鸭子"实现不能都会飞或者叫
13 // 实际开发中经常遇到实现并不需要全部的功能这种情况)
14 // 于是把"变化的部分"独立出去,鸭子类更易于拓展,
15 // 否则可能需要处理很多无用的覆写啦😄
16 // 实际上变化的功能,交给具体的实现去做啦
17 /*
18 * PS: 让鸭子实现直接实现FlyBehavior接口的话,
19 * 也相当于只做了一半的工作。改变鸭子的行为,
20 * 依然需要改变实现,这就是所谓"面对实现编程"
21 */
22 public void performFly() {
23 flyBehavior.fly();
24 }
25 public void performQuark() {
26 quarkBehavior.quark();
27 }
28
29 // 通过使用策略模式,不局限于规范行为的接口,可以动态改变实现的行为
30 public void setFlyBehavior(FlyBehavior fb) {
31 this.flyBehavior = fb;
32 }
33 public void setQuarkBehavior(QuarkBehavior qb) {
34 this.quarkBehavior = qb;
35 }
36}
37
38public class MallardDuck extends Duck {
39
40 public MallardDuck() {
41 this.quarkBehavior = new Quark();
42 this.flyBehavior = new FlyWithWings();
43 }
44
45 @Override
46 public void swim() {
47 //...
48 }
49
50 @Override
51 public void display() {
52 //...
53 }
54
55}
测试客户端 #
1public class DuckTest {
2
3 public static void main(String[] args) {
4 // 这种🦆的飞/叫行为已经在策略里定义了
5 MallardDuck mock = new MallardDuck();
6 mock.performFly();
7 mock.performQuark();
8 // 改变行为试试
9 mock.setFlyBehavior(new FlyWithRocket());
10 mock.performFly();
11 }
12}
13
14///:~
15//Yes! I can fly with wings!
16//Quark!
17//Oh! I can fly with a rocket booster!
18//
模式总结 #
Generated by gemini, revised.
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式使算法可以在不影响客户端的情况下发生变化。
核心思想: 将算法的定义和使用分离,使得算法可以独立于使用它的客户而变化。
组成部分:
• Context(上下文): 持有一个 Strategy 对象的引用。Context 不负责算法的具体实现,而是负责将请求委托给 Strategy 对象。对应示例代码中的抽象基类
Duck.java
。• Strategy(策略接口/抽象策略): 定义所有支持的算法的公共接口。Context 通过这个接口调用具体的算法。对应可变功能接口及其实现,如
FlyBehavior.java
和QuarkBehavior.java
。• ConcreteStrategy(具体策略): 实现 Strategy 接口,封装了具体的算法。对应具体的实现了,如示例中的
MallardDuck.java
。优点:
• 算法独立变化: 可以自由切换、增加、删除算法,互不影响。
• 避免了大量的
if-else
或switch
语句: 将条件判断逻辑分散到各个策略类中,使代码更简洁、易维护。• 提高了代码的可扩展性: 容易添加新的策略,符合开闭原则。
• 简化了算法的复杂性: 每个策略只负责一个具体的算法,降低了单个算法的复杂性。
缺点:
• 策略类增多: 每个算法都需要一个策略类,可能导致类的数量增加。
• 客户端需要了解所有策略: 客户端必须知道有哪些策略可以选择,并根据需要选择合适的策略。
适用场景:
• 需要动态地切换算法或行为。
• 希望封装算法,使其独立于使用它的客户而变化。
• 当不同的行为有很多相似的代码时(可以通过提取公共部分到抽象类或接口中来避免代码重复)。
简单比喻:
想象你有很多种支付方式:信用卡、支付宝、微信支付。
• Context(上下文): 商店的收银台。
• Strategy(策略接口): 支付方式接口,定义了 pay() 方法。
• ConcreteStrategy(具体策略): 信用卡支付、支付宝支付、微信支付,每个类都实现了 pay() 方法,但具体实现不同。
当顾客结账时,收银台(Context)会根据顾客选择的支付方式(策略)来执行相应的支付操作。