目录
介绍
在软件开发中经常遇到这样的情况:实现某一个功能可以有多种算法或者策略,我们根据实际情况可以选择不同的算法或者策略来完成该功能。例如,排序算法,可以使用插入排序、归并排序、冒泡排序等。
针对这种情况,一种常规的方法是将多种算法写在一个类中,例如,需要提供多种排序算法,可以将这些算法写到一个类中,每一个方法对应一个具体排序算法,或者通过if/else进行区分,这样明显不符合开闭原则,如果有一些功能扩展,我们只能在原始的代码基础上进行修改;也不符合单一职责原则。
如果我们将这些算法或者策略抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现类,这样在程序客户端就可以通过注入不同的实现对象来区分出不同的算法功能,这种模式的可扩展性、可维护性也就更高,这也就是我们要讲的策略模式。
定义
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换,策略模式让算法独立于使用它的客户从而可以独立变化。
使用场景
1、针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
2、需要安全的封装多种同一类型的操作时。
3、出现同一抽象类有多个子类,而又需要使用if/else或者switch/case来选择具体的子类时。
代码实现
在这里,我们以北京交通出行为例,写一个计价算法,出行方式分别为公交车、地铁、出租,简单实现如下:
public class PriceCalculator {
private static final int BUS = 1;
private static final int SUBWAY = 2;
private static final int TAXI = 3;
public static void main(String[] args) {
PriceCalculator tStrategy = new PriceCalculator();
System.out.println("坐16公里的公交车票价为 : " + tStrategy.calculatePrice(16, BUS));
System.out.println("坐16公里的地铁票价为 : " + tStrategy.calculatePrice(16, SUBWAY));
}
/**
* 北京公交车,10里之内1块钱,超过十公里之后每加一块钱可以乘5公里
*
* @param miles 公里
* @return
*/
private int busPrice(int miles) {
// 超过10公里的总距离
int extraTotal = miles - 10;
// 超过的距离是5公里的倍数
int extraFactor = extraTotal / 5;
// 超过的距离对5公里的取余
int fraction = extraTotal % 5;
// 价格计算
int price = 1 + extraFactor * 1;
return fraction > 0 ? ++price : price;
}
/**
* 6公里(含)内3元;6-12公里(含)4元;12-22公里(含)5元;22-32公里(含)6元;
*
* @param miles 公里
* @return
*/
private int subwayPrice(int miles) {
if (miles <= 6) {
return 3;
} else if (miles > 6 && miles < 12) {
return 4;
} else if (miles > 12 && miles < 22) {
return 5;
}
else if (miles > 22 && miles < 32) {
return 6;
}
// 其他距离我们简化为7块
return 7;
}
/**
* 简单计算为每公里2块钱吧
*
* @param miles
* @return
*/
private int taxiPrice(int miles) {
return miles * 2;
}
int calculatePrice(int miles, int type) {
if (type == BUS) {
return busPrice(miles);
} else if (type == SUBWAY) {
return subwayPrice(miles);
} else if (type == TAXI) {
return taxiPrice(miles) ;
}
return 0;
}
}
三种计价算法全部都写在一个类中,看到这样的设计结合我们前面讲的六大原则,很容易就应该想到,我们用抽象这样的概念很轻松可以对它进行拆分,我们可以抽象出CalculateStrategy接口,它的功能就是根据出行里程计算费用,接口定义如下:
public interface CalculateStrategy {
/**
* 按距离来计算价格
*
* @param miles 公里
* @return 返回价格
*/
int calculatePrice(int miles);
}
不同的出行方式对费用计算的规则不一样,但是都是它的子类,公交车的算法实现如下:
public class BusStrategy implements CalculateStrategy {
/**
* 北京公交车,10里之内1块钱,超过十公里之后每加一块钱可以乘5公里
*
* @param miles 公里
* @return
*/
@Override
public int calculatePrice(int miles) {
// 超过10公里的总距离
int extraTotal = miles - 10;
// 超过的距离是5公里的倍数
int extraFactor = extraTotal / 5;
// 超过的距离对5公里的取余
int fraction = extraTotal % 5;
// 价格计算
int price = 1 + extraFactor * 1;
return fraction > 0 ? ++price : price;
}
}
地铁的算法实现如下:
public class SubwayStrategy implements CalculateStrategy {
/**
* 6公里(含)内3元;6-12公里(含)4元;12-22公里(含)5元;22-32公里(含)6元;
*
* @param miles 公里
* @return
*/
@Override
public int calculatePrice(int miles) {
if (miles <= 6) {
return 3;
} else if (miles > 6 && miles < 12) {
return 4;
} else if (miles > 12 && miles < 22) {
return 5;
}
else if (miles > 22 && miles < 32) {
return 6;
}
// 其他距离我们简化为7块
return 7;
}
}
出租车的算法实现如下:
public class TaxiStrategy implements CalculateStrategy {
// 价格我们简单计算为 公里数 * 2
@Override
public int calculatePrice(int km) {
return km * 2;
}
}
客户端的使用方式如下:
public class TranficCalculator {
public static void main(String[] args) {
TranficCalculator calculator = new TranficCalculator();
calculator.setStrategy(new BusStrategy());
System.out.println("公交车乘16公里的价格 : " + calculator.calculatePrice(16));
calculator.setStrategy(new TaxiStrategy());
System.out.println("出租车乘16公里的价格 : " + calculator.calculatePrice(16));
}
CalculateStrategy mStrategy;
public void setStrategy(CalculateStrategy mStrategy) {
this.mStrategy = mStrategy;
}
public int calculatePrice(int km) {
return mStrategy.calculatePrice(km);
}
}
看到了吧,和我们前面讲六大原则中的开闭原则非常相似,只需要定义一个CalculateStrategy类型的接口成员变量,要注入哪种实现类型可以由客户端自由选择,开放性非常好,客户端可以随意定制,也不需要和具体的算法耦合。我们要作的就是根据不同的算法策略完成不同的实现即可,这就是策略模式。
回想一下Android源码中的实现,策略模式应该非常多,比如动画插值器,有各种类型的插值器:AccelerateDecelerateInterpolator、AccelerateInterpolator、DecelerateInterpolator、LinearInterpolator、AnticipateInterpolator、AnticipateOvershootInterpolator、OvershootInterpolator、BounceInterpolator、CycleInterpolator、TimeInterpolator,就是根据各自不同的规则计算出插值系数,它们都是实现了TimeInterpolator接口的,源码如下:
还有像LayoutManager,它的子类有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager,也是根据不同的规则进行不同的View布局,就可以实现不同的视图效果。试想一下,像LayoutManager这么复杂的布局逻辑,我们如果不使用策略模式把它们独立抽象开来,而是把计算和布局逻辑全部写在一个类中,那无疑是一场灾难,各种耦合、各种计算,肯定也会带来各种问题,而独立出来之后,它们各司其职,每个实例只需要实现接口定义的布局方法,就会产生出不同的效果,也完全没有任何耦合。