策略模式
策略模式
"Head + First"书中为策略模式设计的实际场景是为鸭子类添加功能。
场景分析
class Duck{
public:
void quack(void){
// 鸭子叫
}
virtual void display(void);
void swim(void){
// 鸭子游泳
}
}
class MallardDuck : public Duck{
public:
virtual void display(void){
// 外观是绿头
}
}
class RedHeadDuck : public Duck{
public:
virtual void display(void){
// 外观是红头
}
}
class RubberDuck : public Duck{
public:
virtual void display(void){
// 外观是橡皮鸭
}
void quack(void){
// 覆盖为吱吱叫
}
}
一个很直接的鸭子类,并且除了真实的鸭子子类,还存在橡皮鸭这种有点小区别的子类。新的一个需求是要求鸭子能飞。
直接在Duck
类中加上fly()
方法,会导致所有继承了Duck
的子类都会飞,那么橡皮鸭在空中飞的场景将会发生。为了避免这种情况,需要在RubberDuck
子类中将fly()
方法重写。当前情况下这么修改是可以接受的。
不同的子类对功能的需求是不同的,假如简单的在父类中添加功能,在子类中通过重写来将功能覆盖,从开发角度来看简直是灾难。
另一种方法是利用接口,将不同的功能设计为不同的接口,具有功能的子类手动调用该接口实现功能。但这太不抽象了,完全没有面向对象之魂。而且时间久了,接口多了,开发新的子类将是无比麻烦的,最主要的是重复代码将会非常多。
设计原则
封装变化原则
假如把所有功能全部放在父类中,那么子类必须频繁的重写函数来保证某些功能能符合自身需求。为了避免这种情况,提出了一种封装变化
的设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
于是我们把不变的功能放在父类中实现,变化的功能放在需要他们的子类中实现,那些不需要这些功能的子类同样也不需要为这种行为付出代码。
针对接口编程原则
现在和场景分析的第二种情况一样,一些不同的子类可能那些变化的功能也是相同的,所以还是有很大的代码重复率。
为了避免这种情况,提出了一种针对接口编程原则
:针对接口编程,而不是针对实现编程。
用一个形象的例子表示。
class Animal {
public:
virtual void makeSound();
}
class Dog : public Animal {
public:
void bark(){
//狗叫声
}
virtual void makrSound(){
bark();
}
}
//针对实现编程
Dog d = new Dog();
d.dark();
//针对接口编程
Animal animal = new Dog();
animal.makeSound();
//更进一步的针对接口编程
Animal a = getAnimal(); //在使用时动态获取动物类型
a.makeSound();
可以很明显的看出,针对接口的编程更多得保障代码的灵活性,提高代码的复用性,减少修改代码的频率。
以鸭子为例,将fly()
行为单独设计一个接口类FlyBehavior()
,并且实现了不同的子类:
FlyBehavior | fly() | 并不做具体实现 |
---|---|---|
FlyWithWings | fly() | 用翅膀飞 |
FlyNoWay | fly() | 不飞 |
这样,飞这个行为和鸭子这个生物已经无关了,其他动物也可以使用这个接口来实现飞的行为。
新的鸭子类
由于定义了FlyBehavior()
接口,所以鸭子类也需要进行针对性的变化,同时还要兼顾到针对接口编程的原则。
class FlyBehavior{
public:
virtual void fly(void);
}
class FlyWithWings : public FlyBehavior{
public:
virtual void fly(void){
// 用翅膀飞
}
}
class FlyNoWay : public FlyBehavior{
public:
virtual void fly(void){
// 不飞
}
}
class Duck{
FlyBehavior *pFlyBehavior;
public:
void fly(){
pFlyBehavior->fly();
}
~Duck(){
delete pFlyBehavior;
}
// ...
}
class MallarDuck : public Duck{
public:
MallarDuck(){
pFlyBehavior = new FlyWithWings;
}
// ...
}
class RedHeadDuck : public Duck{
public:
RedHeadDuck(){
pFlyBehavior = new FlyWithWings;
}
// ...
}
class RubberDuck : public Duck{
public:
RubberDuck(){
pFlyBehavior = new FlyNoWay;
}
// ...
}
在构造函数中,不同的子类获得了对应的FlyBehavior
,实现了不同的飞行行为。与此类似的,可以将quack()
方法也提取出来作为接口,使得鸭子的叫声也可以很好的进行拓展。
同样的,也可以更进一步通过传入FlyBehavior
类来进行构造,但需要注意的是作用域,防止在跨函数传递时,实例被析构。
组合继承原则
总览整个过程,可以总结出一些很经验性的原则:多用组合,少用继承。
继承会导致对类的拓展和修改会牵扯很多东西,相反,利用组合来将大部分方法同类本身分离,来保证在编程甚至在运行时存在很大的弹性空间。
策略模式
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
策略模式是很基础的一种设计模式,在面向对象初期便应该考虑到这种模式的应用。
策略模式的根本思想是将静态和动态的部分分离,将可变的部分总结为接口,以一种不变的形式插回原来的类中,保证从类的角度来看是不变的,但实际的实现会根据初始化甚至传入参数的不同来保证区别。
区别于简单的将相同的部分提取出来,策略模式的关键在于将相同的功能提取出来并抽象化,实际使用时通过使用者按需申请来保证可以动态获取。保证整体静态的同时,也满足了具体实现的动态性。
相关文章
- 23种设计模式之策略模式
- Oracle 创建用户及模式
- 策略模式及Android源码中的应用
- java策略模式实战示例「建议收藏」
- JavaScript设计模式—-策略模式[通俗易懂]
- 十五、状态模式
- IDEA使用Debug模式启动项目一直转圈圈跑起不
- 【说站】Python高级架构模式的整理
- 1、LVS 有哪些负载均衡,和Nginx有什么区别?+2、DR模式、NAT模式和TUN模式的区别?
- ie11兼容性视图设置怎么能自动兼容_ie11兼容模式ie8
- 使用工厂模式+策略模式来优化代码结构
- 真香系列:IPSec策略模板模式、模板模式、策略模式的区别
- 算法策略的主动选择,拒绝if...else...(策略模式+简单工厂模式)
- 策略模式(Strategy)
- 【设计模式】责任链模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )
- 策略模式详解编程语言
- 模式实现Redis单例模式的优化策略(redis单例)
- 设计优化Oracle数据库模式设计的策略(oracle数据库模式)
- Linux:如何进入单用户模式(linux进单用户模式)
- 蚂蚁金服大规模分布式事务实践及四种分布式事务模式
- Oracle 数据库归档模式开启: 如何优化你的数据备份策略(oracle归档模式开启)
- 基于Android设计模式之--SDK源码之策略模式的详解
- 浅析.net策略模式