【最后编辑时间:2017.5.20】
依赖抽象类型。就是为了利用面向对象技术的多态/动态绑定的强大能力。例程2-3不仅说明了这一点,还体现了一种设计模式——策略模式。
所谓策略/strategy或政策/policy指做事情的方式。处理同一件事情能够有不同的策略。正所谓“杀猪杀屁股,各有各的刀法”。比如外出旅游时。能够在多种不同的出行方式中选择,如坐汽车/火车/飞机、骑自行车或步行。海关对不同的商品依照不同的税率征收关税……。★策略模式,以策略的类层次定义和封装算法集合。环境类针对抽象策略编程。
★策略模式定义了一系列的算法。并将每个算法封装起来,并且使它们还能够相互替换。
策略模式让算法独立于使用它的客户而独立变化。[GoF]
[算法,描写叙述处理过程的步骤。一个简单的算法。能够用一个Java方法实现,也能够加上若干个private方法(以功能分解为目的)。
写策略模式,我认为。要给读者一个这种感觉:“你知道的。” 或者说,不须要我说太多,你就应该知道。
]
1.环境类
面对例程2-1所看到的的最简单粗暴的代码,一种直接的改进方式是在BubbleSort基础上重构-提取超类,将全部方法提取到超类中。当中sort(int[])指定为抽象。得到的抽象类NewClass事实上是IntSort和SortTest的复合体。NewClass违反了单一职责原则,把NewClass中接口sort(int[]arr)和工具方法swap(int[],int, int)抽取、独立出来,并形成策略IntSort类层次,(NewClass就变成了SortTest)。避免了直接编写SortTest的类层次。
SortTest被称为IntSort的环境类。[5.9·8效果2一个取代继承的方法]的正确解读是“一个取代编写环境类的类层次的手段”。2. 重构分支结构
以下以较复杂的样例,说明重构分支结构须要考虑到问题。
假定Context有一个方法sum()计算比赛成绩的某种得分:比如计算所得分数的平均数;或者去掉一个最高和一个最低分。再计算平均数。某些黑箱操作场合,计算关键人士(key)给出的分数或关键人士们给出分数的平均数。
应用系统中有一个类Context。除了其它有意义的方法外,另一个方法sum()计算比赛的某种得分。
比如计算所得分数的平均数;或者去掉一个最高和一个最低分,再计算平均数。某些黑箱操作场合,计算关键人士(key)给出的分数或关键人士们给出分数的平均数。
在糟糕的设计中,Context以分支结构推断将运行何种算法。依照[2.1.2多态代替分支结构],须要对以下的代码进行重构。
package method.strategy; import tool.God; import static tool.Print.*; /** * 环境类一般是系统中有价值的类。* @author yqj2065 * @version 0.1 */ public class Context{ //其它有意义的方法 ////////////////////////////////////////分支结构/////////////////////// public double sum(int s/*elect*/,double[] array,int... key){ if(s == 0) return m1(array); if(s == 1) return m2(array); if(s == 2) return m3(array,key); return 0; } /**平均分*/ private double sum1(double[] array){ double sum=0; for(double x: array){ sum+=x; } return sum/array.length; } /**去掉一个最高和一个最低分*/ private double sum2(double[] array){ double sum=0,max=0,min=array[0]; for(double x: array){ if(x>max)max=x; if(x<min)min=x; sum+=x; } return (sum-max-min)/(array.length-2); } /**黑箱操作*/ private double sum3(double[] array,int... key){ double sum=0; for(int i:key){ sum += array[i]; } return sum/ key.length; } }
重构分支结构。本例在归纳出一个接口时。须要将不同的方法名加以统一。还须要注意,其參数列表是详细的策略类所需数据的最大集合。最大集合意味着宁可多不可少——某些详细策略类能够不使用參数列表提供的数据,可是不可由于參数少而导致某些详细策略类缺乏须要的数据。[5.9·8效果6通信开销、9实现1]。
[我认为写到这里就能够了,除了环境类值得提一下外,策略模式=多态。yqj2065将学习和理解策略模式的难度系数设为0)]
以下为遗留文字。
2. 封装算法
对于策略模式最直观的样例,是用Test类測试各种各样的(对int数据)排序算法:选择、插入和交换排序等。很自然地,设计一个抽象类algorithm.sorting.IntSort封装抽象方法int[] sort(int[] arr),而各种排序算法被设计成IntSort的子类。当然,IntSort除了包含策略sort(int[] arr),还能够提供了工具方法:如public static void swap(int[] arr ,int one, int two)——交换数组的两个元素。
当程序猿设计第一个IntSort的子类如BubbleSort(冒泡排序算法)时。一般会在BubbleSort中编写一些简单的測试;可是。当编写十几个排序算法时。为每一种排序算法编写同样的一些測试(可以直接复制、粘贴)会非常乏味,也使得子类的代码非常难看。
一堆相关的类。彼此的唯一区别是所使用的算法。将同样的測试代码放在測试类Test中是自然地设计,程序猿们通常可以无师自通地使用策略模式。
这样的场景下。非常难想象环境类Test有什么值得关注之处。策略模式只简单地使用了多态技术。
而多态给策略模式赋予了诸多长处。
- 算法能够有新的变体。程序猿能够自己定义新的策略实现。(类的扩展性)
- 用户能够自由切换详细策略。
- 替代显式的分支语句。
传统的多重条件推断不easy维护,须要重构。
3.策略类的设计
策略模式中,策略类是设计为抽象类、接口还是函数接口?
在Java8之前。策略类假设须要提供子类共享的方法,如IntSort提供swap()则设计为抽象类;或者都设计为接口。
Java 8之后,假设可能尽量将策略类设计为函数接口。重构例程3-4
package method.strategy; public interface Sum{ double sum(double[] array,int... key); } package method.strategy; public class Sum1 implements Sum{//平均分 public double sum(double[] array,int... key){ //注意:没有使用參数列表提供的数据key double sum=0; for(double x: array){ sum+=x; } return sum/array.length; } } package method.strategy; import static tool.Print.*; public class Context2{ public static void printSum(Sum s,int... key){ double[] array ={ 8,9,9,9,9,10,5}; pln(s.sum(array,key)); } public static void test(){ printSum(new Sum1(),0); printSum(new Sum2(),0); printSum(new Sum3(),0); printSum(new Sum3(),0,1); printSum((double[] array,int... key)->{//如果没有单独的不能上台面的Sum3类 double sum=0; for(int i:key){ sum += array[i]; } return sum/ key.length; },0,1); } }
策略类Sum的抽象方法sum()的參数列表问题:
①參数列表是详细的策略类所需数据的最大集合。最大集合意味着宁可多不可少——某些详细策略类能够不使用參数列表提供的数据。可是不可使得某些详细策略类缺乏须要的数据。
[5.9·8效果6通信开销、9实现1]。当然,Sum也能够设计很多其它的setter方法,以获得须要数据。
②假定为了体现公平公正公开,sum()的參数列表仅仅可以为:double sum(double[] array);可是,程序猿又必须满足某些人的要求。让Sum类获得Context的某些数据(如int... key)。怎么办?
能够让Sum与Context双向依赖——Sum有成员变量Context或局部变量Context (Context通过參数传递给Sum)。相互依赖一般是糟糕的选择。
假设Sum属于类库而Context是应用。则仅仅可以在类库中设计IContext,IContext定义回调接口int[] getKey()。
糟糕之处在于:
- 在须要添加新分支的时候。该代码不遵循OCP。能够通过设计类层次重构分支结构。从重构分支结构的角度看。策略模式与[2.1.3工厂方法模式(3.3)]和[4.2状态模式(5.8)]是三胞胎。
- 各种sum()加上其它有意义的方法外,Context很庞大。能够将各种sum()分离出去。换言之。不设计Context的一系列子类,而是Context有一个Sum而Sum有一系列子类。
在讨论策略模式时,环境类通常被视为引子,从中推演策略模式。
通常情况下,Strategy作为服务而环境类被称为Client。是否须要将Strategy的算法从环境类中分离出去,并非什么原则问题而是设计者的权衡。[5.9·8效果2一个取代继承的方法 文不正确题]