设计模式:基于《Head First 设计模式》的学习之路(二)--策略模式

一、问题的提出

先从简单的模拟鸭子应用做起:

制作鸭子游戏:游戏中会出现各种鸭子,一边游泳,一边呱呱叫。此系统的内部设计使用了OO技术,设计一个鸭子超类(SuperClass),并让鸭子继承该超类。

现在让鸭子能飞:

如果只在duck类上加上fly()方法:

这样的话,所有的鸭子都会飞,本来不该飞的也会飞起来,比如橡皮鸭子。问题的根源是:并不是所有的鸭子都会飞。(对基类添加的方法,所有子类都继承了)

注意:对代码做局部修改,影响可不是局部的。

 再做修改来解决这个问题:如果对不需要fly()的子类中,将该方法覆盖,直接让该方法为空,什么也不做呢?

class RubberDuck:public Duck
{
    void quack(){/*吱吱叫*/};
    void display(){/*显示橡皮鸭*/};
    void fly(){/*什么也不做,直接覆盖为空*/};
};

似乎可以实现,但是后续如果添加其他种类的鸭子呢?添加不会叫但会飞的鸭子,或者不会飞又不会叫等等?着这种情况下总不至于每一种添加的子类鸭子都要单独修改一边吧。

利用接口如何?

如果把不是所有鸭子共性的行为属性单独拿出来作为接口呢?如下:设计接口Flyable和接口Quackable,对于要实现fly和quack的鸭子分别实现这两个接口:

这似乎看上去解决了问题,但如果有48中类型的鸭子,各自有不同的fly方式和quack方式呢?岂不是所有的鸭子都要修改飞行方式和呱呱叫的方式了?由于是接口的实现,所以就算是子类具有相同的飞行方式,也需要在这些子类中都重复实现一遍,因为接口中只有方法的定义,而没有实现,这就导致了代码不能复用和冗余。 

综上,单独的继承或者使用接口都无法很好的实现需求。

二、把问题归零

把变化的部分取出并“封装”起来,好让其他部分不会受到影响。

设计原则:找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。

换句话说,如果每次新的需求以来,都会使得某方面的代码发生变化,那么就可以确定,这部分代码需要被抽出来,和其他稳定的代码有所区分。

换一种思考方式就是“把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充这一部分,而不影响不需要变化的那一部分”。“系统中的某部分变化不会影响其他部分”。

回到鸭子问题,Duck类中,fly()和quack()会随着鸭子的不同而改变。为了把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。

设计鸭子的行为:如何设计那组实现飞行和呱呱叫的行为的类呢?一切应该能有弹性,让鸭子的行为能够动态改变。

设计原则:针对接口编程,而不是针对实现编程。

利用接口代表每个行为,比如FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。

在新的设计中,鸭子的子类使用接口(FlyBehaviorQuackBehavior)所表示的行为,所以实际的“实现”不会绑死在鸭子的子类中。(换句话说,特定的具体行为编写在了实现了FlyBehavior与QuackBehavior的类中)。

这样的做法与以往的差别是:以前行为直接来自于Duck超类的具体实现,或是继承某个接口由鸭子子类自己实现而来。这两种方法的做法都依赖于“实现”,除了写更多的代码外无法随便的更改行为。

下面根据行为接口来实现鸭子的行为:

如上图,行为接口FlyBehaviorQuackBehavior的实现类分别实现了不同的行为。如FlyBehavior接口的三个实现类中:

      FlyWithWinsfly()实现了用翅膀飞;

      FlyNoWayfly()可以直接为空,表示不可飞行;

      otherFlyWayfly()实现了其他的飞行方法。

这样的设计,可以让飞行和瓜瓜叫的动作被其他对象复用,应为这些行为已经和鸭子类无关了。我们还可以新增其他的行为,不会影响既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

整合鸭子的行为

如下图,将FlyBehavior*与QuackBehavior*作为变量加入到Duck类中,同时增加performQuack()和performFly()两个方法用来调用两个行为接口中的方法。

从类图的角度来说,将行为聚合到了Duck类中:

代码实现如下:

class QuackBehavior
{
    public:
    virtual void quack()=0;
};

class FlyBehavior
{
    public:
    virtual void fly()=0;
};

