【设计模式】策略模式详解

前言

学习设计模式已经有一段时间了,前段时间一直在忙一个安卓的app,没时间更新。今天有点空,本着开源的精神,把策略模式的一些东西分享一下。

注意:博主只是个搞安卓的大三学生狗,以下内容纯属自学的,若有不正确的地方欢迎指出。

 

正文

转载请注明出处: http://blog.csdn.net/h28496/article/details/46403815

发 表 时 间: 2015年6月7日

 

举例说明为什么要有策略模式

假设一个场景:我们有一些排序算法,需要观察每个排序算法的排序过程。然后把它封装为客户端。

1. 一般情形下应该怎么做?

我们可以写一个客户端类 Client_NotUseStrategy.java,如下:(注意run()方法里面的switch-case)

/**
 * 没有使用策略模式的客户端
 * @author 郑海鹏
 */
public class Client_NotUseStrategy {
	public static final int INSERT_SORT = 0;
	public static final int BUBBLE_SORT = 1;

	int sortType;
	int[] a;

	public Client_NotUseStrategy(int sortType, int[] a) {
		this.sortType = sortType;
		this.a = a;
	}

	public void run() {
		switch (sortType) {
		case INSERT_SORT:
			insertSort();
			break;

		case BUBBLE_SORT:
			bubbleSort();
			break;

		default:
			break;
		}
	}

	/**
	 * 插入排序
	 */
	private void insertSort() {
		for (int i = 1; i < a.length; i++) {
			int temp = a[i];
			int j = i - 1;
			for (; j >= 0; j--) {
				if (a[j] > temp) {
					a[j + 1] = a[j];
				} else {
					break;
				}
			}
			a[j + 1] = temp;
			Tools.print(a);
		}
	}

	/**
	 * 冒泡排序
	 */
	private void bubbleSort() {
		for (int i = 0; i < a.length - 1; i++) {
			for (int j = 0; j < a.length - i - 1; j++) {
				if (a[j] > a[j + 1]) {
					Tools.swap(a, j, j + 1);
				}
			}
		}
	}
}

执行结果:

2. 这种解决方案的缺点

试想,我们现在要再添加一个快速排序的排序方式。需要怎么做呢?

① 需要更改客户端的run()方法,增加一个case。

② 还有可能需要在客户端中增加一个quickSort()方法。

即,当需要扩展功能时,必须修改客户端代码。

除此之外,如果具体的排序算法需要被修改,那么还要去修改客户端代码。例如我要修改一下插入排序,那么还需要修改Client_NotUseStrategy类。

即,当需要修改原功能时,也必须修改客户端代码。

只要客户端代码需要被修改,这就不是一个好的代码结构。因为好的代码结构怎么能不满足“对修改封闭,对扩展开放”的开闭原则呢?

那要怎么修改才能在这种多分支结构下也满足开闭原则呢?这就引出策略模式了。

 

初步了解策略模式

什么是策略模式呢?

1. 策略模式的定义

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

     

2. 什么时候需要使用策略模式?

有大量if-else if – … - else或者switch – case … , 并且这些不同的操作属于同一类型的操作,只是具体的处理方式不同时,就可以使用策略模式。

     

3. 策略模式的组成

1.UML图

2.角色介绍

① IStrategy:策略接口;

② ConcreteStrategy1, ConcreteStrategy2: 具体的策略实现,封装了相关的算法和行为;

③ Context:用来操作策略的上下文环境。

 

使用策略模式解决问题

现在,我们利用策略模式来解决刚才遇到的排序问题。注意观察Client_NotUseStrategy类和Client_UseStrategy类的区别。

1. 代码结构

2. IStrategy接口

/**
 * 策略接口
 * @author 郑海鹏
 */
public interface IStrategy {
	
	void sort(int[] a);

}


3. Strategy_BubbleSort具体实现策略之冒泡排序

/**
 * 实现策略接口的一个冒泡排序
 * 
 * @author 郑海鹏
 */
public class Strategy_BubbleSort implements IStrategy {

	@Override
	public void sort(int[] a) {
		for (int i = 0; i < a.length - 1; i++) {
			for (int j = 0; j < a.length - i - 1; j++) {
				if (a[j] > a[j + 1]) {
					Tools.swap(a, j, j + 1);
				}
			}
		}
	}
}

4.Strategy_InsertSort 具体实现策略之插入排序

/**
 * 实现策略接口的一个插入排序
 * @author 郑海鹏
 */
public class Strategy_InsertSort implements IStrategy {

