从诸葛亮的三个锦囊妙计谈策略模式
话说当年东吴孙权为刘备借走了荆州不还而耿耿于怀,却不料甘夫人去世,周郎顿时计上心来,让孙权将其妹嫁与刘备,骗刘备来东吴完婚。刘备又不是傻子,当然知道其中的猫腻,当即表态:打死也不去。诸葛亮却说无妨,当下给了赵云三个锦囊妙计,让他陪刘备去东吴完婚……最后的结果大家都知道,赵云在恰当的时候一一打开三个锦囊妙计,从而将危机一一化解。周瑜只落了个“周郎妙计安天下,赔了夫人又折兵”的笑柄。
这个故事,我们感兴趣的是三个锦囊妙计。诸葛亮一定是运用策略模式的高手,我们想啊:诸葛亮为什么要这么麻烦,做三个锦囊,他完全可以只做一个锦囊,将这三个妙计都写在它上面。可他没有这么做,而是正确的运用了策略模式做了三个锦囊。这样做的好处十分明显:诸葛亮一个锦囊写一个妙计,他的思路十分清晰,不会三个计策相互混乱。而赵云看妙计的时候也十分方便,什么时候看哪个妙计,使用十分方便,如果三个妙计混在一起,他就没这么方便了。
现在看来,这个故事正包含了策略模式的解决问题的思路:解决某一问题有多种方法或算法,如诸葛亮给了三个妙计,类的提供者要将这多种方法或算法独立开来,如诸葛亮将三个妙计分三个锦囊装,类的使用者,如赵云,在相应的条件,如故事中的恰当时间,使用相应的方法或算法。
前面我们说过命令模式,命令模式是对行为的封装;策略模式其实跟它的解决思路一样,是对算法的封装。当然,它们都一样的拥有同一个接口。
下面我们以一些例子来详细的说一说策略模式。
首先的一个例子是我们对数组的排序,对数组的排序有很多算法,我们想做一个由用户指定一个整形数组和对它排序的算法,我们给出结果这样一个方法,我们可以这样做:
public int[] sort(int[] inArray,String type)
{
if(type.equals(“a”))
{
int k = 0;
for(int i=0;i
{
for(int j=i+1;j
{
if(inArray[i]> inArray[j])
{
k = inArray[i];
inArray[i] = inArray[j];
inArray[j] = k;
}
}
}
}
else if(type.equals(“b”))
{
int k = 0;
for(int i=0;i< inArray.length;i++)
{
for(int j=0;j< inArray.length-i-1;j++)
{
if(inArray [j]> inArray [j+1])
{
k = inArray [j];
inArray [j] = inArray [j+1];
inArray [j+1] = k;
}
}
}
}
}
一看上面的代码,大家就觉得问题很多:第一,所有的算法都搅和在一起,不满足单一职责原则。第二,依赖细节,不满足依赖颠倒原则。第三,扩展性不好,如果要增加一个新的算法,需要对该方法进行改动。
下面我们来看看策略模式的解决思路。
首先是对所有的算法抽象一个接口,这是为了满足扩展性的基本条件,几乎所有的模式都要以接口为基础:
public interface SortAlgorithm
{
public void sort(int[] inArr);
}
然后是各个算法的具体实现,它们实现了SortAlgorithm接口,完成了对关注点的分离,满足单一职责原则:
public class SortOne implements SortAlgorithm
{
public void sort(int[] inArr)
{
int k = 0;
for(int i=0;i
{
for(int j=i+1;j
{
if(inArr[i]> inArr[j])
{
k = inArr[i];
inArr[i] = inArr[j];
inArr[j] = k;
}
}
}
}
}
public class SortTwo implements SortAlgorithm
{
public void sort(int[] inArr)
{
int k = 0;
for(int j=0;j< inArr.length-i-1;j++)
{
if(inArr[j]> inArr[j+1])
{
k = inArr[j];
inArr[j] = inArr[j+1];
inArr[j+1] = k;
}
}
}
}
然后我们来看客户端的调用:
public int[] sort(int[] inArray,String type)
{
SortAlgorithm sa;
if(type.equals(“a”))
{
sa = new SortOne();
}
else if(type.equals(“b”))
{
sa = new SrotTwo();
}
else
{
……
}
sa.sort(inArray);
}
上面我们就完成了策略模式对该问题的解决,满足了单一职责原则;有了一定的扩展性,如果新增一个算法的话,去实现SortAlgorithm接口就可以了。
当然,我们在客户端代码可以看到,策略模式依然没有完全去掉客户端对具体实现类的依赖,这使得我们增加一个新的算法以后,仍然需要对客户端进行修改。所以策略模式和命令模式一样,只是初步增加了一定的扩展性。如果要完全去掉客户端对具体类的依赖,可以结合工厂模式来对该问题作进一步优化,具体做法大家可以自己做,这里不再讲述。
所谓策略模式,简单说来就是对算法的封装。在实际的编码过程中,我们会遇到各种各种的选择算法的问题,这时候就可以使用策略模式了,下面以一个例子来作为本文的结束。
假设我们有一个POJO类:Employee,里面存储了很多用户的信息,如下:
public class Employee
{
private int id;
private String name;
private double servedYears;
……
public void setId(int id)
{
this.id = id;
}
public int getId()
{
return this.id;
}
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
public void setServedYears(double servedYears)
{
this.servedYears = servedYears;
}
public double getServedYears()
{
return this.servedYears;
}
……
}
现在我们有一个关于Employee的数组,需要对数组进行排序,我们知道该使用Arrays.sort()对该数组进行排序,但是在使用该方法的时候,我们首先要实现Comparator接口,如下:
Arrays.sort(emps,new Comparator(){
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();
}
});
上面实现了一个对POJO对象的排序过程,现在的问题是我们分别需要对Employee类的id、name和servedYears为索引进行排序。我们可以看出,这明显是多个算法的问题,可以使用策略模式来解决问题:
以id为索引的比较器:
public class IdComparator implements Comparator
{
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getId()-((Employee)o2).getId();
}
}
以name为索引的比较器:
public class NameComparator implements Comparator
{
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getName()-((Employee)o2).getName();
}
}
以servedYears为索引的比较器:
public class ServedYearsComparator implements Comparator
{
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getservedYears()-((Employee)o2).getServedYears();
}
}
然后,我们做一个工厂来获取具体的比较器:
public class Factory
{
public static Comparator getInstance(String type)
{
if(type.equals(“id”))
{
return new IdComparator();
}
else if(type.equals(“name”))
{
return new NameComparator();
}
else
{
return ServedYearsComparator();
}
}
}
那么我们就可以对客户端进行编码了:
public void sort(Employee[] emps,String type)
{
Arrays.sort(emps,Factory.getInstance(type));
}
我们可以看到,结合了工厂模式的策略模式的确使多算法的问题得到了很好的解决:抽取了各个算法来单独关注,满足了单一职责原则;满足了依赖颠倒原则,有了很好的扩展性。等等。
还等什么呢?赶快到实践中去灵活运用这些模式吧!