策略模式(Strategy)

本文通过设计一款模拟鸭子游戏的过程,详细介绍了如何运用策略模式解决游戏中鸭子行为的扩展性和灵活性问题。从最初的简单继承,到发现继承方式无法应对频繁的功能变更,最终采用策略模式将鸭子的行为抽象为独立的接口和实现类,实现了行为的动态组合和替换,使得游戏设计更加模块化和易于维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由一个场景来引出问题

场景

有一天,公司要做一个模拟鸭子游戏,游戏里有各种鸭子,会游泳,会叫。你会怎么设计?

最初的设计

设计一个鸭子的超类(AbstractDuck),并让各个鸭子子类继承。

abstract class AbstractDuck {
    void quack() {
        System.out.println("呱呱叫");
    }
    void swim() {
        System.out.println("游泳");
    }
    /**
     * 由于每一种鸭子外观不同,所以该方法是抽象的
     */
    abstract void display();
}
场景

当游戏上线后,市场竞争加剧,需要迭代更新游戏,这时产品说要让鸭子能飞,你会怎样设计?

第1次修改尝试

在AbstractDuck中加上fly()方法。

    void fly() {
        System.out.println("飞");
    }
出现问题

所有的鸭子都能飞了,连橡皮鸭子都能飞了,可怕......

第2次修改尝试

橡皮鸭不会呱呱叫,所以把quack()方法覆盖成吱吱叫。但是“飞”这里怎么处理?像覆盖“呱呱叫”方法一样覆盖掉?但是如果以后加入诱饵鸭怎么办?诱饵鸭是木头的假鸭,不会飞也不会叫,难道要把“飞”和“叫”方法都覆盖成什么也不做?

思考

继承的方式是行不通的,因为如果产品说以后每个月都要更新产品,那么每次新的鸭子出现,都要被迫检查并可能需要覆盖“飞”和“叫”方法,做这么多无用功,很蛋疼。那么如果把某些功能变成接口呢?比如把“飞”从超类中抽出来,放进一个“Flyable”接口中,只有会飞的鸭子才实现此接口。同样,其他的功能也如此处理。但是这样也有一个问题,很多种鸭子都会飞,每次都要实现这个接口中的“飞”方法,写重复的代码,累死人,怎么办?设计模式的思想是分离易变和不变部分,这里可以把鸭子的行为从Duck类中抽出来。

分离变化和不会变化的部分

从哪里开始呢?目前来看,只有“飞”和“叫”有点问题,其它地方暂时没啥问题,依照“不过早优化”原则,没出问题的就先不动它。

第3次修改尝试

这里又有另一种思想“针对接口编程”,这样才能抽象化。其实还是把“飞”和“叫”抽成接口,但是此时不是鸭子的子类去实现这两个接口了,而是每个接口都有自己的一组实现类,创建鸭子子类的时候在指定具体的“飞”和“叫”的实现类。这样行为成为一套独立的体系,鸭子成为一套独立的体系。只有用到的时候再从两个体系中取具体实现组合起来。

// 新的鸭子超类
abstract class AbstractDuck {
    QuackBehavior quackBehavior;
    FlyBehavior flyBehavior;
    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }
    void swim() {
        System.out.println("游泳");
    }
    void fly() {
        flyBehavior.fly();
    }
    void quack() {
        quackBehavior.quack();
    }
    /**
     * 外观:由于每一种鸭子外观不同,所以该方法是抽象的
     */
    abstract void display();
}

飞的接口和实现类

public interface FlyBehavior {
    void fly();
}
public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("不会飞");
    }
}
public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("飞");
    }
}

叫的接口和实现类

public interface QuackBehavior {
    void quack();
}
public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("呱呱叫");
    }
}
public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("吱吱叫");
    }
}
public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("不会叫,叫不出声");
    }
}

使用

public class MallardDuck extends AbstractDuck {
    public MallardDuck(QuackBehavior quackBehavior, FlyBehavior flyBehavior) {
        super.quackBehavior = quackBehavior;
        super.flyBehavior = flyBehavior;
    }
    @Override
    void display() {
        System.out.println("绿头鸭");
    }
}
测试
public class StrategyTest {
    public static void main(String[] args) {
        MallardDuck mallardDuck = new MallardDuck(new Quack(), new FlyWithWings());
        mallardDuck.display();
        mallardDuck.swim();
        mallardDuck.quack();
        //中途可以灵活改变叫声
        mallardDuck.setQuackBehavior(new Squeak());
        mallardDuck.quack();
        mallardDuck.fly();
    }
}
//绿头鸭
//游泳
//呱呱叫
//吱吱叫
//飞

这里如果不再把鸭子的行为说成是“一组行为”,而是想成“一族算法”。特别注意类之间的“关系”,AbstractDuck和飞行为以及叫行为之间的关系是HAS-A(有一个)。

策略模式(Strategy Pattern)定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

转载于:https://my.oschina.net/qingjing/blog/2962016

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值