	@Override
	public void sort(int[] a) {

		for (int i = 1; i < a.length; i++) {
			int temp = a[i];
			int j = i - 1;
			for (; j >= 0; j--) {
				if (a[j] > temp) {
					a[j + 1] = a[j];
				} else {
					break;
				}
			}
			a[j + 1] = temp;
			Tools.print(a);
		}
	}
}

5. Context 上下文环境

/**
 * 上下文类,用来放置策略
 * @author 郑海鹏
 */
public class Context {
	IStrategy strategy;
	
	public Context(IStrategy strategy){
		this.strategy = strategy;
	}
	
	public void sort(int[] a){
		this.strategy.sort(a);
	}
}

6. 客户端代码

/**
 * 使用策略模式的客户端
 * @author 郑海鹏
 */
public class Client_UseStrategy {
	IStrategy strategy;
	int[] a;
	
	public Client_UseStrategy(IStrategy strategy, int[] a){
		this.strategy = strategy;
		this.a = a;
	}
	
	public void run(){
		Context context = new Context(strategy);
		context.sort(a);
	}
}

7. 执行效果

结果和不使用策略模式的结果是一样。

8. 总结

在上面的代码中,如果我们还要再添加一个排序算法,只需添加一个实现了Istrategy接口的类,如快排Strategy_QuickSort即可。需要去修改客户端Client_UseStrategy吗?不需要。实现了具体策略与客户端的解耦。

反观没有使用策略模式的客户端Client_NotUseStrategy,如果需要添加新的算法,必须修改客户端类。

 

关于策略模式中Context的思考

1. 在策略模式中,Context角色有存在的必要吗?

我在学习策略模式时,这个问题困扰我很长时间。如上面的例子,Context完全可以不要。下面是去掉Context之后的代码:

/**
 * 没有用Context的客户端
 * 
 * @author 郑海鹏
 */
public class Client_UseStrategy_WithoutContext {
	IStrategy strategy;
	int[] a;

	public Client_UseStrategy_WithoutContext(IStrategy strategy, int[] a) {
		this.strategy = strategy;
		this.a = a;
	}

	public void run() {
		// 原来的
		// Context context = new Context(strategy);
		// context.sort(a);

		// 不要Context
		this.strategy.sort(a);
	}
}

可以看到,这样也是可以执行出同样的效果。并且优缺点也和有Context时一样。 那Context是不是真的没用呢?肯定不是的。

 

2. Context角色的作用 – 消除冗余代码,提高代码复用性

假设,我们需要计算不同的排序用的时间,可以怎么做呢?下面有两种方法,思考一下哪种更好。

① 可以在Client客户端代码的run()方法中添加计算时间的代码,如下:

/**
 * 使用策略模式的客户端
 * 
 * @author 郑海鹏
 */
public class CopyOfClient_UseStrategy_WithoutContext {
	IStrategy strategy;
	int[] a;

	public CopyOfClient_UseStrategy_WithoutContext(IStrategy strategy, int[] a) {
		this.strategy = strategy;
		this.a = a;
	}

	public void run() {
		// 原来的
		// long startTime = System.currentTimeMillis();
		// Context context = new Context(strategy);
		// context.sort(a);
		// long endTime = System.currentTimeMillis();
		// long usedTime = endTime - startTime;
		// System.out.println("该排序用掉的时间是:" + usedTime);

		// 不要Context
		long startTime = System.currentTimeMillis();
		this.strategy.sort(a);
		long endTime = System.currentTimeMillis();
		long usedTime = endTime - startTime;
		System.out.println("该排序用掉的时间是:" + usedTime);
	}
}

② 在Context中添加计算时间的代码,如下:

/**
 * 新Context类
 * @author 郑海鹏
 * @since 2015年6月5日
 */
public class CopyOfContext {
	IStrategy strategy;
	
	public CopyOfContext(IStrategy strategy){
		this.strategy = strategy;
	}
	
	public void sort(int[] a){
		long startTime = System.currentTimeMillis();
		this.strategy.sort(a);
		long endTime = System.currentTimeMillis();
		long usedTime = endTime - startTime;
		System.out.println("该排序用掉的时间是:" + usedTime);
	}
}

哪种方式更好呢?尤其是需要添加的代码较多时。

很明显,我们不应该添加客户端不用关心的代码在客户端中。于是在Context中添加代码就合理多了。

从代码复用的角度去看,当客户端存在多个时,在每个客户端中都添加一堆相同的计时代码,显然太冗余了。而使用Context则可消除这些冗余

假设有100个客户端类,有一天老板让改一下这些代码,这些相同的代码就得改100次。但如果放在Context中,只需修改一次,代码复用的优势就体现出来了。

 

3. Context角色的作用 – 满足开闭原则

