定义
defines a family of functionality, encapsulate each one, and make them interchangeable
定义一个抽象的算法类,将具体的算法封装成单独的对象,使它们可以相互替换。
实列
无论是生活中还是程序中,我们都会面临许许多多的问题,而解决问题的方式或方法往往又有很多,这时就需要我们根据具体情况选择一个合适的方式解决问题。
比如说,每天上下班你都会面临如何到达目的地的问题,是步行还是开车亦或是坐公交,你都需要做出权衡取舍。同样,程序中如果你要给一串无序的数字排序,那么你有冒泡排序、快速排序、插入排序等等排序算法可以选者;
还有,如果你要给一串字符加密,那么你同样会有很多算法可选如:MD5、SHA1、DES。上面的例子都可以使用策略模式来实现,接下来我们看看如何正确的使用策略模式。
小故事
一周前,我帮老板开发了一款计算税后收入的软件,当时只能计算工资。
前天,他期望这软件也能计算年终奖,因为工资和年终奖的算法不一样,所以当天我就增加好了。
今天,又和我说要增加,利息......,天知道他要加多少种。
public class Calculator {
public Integer getRealIncome(Integer amount,String type){
Integer incomeTax= 0;
if("工资".equals(type)){
//......计算工资所得税的算法
}else if("年终奖".equals(type)){
//......计算年终奖所得税的算法
}else if("利息".equals(type)){
//......计算利息所得税的算法
}
//税后收入
return amount-incomeTax;
}
}
问题
故事中,有三种类型的收入:工资、年终奖、利息,分别对应三种不同的算法。之后,很可能还会要求增加股息、红利等算法,且不说这么多的算法耦合在一个上下文(IncomeTaxCalculator)中,会造成代码膨胀、难以维护,
而且还会让代码失去扩展性、稳定性。比如说,你的同事接手了你的代码,他想不修改你代码的前提下,给这个软件扩展一种新的收入类型,那该如何实现呢?
又或者,他开发的算法性能更好,想替换你的算法或者两个都同时保留由客户端根据具体情况进行切换,那又该怎么办?
最后,如果收入类型有很多且数量不固定,真这样不停的用条件语句进行扩展,难免会殃及池鱼。
因此,这种情况下我们应该使用更具有扩展性的方式——策略模式。
方案
策略模式,首先会将算法从上下文(Context)中提取出来并封装到单独的类中,这个类被称为策略类(Strategy);然后会维护一个指向策略对象的引用,并向客户端暴露设置该引用的操作,使客户端在运行时可以动态的切换策略。
这样,扩展或修改算法时,只需新增或修改策略类,而不是修改上下文的代码。如此这般,也就不会影响其它算法,造成代码膨胀、难以维护。
另外,客户端可以在运行时,根据具体情况动态的切换策略;因为,策略对象是由客户端创建并传递给上下文的,上下文根本不知道算法的实现细节甚至连具体是哪一个策略都不知道,关于算法它只负责将计算任务委派给策略,其它一概不知。
应用
接下来,我们使用策略模式重构一下"计算税后收入的软件"。
首先,创建抽象的所得税计算策略,它接收一个金额并返回该金额所要交纳的所得税。
/**抽象所得税计算策略*/
public interface TaxStrategy {
public Integer calculate(Integer amount);
}
然后,创建各种收入类型对应的策略类,它是抽象策略类的子类,负责实现具体的算法。
/**工资所得税计算策略*/
public class SalaryStrategy implements TaxStrategy {
@Override
public Integer calculate(Integer amount) {
System.out.println("计算薪资所得税");
}
}
/**利息所得税计算策略*/
public class InterestStrategy implements TaxStrategy {
@Override
public Integer calculate(Integer amount) {
System.out.println("计算利息所得税");
}
}
/**年终奖所得税计算策略*/
public class YearEndBonusStrategy implements TaxStrategy {
@Override
public Integer calculate(Integer amount) {
System.out.println("计算薪资所得税");
}
}
现在,我们修改一下上下文类Calculator,让它使用策略来计算各种收入的所得税。
/**个税计算器*/
public class Calculator {
protected TaxStrategy taxStrategy;
public void setTaxStrategy(TaxStrategy taxStrategy) {
this.taxStrategy = taxStrategy;
}
public Integer getRealIncome(Integer amount){
Integer incomeTax = taxStrategy.calculate(amount);
//税后收入
return amount-incomeTax;
}
}
最后,我们在看看客户端如何动态切换算法。
public class Client {
public static void main(String[] args) {
Calculator calculator = new Calculator();
//计算年终奖的收入
calculator.setTaxStrategy(new YearEndBonusStrategy());
Integer bonusIncome = calculator.getRealIncome(10000);
/**动态切换策略*/
//用性能更好的算法计算年终奖
calculator.setTaxStrategy(new YearEndBonusV2Strategy());
Integer bonus = calculator.getRealIncome(10000);
}
}
结构
avatar抽象策略角色(Strategy) :声明所有具体算法的共同接口,它是对上下文对象中共同行为的抽象。
具体策略角色(ConcreteStrategy):实现抽象策略接口,负责实现具体的算法,算法细节对上下文对象不可见。
上下文角色(Context) :具体策略的触发类,它持有一个指向Strategy抽象类的引用,并向客户端暴露设置策略的操作,允许客户端动态地改变策略。
客户端(Client) :上下文的使用类,它根据具体情况决定使用哪一个策略,然后传递给上下文对象。
/**抽象策略类*/
public interface Strategy {
public void operation();
}
/**具体策略类A*/
public class ConcreteStrategyA implements Strategy{
@Override
public void operation() {
System.out.println("ConcreteStrategyA");
}
}
/**具体策略类B*/
public class ConcreteStrategyB implements Strategy{
@Override
public void operation() {
System.out.println("ConcreteStrategyB");
}
}
/**上下文类*/
public class Context {
protected Strategy strategy;
public void setStrategy(Strategy strategy){
this.strategy = strategy;
}
public void doSomeThing(){
System.out.println("before operation");
strategy.operation();
System.out.println("after operation");
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
//使用策略A
context.setStrategy(new ConcreteStrategyA());
context.doSomeThing();
//动态切换策略
context.setStrategy(new ConcreteStrategyB());
context.doSomeThing();
}
}
总结
当一个对象的某个行为存在多种实现时,那么将它们封装成一个单独的对象,并使它们同属于一个继承等级结构。
这样,可以使上下文类不关心具体的实现细节以及一致地使用这些策略,也能让客户端在运行时动态的改变策略,还能在不修改上下文类的前提下扩展策略。
网友评论