策略(strategy)模式

Head First一书中对于策略(strategy)模式的正式定义是:策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

为了介绍这个算法,书中讲了一个例子:

在某个游戏中,有各种各样的鸭子,系统的内部设计使用了严格的OO技术,设计了一个鸭子(Duck)父类,所有的鸭子种类均继承于此父类。Joe设计的鸭子类如下:

package com.first.strategy;

public class Duck1 {

	public Duck1()
	{
	}
	//鸭子会游泳
	public void swim()
	{	
	}
	//鸭子会呱呱叫
	public void quack()
	{	
	}
	//鸭子会飞
	public void fly()
	{	
	}
	//描述不同的鸭子,子类继承时覆盖重写
	public void display()
	{	
	}
}

然后让各种鸭子都继承此类,并且重写display()方法,例如:

class DuckA extends Duck1
{
	public void display()
	{	
		System.out.println("I am DuckA!");
	}
}

这样的设计缺点显而易见:新加入的鸭子种类“橡皮鸭子”也能飞,且也能呱呱叫(橡皮鸭子不能飞也不能呱呱叫),这是违背现实的。因为所有的子类均会继承来自父类的fly()方法和quack()方法,所有的子类均具备了fly()和quack(),使得不适合子类的行为也继承了父类的行为。

这时,Joe又想到了继承,可以在子类橡皮鸭(RubberDuck)中覆盖父类的fly()和quack()方法,让其不能飞也不能呱呱叫。

package com.first.strategy;

public class RubberDuck extends Duck1{

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("我不会呱呱叫,我会吱吱叫!");
	}

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I can not fly!");
	}
}


这时缺点同样清楚:代码重复太多,每个子类全部要覆盖,代码的可重用性太低。

Joe想到了接口,即把Duck类中的fly和quack从父类中抽象出来,变成接口Flyable和Quackable,这样,只有会飞的鸭子子类才继承Flyable接口,只有会呱呱叫的鸭子继承Quackable接口。这也不是一个好主意,因为它的缺点同样显而易见:和上面重写覆盖类似,重复的代码太多,不易维护,要修改,子类全要修改。

既然继承并不能很好的解决这个鸭子问题,那么我们便寻求变化。鸭子的行为在子类中不断变化,并且让所有的鸭子都具有这些行为是不恰当的。Flyable和Quackable接口的想法开始不错,但是,Java接口中的方法不具有实现,所有子类继承接口无法达到代码复用的效果。这就意味着,无论何时要修改某个行为,那么必须向下追踪所有定了这个行为的类(或子类)中去修改。这时一个设计原则应运而生,恰好解决此问题:

设计原则1:找出应用中可能需要变化之处,把他们独立出来,不要把需要变化的代码和不变的代码混杂在一起。即:把会变化的的部分取出来封装起来,以便以后可以轻易的改动或扩充此部分,而不影响不会变化的部分。

现在就把变化的部分从Duck类中分离,变化的部分就是fly()和quack(),因为这两者会随着鸭子的不同而改变。我们分离出来的两个方法建立两个类,一个是与fly相关,一个与quack相关,每一组类实现各自的动作或行为。例如一个类实现“呱呱叫”,另外一个类实现“吱吱叫”,还有一个类实现“安静不叫”(例如木头鸭子)。这是运用了第二条原则:

设计原则2:针对接口编程,而不是实现。

所有的和fly相关的行为,组装成一个接口,quack接口类似。并且分别定义各种与之相关的行为。

package com.first.strategy;

public interface FlyBehavior {

	public void fly();
	
}
package com.first.strategy;

public interface QuackBehavior {

	public void quack();
}

呱呱叫类:

package com.first.strategy;

public class Quack implements QuackBehavior{

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		//呱呱
		System.out.println("quack..quack");
	}

	
}

吱吱叫类:

package com.first.strategy;

public class Squeak implements QuackBehavior{

	public void quack() {
		// TODO Auto-generated method stub
		//吱吱
		System.out.println("squeak..squeak");
	}
}
和fly相关的两个类:
<pre class="java" name="code"><pre class="java" name="code">package com.first.strategy;

