设计模式1-策略模式

05/06/2020
05/08/2020 :增加总结

父子继承关系1

  • 假设父类所有的鸭子特点
    • 不抽象:相同的行为
      • 所有鸭子都会叫,叫的声音都一样 (quack)
      • 所有鸭子都会游泳,游泳姿势都一样 (swim)
    • 抽象:没有相同的行为(virtual)
      • 所有鸭子都有外观这个特点,但每一个鸭子外观不同(diaplay)
  • 子类需要重写外观这个特点
//Duck 基类
class Duck
{
public:
	void quack(); //声音
	void swim();
	virtual void display();
};

//子类鸭子类
class GreenHeadDuck:public Duck
{
public:
	void display()override(); //显示头的颜色是绿色的
};

class RedHeadDuck:public Duck
{
public:
	void display()override(); //红色的头
};

//...其他头部颜色的鸭子

简单完成,如何扩展

如何扩展飞行行为。思考:

  • 每一只鸭子都会飞吗?
  • 如果都会飞,它们飞行的姿态都一样吗?

问题一:飞行作为父类的抽象类

//Duck 基类
class Duck
{
public:
	//... 之前所有的其他行为不变
	void fly(); // 新增飞行类
};

缺点

  • 如果父类加入fly,所有子类都会飞行,但是无生命的鸭子并不会飞行。对代码所做的局部修改,影响层面可不只是局部的。-- 开放-封闭原则。
  • 对于一个设计好的父子结构,在父类增加新的行为,一定要小心,最好禁止这个行为。

问题二:使用抽象的飞行(virtual)

//父类
class Duck
{
public:
	//... 之前所有的其他行为不变
	virtual void fly(); // 新增飞行类
};
//子类鸭子类
class GreenHeadDuck:public Duck
{
public:
	void display()override(); //显示头的颜色是绿色的
};

class RedHeadDuck:public Duck
{
public:
	void display()override(); //红色的头
};

//重点:抽象的飞行--覆盖原有父类会有什么问题
class GreenHeadToyDuck:public Duck //玩具鸭
{
public:
	void display()override(); //红色的头
	void fly()overdie{
		//空的行为,什么都不做,作为不会飞的鸭子
	}
};
  • 覆盖掉父类的方法将会导致程序员无法记住所有的实现,有些需要写空,有些需要实现,总归会有遗漏的。
  • 突然发现quack行为也需要改变,因为有些鸭子也不会叫。

缺点:

利用继承来提供鸭子的行为,会导致什么缺点:

  • 很难知道所有鸭子的全部行为
  • 改变会牵一发动全身,造成其他鸭子不想要的改变
  • 代码在多个子类中重复
  • 运行时的行为不容易改变

继承复用:

只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。2

里氏代换原则:
子类型必须能够替换掉它们的父类型。

例子1:
现实生活中,企鹅是一个特殊的掉,尽管不会飞。但是在面向对象设计时候,一个是鸟类,一个企鹅类,如果鸟是可以飞的,企鹅不会飞,企鹅可以继承鸟这个类吗? – 不能。有时候脱离现实,也源于现实。

问题三:接口使用

  • C++使用抽象类,里面有纯虚函数。抽象类无法生成对象,只能被继承。
  • 继承过来的纯虚函数必须被定义,否则继续声明为纯虚函数。
//Duck 基类
class Duck
{
public:
	//void quack(); //叫声不一样,分离它
	void swim();
	virtual void display();
};
//抽象类,行为用来被继承
class Flyable
{
public:
	virtual void fly() = 0;
};
//突然发现,鸭子的叫声也是不一样,不是所有鸭子都会叫
class Quackable
{
public:
	virtual void quack() = 0;
};

//多继承完成接口使用
class RedHeadDuck:public Duck,public Flyable,public Quackable
{
public:
	//...
	void fly()override{/*完成细节*/} //继承过来的接口,必须定义!
	void Quack()override{/*完成细节*/}
};

//不需要继承接口的玩具鸭
class GreenHeadToyDuck:public Duck
{
public: 
	void display()override{/*完成细节*/}
};

缺点:

虽然完成了扩展,但是子类依然需要去实现它们的细节,但是一旦有很多鸭子子类,需要覆盖的工程量很大。同时代码无法复用。(即重复使用)

设计原则1

  • 找出应用中可能需要的变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  • 如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。就如上述的quack的行为一样。
  • 系统中的某部分改变不会影响其他部分。

