设计模式学习笔记之策略模式

Posted birdlove1987

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式学习笔记之策略模式相关的知识,希望对你有一定的参考价值。

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


单从概念上看总觉得晕乎乎的,下面我们来看一个小例子:


下面是一个模拟小鸭子的列子:




首先,按照OOP的思想先构造一个鸭子的基类:


public abstract class Duck 
	//构造函数
	public Duck() 
	
	//鸭子叫方法
	public void Quack() 
		System.out.println("嘎嘎叫");
	
	//鸭子游泳方法
	public void swim() 
		System.out.println("游泳啦");
	
	//展示的方法写成抽象方法由子类实现
	public abstract void display();
	


现在让我们构造两个具体的鸭子:白鸭子和黑鸭子(ps:差点打成周黑鸭。。。原谅一个吃货吧。。)

白鸭子:


public class WhiteDuck extends Duck 

	@Override  //白鸭子自己的表现函数
	public void display() 
		System.out.println("我是白鸭子");
	


黑鸭子:


public class BlackDuck extends Duck 

	@Override  //黑鸭子自己的表现函数
	public void display() 
		System.out.println("我是黑鸭子");
	



我们写一个主方法来测试一下:


public class StimulateDuck 
	public static void main(String[] args) 
		
		BlackDuck mBlackDuck = new BlackDuck();
		WhiteDuck mWhiteDuck = new WhiteDuck();

		mBlackDuck.display();
		mBlackDuck.Quack();
		mBlackDuck.swim();

		mWhiteDuck.display();
		mWhiteDuck.Quack();
		mWhiteDuck.swim();
	

测试结果:




现在我们想要扩展鸭子的功能比如:飞。

我们首先想到的方法将飞这个方法写到鸭子的基类当中:


public abstract class Duck 
	//构造函数
	public Duck() 
	
	//鸭子叫的方法
	public void Quack() 
		System.out.println("嘎嘎叫");
	
	//鸭子游泳的方法
	public void swim() 
		System.out.println("游泳啦");
	
	//鸭子飞的方法
	public void Fly() 
			System.out.println("我会飞");
		
	//展示的方法写成抽象方法由子类实现
	public abstract void display();
	
	


但是这样做会有一个问题,就是并不是所有的鸭子都会飞,这样设计会产生很严重的bug。

然后我们会想到可以让不同的鸭子的子类覆盖飞的方法,但是如果鸭子的种类很多,这将是一个非常大的工作量。

ps:继承的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。影响会有溢出效应。超类挖的一个坑,每个子类都要来填,增加工作量,复杂度0(N^2)。


面对这样的问题我们便可以引入策略模式来处理。

首先我们要先对问题进行分析,分析项目中变化与不变的部分,提取变化的部分,使用抽象接口来实现。

我们回到我们自己的鸭子项目当中,仔细想一想鸭子的叫声,游泳和飞行对于不同的鸭子来说都是会变化的。所以我们可以将这三个部分抽象成接口:


//叫声接口
public interface QuackBehavior

	void quack();

//游泳接口
public interface SwimBehavior

	void swim();

//飞行接口
public interface FlyBehavior

	void fly();


通过这些接口我们可以进一步构建行为族,用飞行方式来举例:


//飞行接口
public interface FlyBehavior

	void fly();


//擅长飞行类
public class GoodFlyBehavior implements FlyBehavior

	@Override
	public void fly() 
		// TODO Auto-generated method stub
		System.out.println("飞的很好");
	


//不擅长飞行类
public class BadFlyBehavior implements FlyBehavior

	@Override
	public void fly() 
		// TODO Auto-generated method stub
		System.out.println("飞的不好");
	


//不会飞行类
public class NoFlyBehavior implements FlyBehavior

	@Override
	public void fly() 
		// TODO Auto-generated method stub
		System.out.println("不会飞");
	


这样我们就可以在基类里定义这些行为的引用,并通过调用具体行为的函数来实现行为,在子类中来具体实现这些行为引用。


