模拟鸭子应用
需求
- 游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。
一开始,是通过使用继承的方式,用派生类实现各种鸭子。
class Duck {
public:
void quack();
void swim();
void display();
};
class MallardDuck : Duck {
public:
void display() {
// 外观是绿头
}
};
class RedheadDuck {
void display() {
// 外观是红头
}
};
以上缺点:
- 代码在多个子类中重复;
- 运行时的行为不容易改变;
- 我们不能让鸭子跳舞;
- 很难知道所有鸭子的全部行为;
- 鸭子不能同是又飞又叫;
- 改变会牵一发动全身,造成其他鸭子不想要的改变。
由于需求总在变化,现在我们需要让鸭子能飞起来。不同的鸭子,叫声、显示、飞行方式都可能不太一样。
面对这种情况,我们需要的是将不变的部分放在基类中,而将经常变化的部分分离出来,作为独立的部分进行封装。为此,我们做了以下工作:
- 利用抽象类(接口)代表每个行为,比如说FlyBehavior和QuackBehavior,而行为的每个实现都将实现其中的一个抽象类。
- 鸭子的子类将使用抽象类(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。
- 此次使用的是组合方式,而不是继承的方式进行实现。
于是代码变成了如下:
// 定义飞行行为抽象类(接口),
// 将鸭子类中经常变化的部分进行分离与封装。
class FlyBehavior {
public:
virtual void fly() = 0;
// 该类为基类,是需要被继承的。
// 因此,析构函数需要被写成虚函数形式,
// 这样子资源才得以正确释放。
virtual ~FlyBehavior() {};
};
// 使用飞行行为抽象类(接口),
// 实现用翅膀飞行的具体类,
// 为下面继承鸭子基类的派生类复用和灵活设定使用类而使用。
class FlyWithWings : public FlyBehavior {
public:
virtual void fly() {
cout << "Fly with wings." << endl;
}
};
// 使用飞行行为抽象类(接口),
// 实现没有飞行的具体类,
// 为下面继承鸭子基类的派生类复用和灵活设定使用类而使用。
class FlyNoWay : public FlyBehavior {
public:
virtual void fly() {
cout << "FlyNoWay: 啥时都不干。" << endl;
}
};
// 定义鸭叫行为抽象类(接口),
// 将鸭子类中经常变化的部分进行分离与封装。
class QuackBehavior {
public:
virtual void quack() = 0;
// 该类为基类,是需要被继承的。
// 因此,析构函数需要被写成虚函数形式,
// 这样子资源才得以正确释放。
virtual ~QuackBehavior() {};
};
// 使用鸭叫行为抽象类(接口),
// 实现鸭的呱呱叫具体类,
// 为下面继承鸭子基类的派生类复用和灵活设定使用类而使用。
class Quack : public QuackBehavior {
public:
virtual void quack() {
cout << "Quack quack..." << endl;
}
};
// 使用鸭叫行为抽象类(接口),
// 实现鸭的唧唧叫具体类,
// 为下面继承鸭子基类的派生类复用和灵活设定使用类而使用。
class Squeak : public QuackBehavior {
public:
virtual void quack() {
cout << "Squeak squeak..." << endl;
}
};
// 使用鸭叫行为抽象类(接口),
// 实现鸭的哑叫(不叫)具体类,
// 为下面继承鸭子基类的派生类复用和灵活设定使用类而使用。
class MuteQuack : public QuackBehavior {
public:
virtual void quack() {
cout << "mute quack..." << endl;
}
};
// 鸭子作为具体基类,为后面各种鸭子的派生类做基础。
// 鸭子之所以作为基类而不是抽象类(接口),
// 是因为鸭子本身有具体的部分。
// 将鸭子的叫、飞行等方式通过组合的形式,而不是继承的方式实现,
// 可以获得在运行中动态改变,也实现了代码的最大化复用。
// 也不会出现引起继承所造成的牵一发动全身的缺点。
// 将不变的部分封装在基类即可。
class Duck {
public:
Duck() {
quackBehavior = nullptr;
flyBehavior = nullptr;
}
// 该类为基类,是需要被继承的。
// 因此,析构函数需要被写成虚函数形式,
// 这样子资源才得以正确释放。
virtual ~Duck() {};
// 将鸭叫行为委托给行为类实现,
// 实现类中经常变化的部分分离,得以封装。
// 也实现了将变化的代码最大化的复用与灵活性。
void performQuack() {
quackBehavior->quack();
};
// 将飞行行为委托给行为类实现,
// 实现类中经常变化的部分分离,得以封装。
// 也实现了将变化的代码最大化的复用与灵活性。
void performFly() {
flyBehavior->fly();
};
virtual void display() {
cout << "Duck display" << endl;
}
// 获得该类的鸭叫行为类
const QuackBehavior const* getQuackBehavior() {
return quackBehavior;
}
// 获得该类的飞行行为类
const FlyBehavior const* getFlyBehavior() {
return flyBehavior;
}
// 设置该类的鸭叫行为类
void setQuackBehavior(QuackBehavior* quack) {
delete quackBehavior;
quackBehavior = quack;
}
// 设置该类的飞行行为类
void setFlyBehavior(FlyBehavior* fly) {
delete flyBehavior;
flyBehavior = fly;
}
// 类成员使用保护方式,将实现派生类可以访问该类成员,
// 实现该类在具体运行过程中可以动态改变行为。
protected:
QuackBehavior* quackBehavior;
FlyBehavior* flyBehavior;
};
class MallardDuck : public Duck {
public:
// 初始化派生类的行为类
MallardDuck() {
quackBehavior = new Quack;
flyBehavior = new FlyWithWings;
}
// 释放派生类所拥有的资源
virtual ~MallardDuck() {
delete quackBehavior;
delete flyBehavior;
}
// 实现派生类的显示
virtual void display() {
cout << "MallardDuck display..." << endl;
}
};
class RedHeadDuck : public Duck {
public:
// 初始化派生类的行为类
RedHeadDuck() {
quackBehavior = new Squeak;
flyBehavior = new FlyNoWay;
}
// 释放派生类所拥有的资源
virtual ~RedHeadDuck() {
delete quackBehavior;
delete flyBehavior;
}
// 实现派生类的显示
virtual void display() {
cout << "RedHeadDuck display..." << endl;
}
};
网友评论