现有一套模拟鸭子游戏,可以一边游泳,一边呱呱叫。
每种鸭子都会呱呱叫和游泳,只是外观不同。因此,quack和swim放在父类中,display放在子类中实现。
增加新的功能:鸭子飞翔。
1 我们可能想到直接在父类中增加fly的方法。
但如果出现一个橡皮鸭,我们只能在子类中覆盖fly方法,FlyNoWay,同时,也需要覆盖quack方法,吱吱叫。如果还有其它的鸭子子类,如诱饵鸭等等,那么就需要在各自的子类中进行修改,检查是否需要修改quack和fly方法。这(继承)导致的问题会有:
- 代码在多个子类中重复
- 运行时的行为不容易改变
- 很难指导所有鸭子的全部行为
- 改变会牵一发动全身,造成其它鸭子不想要的改变
2 如果将fly和quack从超类中独立出来,设计为两个新的接口呢?
这个改变好像会导致重复的代码更多,更加愚蠢?
---------------------------------------------------------------------------------------------------------------------------------
这本书是用的Java,如果这里是C++,是不是会想到flyable和quackable是接口,然后再有类继承接口,最后Duck子类实现多类继承?多类继承我还没有具体深入研究过,只是感觉可能会提高复杂度。
或者直接是继承多个超类,需要覆盖的在子类中覆盖?
---------------------------------------------------------------------------------------------------------------------------------
这里引出了一个设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。(会变化的部分封装起来,以后就可以轻易改动或扩充此部分,而不影响其它不需要变化的部分)
第二个设计原则:针对接口编程,而不是针对实现编程。
鸭子的行为将被放在分开的类中,此类专门提供某行为接口的实现。这样,鸭子类就不再需要知道行为的具体实现细节。
鸭子类不负责实现Flyable和Quackable接口,而是新建一组类去专门实现这两个接口,这些类就叫做行为类。以往,行为来自Duck超类的具体实现,或者继承某个接口并由子类自行实现而来。
这样设计,可以让飞行和呱呱叫的动作被其它对象复用,因为这些动作已经与鸭子类无关了。而且我们可以新增一些行为,不会影响到既有行为类,也不会影响到使用到飞行行为的鸭子类。
复用的具体操作为(组合),在Duck类中加入两个实例变量,分别为flyBehavior, quackBehavior,每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型。
class Duck
{
private:
QuackBehavior quackBehavior;
public:
void performQuack()
{
quackBehavior.quack();
}
}
具体的子类中可以在构造函数中对quackBehavior进行初始化。
class MallardDuck: public Duck
{
public:
MallarDuck()
{
quackBehavior = new Quack();
}
}
上面的代码在构建一个具体的Quack实现类的实例,但我们通过新家setter方法仍能够在运行时动态地给它指定不同的QuackBehavior实现类。
class QuackBehavior
{
public:
void quack() = 0;
}
class Quack: public QuackBehavior
{
public:
void quack()
{
std::cout << "Quack" << std::endl;
}
}
class Duck
{
private:
QuackBehavior quackBehavior;
public:
void performQuack()
{
quackBehavior.quack();
}
void setQuackBehavior(QuackBehavior qb)
{
quackBehavior = qb;
}
}
设计原则:多用组合,少用继承。
策略模式:定义了算法簇,分别封装起来,让他们可以互相替换,此模式让算法的变化独立于使用算法的客户。