设计模式之策略模式(Strategy Pattern)

一.什么是策略模式(Strategy Pattern)?

从字面上理解,策略模式就是应用了某种“策略”的设计模式,而这个“策略”就是:把变化的部分封装起来。

其实这个理解有误,例子没有大错,但对此模式的理解存在偏差,修改内容已经追加在本文尾部,点我跳转>>

二.举个例子

假定现在我们需要用类来描述Dog,首先,所有的Dog都有外形(比如Color),有行为(比如Run),于是我们很自然地定义了这样一个基类Dog:

public abstract class Dog {
	public abstract void display();//显示Dog的外形
	public abstract void run();//定义Dog的Run行为
}

其它的Dog具体类全部继承自Dog基类就好了,很快,需求来了:有一只Red Dog,它是宠物犬,跑得很慢,叫声也很小。于是我们又定义了RedDog类:

public class RedDog {
	@override
	public void display(){
		System.out.println("this is a red dog.");
	}
	@override
	public void run(){
		System.out.println("it's running slowly.");
	}
}

接着,来了一大批需求,Black Dog、Gray Dog、Pink Dog、BlackGrayDog、PinkGrayDog。。。一共100只Dog,除Color不同外,Run的速度也不同。于是我们定义了100个类来表示100种不同类型的Dog,每个类中都实现了Run方法与Display方法。

嗯~,看看我们的类图,没错,这个BigBang就是我们的类图了,好像很合理啊,有100种Dog当然会有100个类啊,而且具体Dog类当然要继承自Dog基类吧?嗯,好像是这样的,而且这样好像也没什么不好,至少现在看不出来有什么不妥。。。

又一个新的需求来了,我们发现Dog不仅可以Run,还可以Bark。自然而然地,我们想到了给Dog基类定义一个新行为Bark,然后在100个具体类中逐一重写了Bark方法。。。虽然过程有些麻烦,不过好在我们还是解决了问题,现在所有的Dog都可以Bark了

这天,来了一只新Dog,它叫ToyDog,是玩具狗,不会Run也不能Bark,只是个玩偶。于是我们让ToyDog继承了Dog基类,重写了Run方法和Bark方法(方法体为空,因为Toy不会Run也不会Bark)。问题好像也被完美解决了,至少现在看来不存在什么问题

很多天后,ToyDog工厂技术革新了,新产品被装上了电池,可以Bark了。没关系,我们修改了ToyDog类,实现了Bark。我们初始化了一只ToyDog,它欢快的BarkBarkBark,过了一会儿,电池没电了,ToyDog不能Bark了,但是我们的ToyDog类显示它还可以Bark。。。

现在,终于出问题了吧,我们发现Dog问题中最难处理的部分就是Run、Bark这两个行为,一旦发生变化我们就需要修改具体Dog类,甚至是Dog基类,不仅需要花费大量的时间,而且所有具体Dog类中都实现了Run与Bark方法,显得很臃肿。还有最重要的问题,我们无法动态地修改Dog的行为,比如小Dog跑得慢叫声小,长大后跑得快叫声大,也无法应对上面提到的电池没电导致的行为变化问题。。。。这一系列的问题想向我们说明一点:这从一开始就是一个糟糕的设计。

三.如何应用策略模式?

策略模式要求把变化的部分封装起来,首先,我们要找到代码中频频发生变化的部分。在上一个例子中,变化的部分是什么?

1.Run行为;2.Bark行为;3.其它可能存在的行为

下面我们把这些行为封装起来(以Run为例):

package StrategyPattern;

/**
 * @author ayqy
 * 定义Run接口
 *
 */
public interface IRun {
	public void run();//定义run行为
}
package StrategyPattern;

/**
 * @author ayqy
 * 实现RunQuickly行为
 *
 */
public class RunQuickly implements IRun{

