策略模式

策略模式

  • 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.

设计原则 #

  1. 找出应用之中可以变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  2. 针对接口编程,而不是针对实现编程
    • 客户的行为可以抽象为接口的,不必让客户去实现接口。如果这样做,客户想改变行为需要不断地去编写 实现。这样的行为,可以理解为针对实现编程。
    • 鉴于此,可以在别处实现接口,客户只需要根据接口来选择合适的行为,这样做客户的代码更简洁且便于 维护。
  3. 多用组合,少用继承
    • 将两(多)个类组合起来使用,就是组合(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)是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换。策略模式使算法可以在不影响客户端的情况下发生变化。

  1. 核心思想: 将算法的定义和使用分离,使得算法可以独立于使用它的客户而变化。

  2. 组成部分:

    • Context(上下文): 持有一个 Strategy 对象的引用。Context 不负责算法的具体实现,而是负责将请求委托给 Strategy 对象。对应示例代码中的抽象基类Duck.java

    • Strategy(策略接口/抽象策略): 定义所有支持的算法的公共接口。Context 通过这个接口调用具体的算法。对应可变功能接口及其实现,如FlyBehavior.javaQuarkBehavior.java

    • ConcreteStrategy(具体策略): 实现 Strategy 接口,封装了具体的算法。对应具体的实现了,如示例中的MallardDuck.java

  3. 优点:

    • 算法独立变化: 可以自由切换、增加、删除算法,互不影响。

    • 避免了大量的 if-elseswitch 语句: 将条件判断逻辑分散到各个策略类中,使代码更简洁、易维护。

    • 提高了代码的可扩展性: 容易添加新的策略,符合开闭原则

    • 简化了算法的复杂性: 每个策略只负责一个具体的算法,降低了单个算法的复杂性。

  4. 缺点:

    • 策略类增多: 每个算法都需要一个策略类,可能导致类的数量增加。

    • 客户端需要了解所有策略: 客户端必须知道有哪些策略可以选择,并根据需要选择合适的策略。

  5. 适用场景:

    • 需要动态地切换算法或行为。

    • 希望封装算法,使其独立于使用它的客户而变化。

    • 当不同的行为有很多相似的代码时(可以通过提取公共部分到抽象类或接口中来避免代码重复)。

  6. 简单比喻:

    想象你有很多种支付方式:信用卡、支付宝、微信支付。

    • Context(上下文): 商店的收银台。

    • Strategy(策略接口): 支付方式接口,定义了 pay() 方法。

    • ConcreteStrategy(具体策略): 信用卡支付、支付宝支付、微信支付,每个类都实现了 pay() 方法,但具体实现不同。

    当顾客结账时,收银台(Context)会根据顾客选择的支付方式(策略)来执行相应的支付操作。