public class FlyNoWay implements FlyBehavior{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("Can not fly!!");
	}
	

}
package com.first.strategy;

public class FlyWithWings implements FlyBehavior{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("Flying with wings!");
	}

}



这样一来,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为被抽象出来,与鸭子类无关了。我们可以增加一些动作,既不会影响现有的行为类,也不会影响使用这些行为类的鸭子类。

现在要整合鸭子的行为:

1.在Duck中加入两个实例变量,分别是FlyBehavior的实例变量flybehavior和QuackBehavior的实例变量quackbehavior,为借口类型而不是具体的实现类型(为了使用多态)。同时将原Duck类中的fly()和quack()方法删除,加入两个相似的方法performFly()和performQuack()

package com.first.strategy;

public abstract class Duck {

	FlyBehavior flybehavior;
	QuackBehavior quackbehavior;
	
	public Duck()
	{
		
	}
	public abstract void display();
	
	public void performFly()
	{
		flybehavior.fly();
	}
	public void performQuack()
	{
		quackbehavior.quack();
	}
	public void swim()
	{
		System.out.println("All ducks can swim!");
	}
}

2.Duck子类中对flybehavior和quackbehavior实例变量的设置:

package com.first.strategy;

public class MallardDuck extends Duck{
	
	public MallardDuck()
	{
		flybehavior = new FlyWithWings();
		quackbehavior = new Squeak();
	}
	
	public void display()
	{
		System.out.println("I am a mallardDuck!");
	}
}
package com.first.strategy;

public class RubberDuck extends Duck1{

	@Override
	public void quack() {
		// TODO Auto-generated method stub
		System.out.println("我不会呱呱叫,我会吱吱叫!");
	}
	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I can not fly!");
	}
}
3.主测试用例
package com.first.strategy;

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Duck mallardduck = new MallardDuck();
		
		mallardduck.display();
		
		mallardduck.performFly();
		
		mallardduck.performQuack();
	}

}

 

4.运行程序。

动态设定行为

1.在鸭子里很多动态的功能没有用到,很可惜,我们可以在Duck类中增加setter方法来设置鸭子的行为,而不是在构造器中实例化:

package com.first.strategy;

public abstract class Duck {

	FlyBehavior flybehavior;
	QuackBehavior quackbehavior;
	
	public Duck()
	{
		
	}
	public void setFlyBehavior(FlyBehavior flybehavior)
	{
		this.flybehavior = flybehavior;
	}
	
	public void setQuackBehavior(QuackBehavior quackbehavior)
	{
		this.quackbehavior = quackbehavior;
	}

	public abstract void display();
	
	public void performFly()
	{
		flybehavior.fly();
	}
	public void performQuack()
	{
		quackbehavior.quack();
	}
	public void swim()
	{
		System.out.println("All ducks can swim!");
	}
}


2.构造一个新的鸭子模型:模型鸭子(ModelDuck),这个鸭子开始不会飞。

package com.first.strategy;

public class ModelDuck extends Duck{
	
	public MallardDuck()
	{
		flybehavior = new FlyNoWay();//不会飞
		quackbehavior = new Quack();
	}
	
	public void display()
	{
		System.out.println("I am a ModelDuck!");
	}
}


3.建立新的FlyBehavior类型

package com.first.strategy;

public class FlyRocketPowered implements FlyBehavior{

	@Override
	public void fly() {
		// TODO Auto-generated method stub
		System.out.println("I can fly with a rocket!");
	}

}


4.主测试类:

package com.first.strategy;

public class Client {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Duck modelduck = new ModelDuck();
		modelduck.performFly();//开始不会飞
                  	modelduck.setFlyBehavior(new FlyRocketPowered());
		modelduck.performFly();//现在会飞了
	}

}


 

鸭子和FlyBehavior和QuackBehavior是“HAS—A”关系,两个类组合起来(composition),鸭子的行为不是继承来的,而是组合而来。这里是第三个设计原则:

原则3:多用组合,少用继承。

使用组合组建系统具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态的改变行为。


 

posted @ 2015-08-22 21:52  sunp823  阅读(314)  评论(0编辑  收藏  举报