设计模式学习笔记①-策略模式

今天我看《Head First 设计模式》学到了第一个设计模式-策略模式,写篇博客来总结一下:

策略模式的定义和使用场景


定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。

分析下定义,策略模式定义和封装了一系列的算法,它们是可以相互替换的,也就是说它们具有共性,而它们的共性就体现在策略接口的行为上,另外为了达到最后一句话的目的,也就是说让算法独立于使用它的客户而独立变化,我们需要让客户端依赖于策略接口。

策略模式的使用场景:

1.针对同一类型问题的多种处理方式,仅仅是具体行为有差别时; 
2.需要安全地封装多种同一类型的操作时; 
3.出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体子类时。
 

首先我们写一个鸭子类

class Duck{
public:
     void fly();
     void Quack();
     virtual void Display();
};

现在我们又写了有几个鸭子子类MallardDuck、RedHeadDuck继承这个Duck基类。

class MallardDuck{
public:
     void fly();
     void Quack();
     void Display() override;
};
class RedHeadDuck{
public:
     void fly();
     void Quack();
     void Display() override;
};

现在假设我们有一个新的橡皮鸭的新类的话,那么我们直接继承那个基类Duck的话,将会出现一个问题:我们会将基类的fly方法也直接继承过来,而橡皮鸭实际上来讲是不会飞的。那么我们如何处理这个问题呢? 可能我们会想说通过将fly()方法设计为虚方法,带有一个默认实现。然后子类继承的时候可以根据需要去重写这个fly()方法。

class Duck{
public:
    virtual void fly();//默认实现为空
    virtual void Quack();
    virtual void Display();
};
class MallardDuck{
public:
     void fly() override;
     void Quack() override;
     void Display() override;
};
class RedHeadDuck{
public:
     void fly() override;
     void Quack() override;
     void Display() override;
};

         那么这个处理方法有哪些局限性呢?

     1.那个fly方法会在多个子类里重复。(由于每个子类都重写fly的缘故)

     2.很难知道所有鸭子的所有行为。(比如我们知道MallardDuck、RedHeadDuck这两个鸭子都是可以叫可以飞的。而橡皮鸭只能叫无法飞,我们总是无法提前知道新的子类可能有何种行为)

     3.运行时的行为不易改变。(比如我们现在的鸭子是可飞可叫的,那么我在运行过程中想使得这个鸭子只会飞不会叫,使用继承这种做法的话,我们必须重新修改代码,缺少灵活性。)

     4.改变会牵一发动全身,造成其他鸭子不想要的改变。(这点我不确定是否理解正确,应该是指如果fly方法不是虚方法时,子类通过继承直接继承那个fly()方法,每个子类都有一个相同的fly方法。那么如果我们改动了Duck类的fly方法时,所有的子类都会会带有这个改变。对于那些不需要这些改变的子类,这就显得很多余了。)

所以这个处理方法也导致了我们每当创建一个Duck子类时都要思考它是否需要重写fly和Quack行为,给我们带来了困扰。

那么我们要怎么去改进呢?

这里书中提到了第一个设计原则:找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。

下面是这个原则的另一种思考方式:“把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分”。这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。

下面我们按书中所讲的那样来把鸭子的行为从Duck类中取出试试:

这里我又学到了一个新的设计原则:针对接口编程,而不是实现编程。通过将飞行和叫这两个行为定义为一个接口,然后根据不同需求定义一些不同实现类。

class FlyBehavior
{
public:
	virtual void fly() = 0;
};
class QuackBehavior
{
public:
	virtual void Quack() = 0;
};
class FlywithWing :public FlyBehavior
{
public:
	void fly() override {
	    //用翅膀飞
	}
};
class FlyWithNoWay : public FlyBehavior
{
public:
	void fly() override {
	//不能飞
	}
};
class FlyWithRocket :public FlyBehavior
{
public:
	void fly() override {
		//借用火箭飞
	}
};
class MuteQuack :public QuackBehavior
{
public:
	void Quack() override {
		// 大声叫
	}
};
class Squeak :public QuackBehavior
{
public:
	void Quack() override {
	    //小声叫
	}
};

这样的设计,可以让飞行和呱呱叫的动作被其他对象复用,因为这些行为已经与鸭子类无关了。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。这样一来有了继承的复用好处,却没有继承所带来的包袱。

这里粘贴一些书上的思考题:

    1.我们是不是一定要先把系统做出来,再思考有哪些地方需要改变,然后再回过头将这些变化进行分离&封装。

答:不尽然,通常在你设计系统时,预先考虑到有哪些地方未来可能需要发生改变。于是提前在代码加入这些弹性。你会发现,原则与模式可以应用在软件开发的任何阶段。

   2.Duck是否也需要设计为一个接口?

答:在这个问题中,这么做并不恰当。如你所见的,我们已经让一切都准备妥当,而且让Duck成为一个具现类,这样可以让衍生的特定类(例如绿头鸭)具有Duck共同的属性和方法。我们已经从Duck的继承体系中剔除了变化的部分,原先的问题我们都已经解决了,所以不需要把Duck设计为接口。

  3.用一个类代表一个行为,感觉似乎有点奇怪,类不是应该代表某种“东西”么?类不是应该同时具备状态“与”的行为么?

答:在OO系统中,是的。类代表的东西一般都是既有状态(实例变量)又有方法。只有在本例中,碰巧“东西”是个行为。但是即使是行为,也仍然可以有状态和方法,例如,飞行的行为可以具有实例变量,记录飞行行为的属性(每秒磁棒拍动几下,最大高度和速度等)

个人理解:飞行行为是一个接口,根据这个接口实现的具现类。可以有自己的成员变量去存储一些记录飞行行为的属性(每秒磁棒拍动几下,最大高度和速度等),这是每个具现类都可以不一样的部分。然后有一个fly方法提供外部使用。

关键在于,鸭子现在会将飞行和呱呱叫的动作“委托(delegate)”别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。

class duck {//鸭子类可以是抽象类 也可以是普通类
public:
	void swim() {}//所有鸭子都漂浮着游泳
	void performQuack() { quackBehavior->Quack(); }
	void performFly() { flyBehavior->fly(); }
	virtual void display() {}
	void setFlyBehavior(FlyBehavior* fb) {
		flyBehavior = fb;//这样使得鸭子可以在代码运行过程中,改变飞的方式
	}
	void setQuackBehavior(QuackBehavior* qb) {//这样使得鸭子可以在代码运行过程中,改变叫的方式
		quackBehavior = qb;
	}
  protected://分开鸭子会变化的部分 和不发生变化的部分
	 //比如鸭子的飞行方式和叫声可以每种鸭子不一样 
	//面向对象编程准则:面向接口编程而不是面向实现编程
	FlyBehavior* flyBehavior;
	QuackBehavior* quackBehavior;
};
class MallardDuck :public duck
{
public:
	MallardDuck() {
		flyBehavior = new FlyWithNoWay();
		quackBehavior = new Squeak();
	}
	void display() override {
	  //我是一只丑小鸭
	}
};

这里我们发现对于每个鸭子的构造函数里还是用了根据实际实现编程的思路,但此时我们已经可以在运行时随意更改行为接口指向的具现类从而改变行为。此时的代码已经很有弹性的,只是在构造函数中的初始化做法不够弹性,后面我们在学了工厂模式后可以进一步优化。

“有一个”关系相当有趣:每一个鸭子都有一个flyBehavior和QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。当你将两个类结合起来使用,如同本例一般,这就是组合(composition)。这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”而来的。

这里我又学到了第三个设计原则:多用组合,少用继承。使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值