class Duck
{
protected:
    QuackBehavior* quackBehavior;
    FlyBehavior* flyBehavior;
public:
    void performQuack()
    {
        quackBehavior->quack();
    }
    void performFly()
    {
        flyBehavior->fly();
    }

	void swim();
	void display();
};

如果现在要实现MallardDuck这种鸭子,代码该如何实现呢?MallardDuck的飞行方式为FlyWithWins(),叫的方式为Squeak()。 

代码实现如下:

class FlyBehavior
{
    public:
    virtual void fly()=0;
};
class QuackBehavior
{
    public:
    virtual void quack()=0;
};

class Duck2
{
protected:
    QuackBehavior* quackBehavior;
    FlyBehavior* flyBehavior;
public:
    void performQuack()
    {
        quackBehavior->quack();
    }
    void performFly()
    {
        flyBehavior->fly();
    }
    void swim();
    void display();
};

class FlyWitrhWins:public FlyBehavior
{
    virtual void fly() override {/*fly with wins*/};
};
class SQueak:public QuackBehavior
{
    virtual void quack() override {/*Squeak*/};
};

class MallardDuck:public Duck2
{
    //继承父类的各种行为
public:
    MallardDuck()  //在构造函数中实例化行为
    {
        flyBehavior = new FlyWitrhWins();
        quackBehavior = new SQueak();
    }
};

int main()
{
    //1.直接构造MallarDuck对象
    MallardDuck mallarDuck;
    mallarDuck.performQuack();//执行FlyWitrhWins的动作
    mallarDuck.performFly();//执行SQueak的动作
    //2.使用多态实现
    Duck2* mallar_Duck = new MallardDuck();
    mallar_Duck->performFly();
    mallar_Duck->performQuack();
}

 动态设定行为

假设我们想在鸭子的子类中动态的设定行为怎么做呢?上面的子类MallardDuck中,飞行行为在其构造函数中直接new出来的,没法通过“设定方法”的方式来设定。若要通过在子类中通过通过方法设定,可以再鸭子来中再增加两个新方法:

 如上图,在鸭子父类Duck中增加了两个方法:

void setFlyBehavior(FlyBehavior *fb)
{
    flyBehavior = fb;
}
void setQuackBehavior(QuackBehavior *qb)
{
    quackBehavior = qb;
}

此时,我们就可以随时调用这两个方法改变鸭子的行为:

假设有一个新的飞行方法:

class FlyWithOtherWay:public FlyBehavior
{
    virtual void fly(){/*其它飞行方法*/}
};

我们可以随时改变上述子类鸭子的飞行行为:

int main()
{
    //1.直接构造MallarDuck对象
    MallardDuck mallarDuck;
    mallarDuck.performQuack();//执行FlyWitrhWins的动作
    mallarDuck.performFly();//执行SQueak的动作
    //2.使用多态实现
    Duck2* mallar_Duck = new MallardDuck();
    mallar_Duck->performFly();
    mallar_Duck->performQuack();
    //3.随时改变鸭子的行为
    mallar_Duck->setFlyBehavior(new FlyWithOtherWay());
    mallar_Duck->performFly();  //现在mallar_Duck鸭子的飞行行为变成了FlyWithOtherWay()这种方式
}

大局观

再来整体回顾下:其实鸭子类和行为类的关联关系,应该是组合更为合适:

父类与子类的关系:继承,是is-a(是一个)的关系。

鸭子类Duck与行为之间是一种关联关系,这里为组合(认为是聚合也没问题,关联、组合、聚合三种在语法上本来就是一样的,不同的只是关系强弱而已),是一种has-a(有一个)的关系。

行为接口与行为实现类之间是接口实现关系。

“有一个”可能比“是一个”更好

有一个关系关系:每一个鸭子都有一个FlyBehaviorQuackBehavior,好将鸭子的飞行行为和呱呱叫行为委托给他们处理。

本例中,这种关系是聚组合。组合和继承不同的地方在于,鸭子的行为不是继承而来的,而是和适当的对象“组合”而来。

第三个设计原则:多用组合,少用继承

好了,以上为策略模式,希望对大家有帮助。

感谢Header first 设计模式这本书,写的真棒!后面会继续整理。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三零散人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值