	@Override
	public void run() {
		System.out.println("it's running quicky.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 实现RunSlowly行为
 *
 */
public class RunSlowly implements IRun{

	@Override
	public void run() {
		System.out.println("it's running slowly.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * 实现RunNoWay行为
 *
 */
public class RunNoWay implements IRun{

	@Override
	public void run() {
		System.out.println("it isn't able to run.");
	}

}

Bark行为的封装与之类似。封装好“变化”之后,我们的Dog基类也要做相应改变:

package StrategyPattern;

/**
 * @author ayqy
 * 定义Dog基类
 *
 */
public abstract class Dog {
	IBark bark;
	IRun run;
	
	public abstract void display();//显示Dog的外形
	
	public IBark getBark() {
		return bark;
	}

	public void setBark(IBark bark) {
		this.bark = bark;
	}

	public IRun getRun() {
		return run;
	}

	public void setRun(IRun run) {
		this.run = run;
	}
}

注意,我们只封装了容易发生变化的部分(Bark与Run),而没有封装Display方法(Dog的外形应该比较fixed吧),什么才是“变化的部分”?这需要我们仔细考虑,视具体情景而定

现在来看看我们新的具体Dog类吧

package StrategyPattern;

/**
 * @author ayqy
 * 实现RedDog
 *
 */
public class RedDog extends Dog{
	
	public RedDog()
	{
		//红狗是宠物狗
		//跑得慢,叫声小
		super.setRun(new RunSlowly());
		super.setBark(new BarkQuietly());
	}

	@Override
	public void display() {
		System.out.println("this is a red dog.");
	}
}

类被明显瘦身了吧,而且我们现在还能在运行时动态地修改Dog的行为,看到策略模式的威力了吧。

四.效果示例

编写一个测试类:

package StrategyPattern;

/**
 * @author ayqy
 * 测试策略模式<br/>
 * 策略模式,简单的说就是要求把“变化”封装起来,以隔离“变化”对其它部分的影响
 *
 */
public class Test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println("------- 创造一只Red Dog -------");
		Dog redDog = new RedDog();
		showDogInfo(redDog);
		
		System.out.println("\n------- 创造一只Black Dog -------");
		Dog blackDog = new BlackDog();
		showDogInfo(blackDog);
		
		System.out.println("\n------- 创造一只Toy Dog -------");
		Dog toyDog = new ToyDog();
		showDogInfo(toyDog);
		
		System.out.println("\n------- 技术革新,现在Toy Dog可以小声叫了 -------");
		toyDog.setBark(new BarkQuietly());
		showDogInfo(toyDog);
		
		//上面的代码并不是最优的,只是为了说明策略模式
		//一个很明显的问题是Dog redDog = new RedDog();
		//我们在面向具体对象编码,而不是设计模式所提倡的面向接口编码
		//可以定义一个Dog工厂来生产Dog对象以求模块之间更松的耦合
	}
	
	/**
	 * @param dog
	 * 显示Dog相关信息
	 */
	private static void showDogInfo(Dog dog)
	{
		dog.display();
		dog.run.run();
		dog.bark.bark();
	}
}

运行结果示例:

五.总结

策略模式的核心是要把频繁发生变化的部分封装起来,作用是把变化部分的影响隔离开,避免局部的变化对其它fixed部分造成影响,设计时可能需要更多的时间,但便于维护、复用与扩展,在本例中,Run、Bark行为都可以在新的类(如Pig)中复用;一旦行为发生变化我们只需要修改各个行为接口,最多再对Dog基类做简单修改就可以从容地应对变化了。

六.修正

上面的内容有些偏差,但勉强可以看,只是不太准确,下面作以准确的描述:

策略模式的思想确实是封装,但这里的”策略“并不是指”把变化封装起来“,这是在反复读了几遍原文后发现的(《Head First 设计模式》第一章感觉有点不太好,第一遍理解偏了)

这里的”策略“近似于算法

策略模式不太容易理解(虽然笔者极力避免照搬原文,但万一再理解偏了就误导别人了。。),书上的准确定义是这样的:

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

个人反复读过几遍之后,觉得原文想表达的东西是这样的:

  • 1.策略模式核心是对算法的封装(还有一种设计模式叫”模版方法模式“,也是对算法的封装,区别与联系放在里面介绍,点我跳转>>
  • 2.专注于实现算法(策略)的选择,支持运行时动态改变策略
  • 3.具体实现是把变化的部分找出来,定义为接口,每个接口对应一组算法,每一个都是一种策略
  • 4.同一接口下的算法是可以相互替换的
  • 5.算法是独立于客户代码的,也就是对算法封装的具体体现