标签:
学习设计模式已经有一段时间了,前段时间一直在忙一个安卓的app,没时间更新。今天有点空,本着开源的精神,把策略模式的一些东西分享一下。
注意:博主只是个搞安卓的大三学生狗,以下内容纯属自学的,若有不正确的地方欢迎指出。
转载请注明出处: http://blog.csdn.net/h28496/article/details/46403815
发 表 时 间: 2015年6月7日
作 者 信 息: 中北大学 郑海鹏
假设一个场景:我们有一些排序算法,需要观察每个排序算法的排序过程。然后把它封装为客户端。
我们可以写一个客户端类 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); } } } } }
执行结果:
试想,我们现在要再添加一个快速排序的排序方式。需要怎么做呢?
① 需要更改客户端的run()方法,增加一个case。
② 还有可能需要在客户端中增加一个quickSort()方法。
即,当需要扩展功能时,必须修改客户端代码。
除此之外,如果具体的排序算法需要被修改,那么还要去修改客户端代码。例如我要修改一下插入排序,那么还需要修改Client_NotUseStrategy类。
即,当需要修改原功能时,也必须修改客户端代码。
只要客户端代码需要被修改,这就不是一个好的代码结构。因为好的代码结构怎么能不满足“对修改封闭,对扩展开放”的开闭原则呢?
那要怎么修改才能在这种多分支结构下也满足开闭原则呢?这就引出策略模式了。
什么是策略模式呢?
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
有大量if-else if – … - else或者switch – case … , 并且这些不同的操作属于同一类型的操作,只是具体的处理方式不同时,就可以使用策略模式。
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完全可以不要。下面是去掉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是不是真的没用呢?肯定不是的。
假设,我们需要计算不同的排序用的时间,可以怎么做呢?下面有两种方法,思考一下哪种更好。
① 可以在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中,只需修改一次,代码复用的优势就体现出来了。
上面的例子中,IStrategy的sort(int[] a)方法只有一个参数。突然有一天,需求改了,参数变成了两个(int[] a, float[] b)。这时应该怎么修改代码呢?
① 如果没有Context:我们需要去在每个客户端代码中更改代码this.strategy(a)为this.strategy(a, b)。这没有满足开闭原则!
② 如果有Context:我们只需在Context类中修改sort(int[] a)方法即可,客户端代码不受任何影响。
下面举一个在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时用到策略模式。
/** * @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; } }); } }
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; } }); } }
使用策略模式时,不管以后多添加了多少种情况,initHandler()方法中的那三行代码都能处理过来。整个Actiivty类中的代码几乎不需要修改(要修改也不会是在这个方法中修改)。不需要预定义一些msg,耦合性也很低。
标签:
原文地址:http://blog.csdn.net/h28496/article/details/46403815