策略、模板方法和状态模式都在隔离变化,但变化的对象不同:策略替换一组算法,模板方法固定流程并开放部分步骤,状态模式让同一操作随内部状态产生不同响应。

下面沿用我读设计模式时的武器、饮品和投币机类比。它们是教学示例,不代表实现过这些业务系统;相关概念可结合《Head First 设计模式》阅读。

策略:角色把攻击行为交给一个接口

如果把所有武器行为写进角色父类,每加一种武器都可能影响不相关角色。把变化拆成接口,角色只负责委托:

interface WeaponBehavior {
    void useWeapon();
}

class GameCharacter {
    private WeaponBehavior weapon;

    GameCharacter(WeaponBehavior weapon) {
        setWeapon(weapon);
    }

    void setWeapon(WeaponBehavior weapon) {
        this.weapon = java.util.Objects.requireNonNull(weapon);
    }

    void attack() {
        weapon.useWeapon();
    }
}

调用片段可以是:

GameCharacter player = new GameCharacter(
        () -> System.out.println("使用匕首"));
player.attack();
player.setWeapon(() -> System.out.println("使用弓箭"));
player.attack();

角色不需要知道武器的实现细节。策略可以由配置、调用者或业务规则选择,不要求每个项目都在运行时切换。新增武器仍可能需要改装配、资源和权限规则,“增加一个类”不等于整个系统零修改。

模板方法:顺序固定,部分步骤交给子类

泡茶和泡咖啡都经历烧水、冲泡、倒杯和可选加料,变化的是具体冲泡与配料,而不是所有步骤都一样:

abstract class Drink {
    public final void prepare() {
        boilWater();
        brew();
        pourIntoCup();
        if (wantsCondiments()) addCondiments();
    }

    private void boilWater() {
        System.out.println("烧水");
    }

    private void pourIntoCup() {
        System.out.println("倒入杯中");
    }

    protected abstract void brew();
    protected abstract void addCondiments();

    protected boolean wantsCondiments() {
        return true;
    }
}

子类实现冲泡与配料,需要不加料时覆写钩子 wantsCondiments() 返回 false。prepare() 的 final 保持流程骨架不被覆写;钩子提供默认行为,让子类可选扩展。

这里依靠继承扩展部分步骤,与角色持有策略对象的组合方式不同。不能仅因框架调用了一个可覆写方法,就认定任何实现都严格对应某一本书的类图。

状态:同一个动作,在不同状态下含义不同

投币机可以先写出转换规则,再考虑是否拆成状态类:

当前状态投币退币转动摇杆
未投币且有库存进入已投币拒绝,无币可退拒绝
已投币拒绝重复投币退币并回到未投币进入出货流程
出货中拒绝拒绝拒绝重复出货
售罄拒绝拒绝拒绝

出货完成后根据剩余库存转回未投币或售罄。若库存不足或出货异常,退款与补偿需要另外定义,不由模式自动解决。

状态接口可以统一声明投币、退币和转动操作,具体状态决定响应;上下文持有当前状态并完成转换。这样避免每个操作重复一大段状态判断,但转换约束、并发和重复请求仍需设计。

怎么选择,而不是怎么套名字

需要替换同类算法,先考虑策略;需要稳定步骤和扩展点,考虑模板方法;行为由生命周期阶段决定,先画清状态转换,再判断状态对象是否有助维护。

策略由调用者选择、状态常由内部事件驱动,是有用的理解角度,不是不可突破的规则。少量清晰分支不必为消除 if 而拆成很多类。类似“包一层”的模式区别,见 装饰、适配、外观与代理。

参考:《Head First Design Patterns》出版方目录。这里引用其学习主题,不据此声称已核对某一版译本的全部章节。