上面的例子中,IStrategy的sort(int[] a)方法只有一个参数。突然有一天,需求改了,参数变成了两个(int[] a, float[] b)。这时应该怎么修改代码呢?

① 如果没有Context:我们需要去在每个客户端代码中更改代码this.strategy(a)为this.strategy(a, b)。这没有满足开闭原则

② 如果有Context:我们只需在Context类中修改sort(int[] a)方法即可,客户端代码不受任何影响。

 

策略模式在Android开发中的使用

下面举一个在Android开发中用到策略模式的例子。

在Android开发中经常会用到Handler。Handler一般放在Activity中,子线程通过handler发送消息到Activity,然后通过switch(msg.what)的方式来处理不同的消息。这会造成如下问题:

① 需要定义很多常量,用于 case xxx。子线程在发送时需要调用这些常量,这增加了类之间的耦合性。

 ② 当处理的消息类型很多时,就会出现大量的case分支,Activity类的代码行数嗖嗖嗖就上去了,十分臃肿。一个Activity类的任务可是很重的,仅仅一个handler就占了几百行代码是个很恐怖的事。(在此吐槽一下很多东西都需要Activity来处理的这种设计,不过据说在刚过去的I/O大会上,Google把 Data Binding引入到了Android中,有空学习一下~)

③ 没有满足开闭原则。

下面只贴了activity的代码,其它部分的代码在文末的附件中。模拟的是在两个子线程中从服务器下载图片和文字。然后通过handler发送消息给Activity,并显示在界面上。在handler处理msg时用到策略模式。

1. 没有使用策略模式的Activity

/**
 * @author 郑海鹏
 * @since 2015年6月5日
 */
public class NotUseStrategy extends Activity {
	/**
	 * 从服务器下载图片完毕,用于handler的消息
	 */
	public static final int MSG_DOWNLOAD_IMAGE_FINSHED = 100;

	/**
	 * 从服务器获得好友列表结束
	 */
	public static final int MSG_GET_CONTAINS_FINSH = 101;

	Handler handler;
	LinearLayout bgLayout;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		bgLayout = (LinearLayout) findViewById(R.id.bgLayout);

		initHandler();
		new Thread_DownloadImage1(handler, this).start();
		new Thread_GetContains1(handler).start();
		
	}

	/**
	 * 初始化Handler
	 */
	private void initHandler() {
		handler = new Handler(new Handler.Callback() {

			@Override
			public boolean handleMessage(Message msg) {
				// 通过msg.what的不同处理各种操作
				switch (msg.what) {
				// 当图片下载完毕后,加入到视图中
				case MSG_DOWNLOAD_IMAGE_FINSHED:
					// 新建一个ImageView
					ImageView imageView = new ImageView(NotUseStrategy.this);
					// 获得从子线程中传入的Bitmap
					Bitmap bitmap = (Bitmap) msg.obj;
					// 将imageView的图像设置为传入的图像
					imageView.setImageBitmap(bitmap);
					// 添加ImageView到视图中
					bgLayout.addView(imageView);
					break;

				// 当联系人下载完毕后,加入到视图中
				case MSG_GET_CONTAINS_FINSH:
					TextView textView = new TextView(NotUseStrategy.this);
					String s = (String) msg.obj;
					textView.setText(s);
					bgLayout.addView(textView);
					break;

				default:
					break;
				}
				return false;
			}
		});
	}
}

2. 使用策略模式后的Activity

public class UseStrategy extends Activity {
	// 使用策略模式不用再预先定义一堆消息。降低了耦合性。

	Handler handler;
	LinearLayout bgLayout;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		bgLayout = (LinearLayout) findViewById(R.id.bgLayout);

		initHandler();
		new Thread_DownloadImage2(handler, this, bgLayout).start();
		new Thread_GetContains2(handler, this, bgLayout).start();
	}

	/**
	 * 初始化handler
	 */
	private void initHandler() {
		handler = new Handler(new Handler.Callback() {
			@Override
			public boolean handleMessage(Message msg) {
				// 使用策略模式,不用写一堆switch-case
				// 获得传入的策略
				IStrategy strategy = (IStrategy) msg.obj;
				// 把策略放到Context中执行
				MyContext context = new MyContext(strategy);
				context.run();
				return false;
			}
		});
	}
}

3. 运行截图


4. 评价

使用策略模式时,不管以后多添加了多少种情况,initHandler()方法中的那三行代码都能处理过来。整个Actiivty类中的代码几乎不需要修改(要修改也不会是在这个方法中修改)。不需要预定义一些msg,耦合性也很低。

 

代码下载

http://download.csdn.net/detail/h28496/8782581

转载请注明出处: http://blog.csdn.net/h28496/article/details/46403815
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值