一、问题的提出
先从简单的模拟鸭子应用做起:
制作鸭子游戏:游戏中会出现各种鸭子,一边游泳,一边呱呱叫。此系统的内部设计使用了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,而行为的每个实现都将实现其中的一个接口。
在新的设计中,鸭子的子类使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会绑死在鸭子的子类中。(换句话说,特定的具体行为编写在了实现了FlyBehavior与QuackBehavior的类中)。
这样的做法与以往的差别是:以前行为直接来自于Duck超类的具体实现,或是继承某个接口由鸭子子类自己实现而来。这两种方法的做法都依赖于“实现”,除了写更多的代码外无法随便的更改行为。
下面根据行为接口来实现鸭子的行为:

如上图,行为接口FlyBehavior与QuackBehavior的实现类分别实现了不同的行为。如FlyBehavior接口的三个实现类中:
FlyWithWins中fly()实现了用翅膀飞;
FlyNoWay中fly()可以直接为空,表示不可飞行;
otherFlyWay中fly()实现了其他的飞行方法。
这样的设计,可以让飞行和瓜瓜叫的动作被其他对象复用,应为这些行为已经和鸭子类无关了。我们还可以新增其他的行为,不会影响既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
整合鸭子的行为
如下图,将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(有一个)的关系。
行为接口与行为实现类之间是接口实现关系。
“有一个”可能比“是一个”更好
有一个关系关系:每一个鸭子都有一个FlyBehavior和QuackBehavior,好将鸭子的飞行行为和呱呱叫行为委托给他们处理。
本例中,这种关系是聚组合。组合和继承不同的地方在于,鸭子的行为不是继承而来的,而是和适当的对象“组合”而来。
第三个设计原则:多用组合,少用继承
好了,以上为策略模式,希望对大家有帮助。
感谢Header first 设计模式这本书,写的真棒!后面会继续整理。。。
--策略模式&spm=1001.2101.3001.5002&articleId=145147344&d=1&t=3&u=903bd663b2094b138766456fef58bad0)
1306

被折叠的 条评论
为什么被折叠?