分开变化和不会变化部分

变化:
- fly和quack 随着鸭子的不同而改变
- 对变化的行为设立新类

设计原则2

针对接口(即抽象类)编程,而不是针对实现编程。

  • 行为类,区别于鸭子类,鸭子不需要去实现fly和quack的接口
    例子:飞行行为
//fly 行为接口
class FlyBehavior
{
public:
	virtual void fly() = 0;
};

//继承飞行接口
class FlyWithWings:public FlyBehavior
{
public:
	void fly()override{} //实现鸭子飞行行为
};

class FlyNoWay:public FlyBehavior
{
public:
	void fly()override{} //不会飞
};

优点:

  • 针对接口编程:关键就在多态,利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。
    例子1:针对实现编程
GreenHeadDuck* gD = new GreenHeadDuck;
gD->display();

例子2:针对接口/超类型编程:不需要知道子类的类型,关心如何正确进行display的动作就够了。

Duck* d = new GreenHeadDuck; //多态的体现
d->display();
  • 完成复用,即其他类也能使用fly和quack的行为类。与鸭子类无关了。完成里氏代换原则。

如何整合鸭子的行为

  • 继承还是拥有
    • 加入两个实例变量,flyBehavior 和 quackBehavior
class FlyBehavior;
class QuackBehavior;
class Duck
{
public:
	void swim();
	virtual void display();

	//取代原有的fly和quack
	void performFly() 
	{
		mFly->fly();
	}
	void performQuack()
	{
		mQuack->quack();
	}
protected:
	//多态体现
	FlyBehavior* mFly; 
	QuackBehavior* mQuack;
	
};
  • 并不在乎quackBehavior接口对象到底是什么,我们只关心该对象如何进行fly和quack。

子类的使用

class GreenHeadDuck:public Duck
{
public:
	GreenHeadDuck()
	{
		mFly = new FlyWithWings; //初始化你想要的具体类型
		mQuack = new Quack;
	}
};

缺点

  • 我们不能对具体实现编程,在构造器中我们实现了具体的类。
  • 初始化实例变量的做法还不够弹性。

动态设定行为

如何实现在运行时,通过多态的魔力动态地给它指定不同QuackBehavior实现类。

  • 使用set方法,动态设置
```cpp
class FlyBehavior;
class QuackBehavior;
class Duck
{
public:
	//...
	//建议使用智能指针,不然需要delete
	void setFlyBehavior(FlyBehavior* fb)
	{
		mFly = fb;
	}
	void setQuackBehavior(QuackBehavior* q)
	{
		mQuack = q;
	}
protected:
	//多态体现
	FlyBehavior* mFly; 
	QuackBehavior* mQuack;
	
};
//main
Duck* d= new GreenHeadDuck;
d->performFly();
d->setFlyBehavior(new FlyNoWay);
d->performFly();

总结

合成(组合)关系(Composition)3

鸭子拥有飞行或者呱呱叫这个行为,说明了一种合成关系。是一种拥有的关系。体现了严格的部分和整体关系,部分和整体的生命周期一样。当我们初始化鸭子的时候,同时也初始化了飞行和叫声。这体现了相同的生命周期。

其他例子

鸟和翅膀的关系,武器和子弹的关系

聚合关系

聚合表示一种的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。

例子

  • 大雁和大燕群的关系,使用到了一组容器。
  • 商场促销策略4

策略模式(Strategy)4

策略模式是一种定义一系列算法,在这里鸭子飞行类就定义了一套算法,它们都是为了完成飞行这个相同的任务,只是实现不同,但可以以相同的方式调用所有算法,减少了鸭子和飞行之间的耦合。

优点

  • 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试

应用

不同时间应用不同的业务规则,可以考虑策略模式处理变化的可能性。

如何减少对if语句的依赖

当不同的行为堆砌在一个类中,就很难避免使用条件语句选择合适的行为。将这些行为封装在一个个独立的strategy类中,可以在使用这些行为的类中消除条件语句。所以策略封装了变化

问题

但是客户端对象面临了选择的压力。


  1. HeadFirst Design Patterns ↩︎

  2. 大话设计模式 第五章 5.3 ↩︎

  3. 大话设计模式 第一章 1.11 ↩︎

  4. 大话设计模式 第二章 2.7 ↩︎ ↩︎

猜你喜欢

转载自blog.csdn.net/weixin_44200074/article/details/105946084