public abstract class Duck 
	
	//行为引用
	FlyBehavior mFlyBehavior;
	QuackBehavior mQuackBehavior;
	SwimBehavior mSwimBehavior;
	
	//构造函数
	public Duck() 

	
	
	//飞行方法
	public void Fly() 
		mFlyBehavior.fly();
	
	
	//叫声方法
	public void Quack() 
		mQuackBehavior.quack();
	
	
	//游泳方法
	public void swim() 
		mSwimBehavior.swim();
	
	
	//展示的方法写成抽象方法由子类实现
	public abstract void display();



这事我们再来重新实现子类

白鸭子:


public class WhiteDuck extends Duck 

	public WhiteDuck() 
		//子类中实现具体的行为方式
		mFlyBehavior = new GoodFlyBehavior();
		mQuackBehavior = new GaGaQuackBehavior();
		mSwimBehavior = new  GoodSwimBehavior();
	

	@Override
	public void display() 
		System.out.println("我是白鸭子");
	

黑鸭子:


public class BlackDuck extends Duck 

	public BlackDuck() 
		//子类中实现具体的行为方式
		mFlyBehavior = new BadFlyBehavior()
		mQuackBehavior = new GeGeQuackBehavior();
		mSwimBehavior = new  BadSwimBehavior();
	

	@Override
	public void display() 
		System.out.println("我是黑鸭子");
	


这样设计的好处:新增行为简单,行为类更好的复用,组合更方便。既有继承带来的复用好处,没有挖坑。

下面我们再来重新认识一下策略模式


策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象
原则:分离变化部分,封装接口,基于接口编程各种功能。这种模式让行为算法的变化独立与算法的使用者。

我们现在写一个测试类测试一下我们重新设计的鸭子模型:


public class StimulateDuck 

	public static void main(String[] args) 
		//多态特性
		Duck mWhiteDuck = new WhiteDuck();
		Duck mBlackDuck = new BlackDuck();

		mWhiteDuck.display();
		mWhiteDuck.Fly();
		mWhiteDuck.Quack();
		mWhiteDuck.swim();

		mBlackDuck.display();
		mBlackDuck.Fly();
		mBlackDuck.Quack();
		mBlackDuck.swim();
	


输出结果



这样设计确实方便了以后的扩展啊~~~~~

而且如果我们也可以很方便的动态的重新改变鸭子行动特性


public abstract class Duck 
	
	//行为引用
	FlyBehavior mFlyBehavior;
	QuackBehavior mQuackBehavior;
	SwimBehavior mSwimBehavior;
	
	//构造函数
	public Duck() 

	
	
	//飞行方法
	public void Fly() 
		mFlyBehavior.fly();
	
	
	//叫声方法
	public void Quack() 
		mQuackBehavior.quack();
	
	
	//游泳方法
	public void swim() 
		mSwimBehavior.swim();
	
	
	//动态改变叫声行为
	public void SetQuackBehavoir(QuackBehavior qb) 
		mQuackBehavior = qb;
	
	
	//动态改变飞行行为
	public void SetFlyBehavoir(FlyBehavior fb) 
		mFlyBehavior = fb;
	
	
	//动态改变游泳行为
	public void SetSwimBehavior(SwimBehavior sb) 
		mSwimBehavior = sb;
	
	
	//展示的方法写成抽象方法由子类实现
	public abstract void display();


我们在测试函数中改变黑鸭子的飞行和叫声试一下


public class StimulateDuck 

	public static void main(String[] args) 

		Duck mWhiteDuck = new WhiteDuck();
		Duck mBlackDuck = new BlackDuck();

		mWhiteDuck.display();
		mWhiteDuck.Fly();
		mWhiteDuck.Quack();
		mWhiteDuck.swim();

		mBlackDuck.display();
		mBlackDuck.Fly();
		mBlackDuck.Quack();
		mBlackDuck.swim();
		
		//动态改变飞行行为
		mBlackDuck.SetFlyBehavoir(new NoFlyBehavior());
		mBlackDuck.Fly();
		
		//动态改变叫声行为
		mBlackDuck.SetQuackBehavoir(new NoQuackBehavior());
		mBlackDuck.Quack();
	


测试结果




感觉方便多了!!


最后我们来总结一下策略模式的优缺点:


优点:
1、 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。


2、 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。


3、 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。


缺点:
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。


2、 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。


以上是关于设计模式学习笔记之策略模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式 策略模式

设计模式 策略模式

Java常见设计模式学习(非原创)

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

设计模式学习_策略模式

设计模式之策略模式