一、案例背景
J o e 上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子父类,并让各种鸭子继承此父类。
现在,需求发生了变化,需要添加会飞并且会叫的鸭子,怎么做合适?
二、案例分析
自然而然地,你可能会脱口而出:“这还不简单?找出父类,添加一个fly方法一个quack方法,解决!“。
嗯,看起来很完美,可以试一试。
不过很快你就发现了似乎不对劲:游戏里的“橡皮鸭”居然也会飞了!这太荒谬了!
你挠了挠头,马上又想起来补救之策:“可以把橡皮鸭中的fly方法给覆盖掉,让它什么也不做,这样它就不会飞了。”。
可是,如果以后我加入木头鸭呢?木头鸭既不会飞也不会叫,橡皮鸭不会飞,但会叫。
我们知道,并非所有的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。
刚开始出现继承这种机制的时候,很多程序员便很喜欢用这种方法解决问题。因为这很符合我们以往的认知习惯:某某人的儿子可以使用父亲手中的资源,这种使用似乎“理所应当”,很顺其自然的一件事。但是,写代码的时候你就会发现继承能做的事情,往往组合也能实现,并且有时候会比用继承更好。
直接继承如果不行的话,那如果把fly方法和quack方法定义成纯虚函数呢?这样一来,他们的子类就可以根据自身需要自己去实现fly方法和quack方法了。
软件开发中有个目标叫复用。如果你的代码里出现很多一模一样的函数,显然并不是一份很好的设计。软件的需求一直在变化,正如世界一样,我们的代码也需要与时俱进,否则就会死亡。一份程序在刚写好的时候仅仅是开始,后期花在维护,改进,更新上的时间要远远多于刚开始开发的时间,这些就是所谓的“change”,正是这些变化才造成了你不断的加班。对于软件开发来讲,唯一不变的是变化。
那么应该采取什么样的设计才能有效的抵御变化,少加班呢?
找出应用中可能需要变化之处,把他们独立出来,不要和稳定的代码混在一起。
换句话说,如果每次新的需求依赖,都会使某些方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽离,和其他稳定的代码有所区分。也就是说把会变化的部分取出并封装起来,以便以后可以轻易地改动以扩充此部分而不影响其它部分。
这个概念很简单,几乎是每个设计模式背后的精神所在。所有的设计模式都提供了一套方法让“系统中的某部分改变不会影响其它部分”。
具体问题,具体分析。对于上述案例中每次变化的是一个有一个“行为”,比如飞行,比如呱呱叫,并且后期可能还会有其他的行为被添加进来,如何将它们隔离出来呢?
针对接口编程,而不是针对实现编程。
如何理解这句话?
还记得引言中的那个案例吗?这里的接口并不是指某个具体的函数,而是指抽象父类。子类通过override父类中的虚函数实现具体的行为,而利用父类指针又可以指向子类的实例,这使得我们可以在设计类的时候引入一个父类的指针,而在使用的时候可以插入具体的子类实例,从而实现更好的设计。
我们知道Duck类内的fly方法和quack方法会随着鸭子的不同而改变。为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。
三、代码实现
这里给出相关案例的C++实现:
#include <iostream>
using namespace std;
//@brief: 飞行行为(抽象接口)
class FlyBehavior
{
public:
virtual void fly() = 0;
};
//@brief: 飞行行为:翅膀飞行(具体实现)
class FlyWithWings : public FlyBehavior
{
public:
void fly() override
{
cout << "Fly with Wings..." << endl;
}
};
//@brief: 飞行行为:不能飞(具体实现)
class FlyNoway : public FlyBehavior
{
public:
void fly()
{
cout << "i can not fly..." << endl;
}
};
//@brief: 飞行行为:火箭飞行(具体实现)
class FlyWithRockets : public FlyBehavior
{
public:
void fly()
{
cout << "Fly with Rockets..." << endl;
}
};
//@brief: 鸣叫行为(抽象接口)
class QuackBehavior
{
public:
virtual void quack() = 0;
};
//@brief: 鸣叫行为:呱呱叫(具体实现)
class Quack : public QuackBehavior
{
public:
void quack() override
{
cout << "quack..." << endl;
}
};
//@brief: 鸣叫行为:不会叫(具体实现)
class MuteQuack : public QuackBehavior
{
public:
void quack() override
{
cout << "silence..." << endl;
}
};
//@brief: 鸣叫行为:嘎嘎叫(具体实现)
class Squeak : public QuackBehavior
{
public:
void quack() override
{
cout << "Squeak..." << endl;
}
};
//@brief: 鸭子(抽象接口)
class Duck
{
private:
FlyBehavior* flyBehavior;
QuackBehavior* quackBehavior;
public:
Duck(FlyBehavior* fb, QuackBehavior* qb) : flyBehavior(fb), quackBehavior(qb) {}
// 动态的设置各种行为,以防后期出现各种“变化”
void setFlyBehavior(FlyBehavior* flyBehavior){
this->flyBehavior = flyBehavior;
}
void setQuackBehavior(QuackBehavior* quackBehavior){
this->quackBehavior = quackBehavior;
}
// 共有方法
void swim()
{
cout << "Swimming..." << endl;
}
void fly(){
flyBehavior->fly();
}
void quack(){
quackBehavior->quack();
}
};
//@brief: 绿头鸭(具体实现)
class MallardDuck : public Duck
{
public:
MallardDuck(FlyWithWings* fw, Quack* q) : Duck(fw, q) {}
};
//@brief: 红头鸭(具体实现)
class RedheadDuck : public Duck
{
public:
RedheadDuck(FlyWithWings* fw, Quack* q) : Duck(fw, q) {}
};
//@brief: 橡皮鸭(具体实现)
class RubberDuck : public Duck
{
public:
RubberDuck(FlyNoway* fn, MuteQuack* q) : Duck(fn, q) {}
};
//测试方法
int main()
{
// 创建并初始化鸭子
Duck* mallard = new MallardDuck(new FlyWithWings(), new Quack());
Duck* redhead = new RedheadDuck(new FlyWithWings(), new Quack());
Duck* rubberDuck = new RubberDuck(new FlyNoway(), new MuteQuack());
// 让��子表演
mallard->quack();
mallard->fly();
mallard->swim();
redhead->quack();
redhead->fly();
redhead->swim();
rubberDuck->quack();
rubberDuck->fly();
rubberDuck->swim();
// 释放资源
delete mallard;
delete redhead;
delete rubberDuck;
return 0;
}