策略模式(Strategy Pattern)

 

现在我们得让鸭子能飞è 在Duck类中加上fly()方法,然后所有鸭子都会继承fly()。

 

但是,可怕的问题发生了……

“橡皮鸭子”在屏幕上飞来飞去,这是你在开玩笑吗?

 

 

把“橡皮鸭子”当成一种“特色”,


 覆盖父类的fly()方法




可是,如果以后我加入诱饵鸭(DecoyDuck) -->  木头假鸭,不会飞也不会叫……

 

 

利用继承来提供Duck的行为,会导致如下行为

 

代码在多个子类中重复。

qB.  运行时的行为不容易改变。

qC.  我们不能让鸭子跳舞。

qD.  很难知道所有鸭子的全部行为。

qE.  鸭子不能同时又飞又叫。

qF.  改变会牵一发动全身,造成其他鸭子不想要的改变。

 

利用接口如何?

 

我可以把fly()从超类中取出来,放进一个“Flyable接口”中。这么一来,只有会飞的鸭子才实现此接口。同样的方式,也可以用来设计一个“Quackable接口”,因为不是所有的鸭子都会叫。


 

 

你觉得这个设计如何?

这真是一个超笨的主意,你没发现这么一来重复的代码会变多吗?如果你认为覆盖几个方法就算是差劲,那么对于48个Duck的子类都要稍微修改一下飞行的行为,你又怎么说?!

 

并非“所有”的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。

虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但是却造成代码无法复用,这只能算是从一个恶梦跳进另一个恶梦。甚至,在会飞的鸭子中,飞行的动作可能还有多种变化……

 

现在我们知道使用继承并不能很好地解决问题,因为鸭子的行为在子类不断改变,并且所有的子类都有这些行为是不恰当的

 

Flyable与Quackable接口一开始似乎还挺不错,解决了问题(只有会飞的鸭子才继承Flyable),但是Java接口不具有实现代码,所以继承接口无法达到代码的复用。

无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误!

 

设计原则

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

找到变化并封装之,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分

如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。   

这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。


分开变化和不会变化的部分:

除了fly()和quack()的问题之外,Duck类还算一切正常,似乎没有特别需要经常变化或修改的地方。

为了要分开“变化和不会变化的部分”,我们准备建立两组类(完全远离Duck类),一个是“fly”相关的,一个是“quack”相关的,每一组类将实现各自的动作。

比方说,我们可能有一个类实现“呱呱叫”,另一个类实现“吱吱叫”,还有一个类实现“安静”。


我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。

 

设计鸭子的行为 :

我们希望一切能有弹性,我们还想能够“指定”行为到鸭子的实例。比方说,我们想要产生一个新的绿头鸭(Mallard)实例,并指定特定“类型”的飞行行为给它。让鸭子的行为可以动态地改变。

换句话说,我们应该在鸭子类中包含设定行为的方法(SetBehavior),这样就可以在“运行时动态地改变”绿头鸭的飞行行为。

 

针对接口编程,而不是针对实现编程。

 

利用接口代表每个行为,比方说,FlyBehaviorQuack-Behavior,而行为的每个实现都将实现其中的一个接口。

 

鸭子类不会负责实现FlyingQuacking接口,反而是由我们制造一组其他类专门实现FlyBehaviorQuackBehavior,这就称为“行为”类。

鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。


虚线三角形   -->      实现接口(    △-------------)

 

为 什 么非要 把FlyBehavior设计成接口。为何不使用抽象超类,这样不就可以使用多态了吗?

“接口 ” 有多个含义,接口是一个“ 概念”,也是一种Java的interface构造。你可以在不涉及Java interface的情况下,“针对接口编程”,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。“针对超类型编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型!

 (C++ 中 抽象类,接口。。。额。。。。不懂)

看看下面这个简单的多态例子:假设有一个抽象类Animal,有两个具体的实现(Dog与Cat)继承Animal。

做法如下:

 

“针对实现编程”

Dog d = new Dog( );

d.bark( );

 

“针对接口/超类型编程”做法会如下:

Animal animal = new Dog( );

animal.makeSound( ); 

 

更棒的是,子类实例化的动作不再需要在代码中硬编码,例如newDog(),而是“在运行时才指定具体实现的对象”。

a = getAnimal( );

a.makeSound( );


 

 


这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。

而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

 

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

 

OO系统中,是的,类代表的东西一般都是既有状态(实例变量)又有方法。

如果你要加上一个火箭动力的飞行动作到SimUDuck系统中,你该怎么做?

建立一个FlyRocketPowered 类,实现FlyBehavior 接口。

 

整合鸭子的行为:

class Duck{
protected:
	FlyBehavior *myFlyBehavoir;
public:
	void PerformFly()
	{
		myFlyBehavoir->Fly();		
	};

};
想进行飞的动作,只要叫FlyBehavior对象去飞就好了: myFlyBehavior->Fly();在这部分的代码中,我们不在乎myFlyBehavior接口的对象到底是什么,我们只关心该 对象知道如何进行飞就够了。

同理: 想进行呱呱叫的动作,Duck对象只要叫quackBehavior对象去呱呱叫就可以了。

绿头鸭:

class MallardDuck : public Duck
{
public:
	MallardDuck(){
		myFlyBehavoir = new FlyWithWings();
	}
};

 

上面的FlyWithWings是FlyBehavior的具体实现类;myFlyBehavoir = new FlyWithWings();  MallardDuck就知道怎么叫怎么飞了。

 

通过实例化类似Quack或FlyWithWings的行为类,并把它指定到行为引用变量中。这种初始化实例变量的做法不够弹性。

但是因为quackBehavior的实例变量是一个接口类型,我们能够在运行时,通过多态的魔力动态地给它地指定不同的QuickBehavior实现类。

#include <iostream>

using namespace std;

class FlyBehavior
{
public:
	virtual void Fly() {};
};

class FlyWithWings :public FlyBehavior
{
	void Fly()
	{
		cout << "fly with wings...\n";
	}
};

class FlyNoWay :public FlyBehavior
{
	void Fly()
	{
		cout << "cannot fly...\n";
	}

};

class Duck{
protected:
	FlyBehavior *myFlyBehavoir;
public:
	void PerformFly()
	{
		myFlyBehavoir->Fly();		
	};
	void setFlyBehavior(FlyBehavior * flyBehavior)
	{
		myFlyBehavoir = flyBehavior;
	}
};

class MallardDuck : public Duck
{
public:
	MallardDuck(){
		myFlyBehavoir = new FlyWithWings();
	}
};

int main()
{	
	Duck *duck = new MallardDuck();
	duck->PerformFly();

	duck->setFlyBehavior(new FlyNoWay());
	duck->PerformFly();

	return 0;
}
 

鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。

 

多用组合,少用继承。

策略模式:定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

Motivation

There are common situations when classes differ only in their behavior. For this cases is a good idea to isolate the algorithms in separate classes in order to have the ability to select different algorithms at runtime.

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.



Strategy - defines an interface common to all supported algorithms. Context uses this interface to call the algorithm defined by a ConcreteStrategy.

Context包含一个抽象类Stategy,该抽象类有一个抽象方法,指定如何调用算法。每个派生类按需要实现该算法。具体实现的职责由Client对象承担,并转让给Context对象

ConcreteStrategy - each concrete strategy implements an algorithm.

Hot points

The strategy design pattern splits the behavior (there are many behaviors) of a class from the class itself. This has some advantages, but the main draw back is that a client must understand how the Strategies differ. Since clients get exposed to implementation issues the strategy design pattern should be used only when the variation in behavior is relevant to them.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值