注:本文基于jdk1.6和Clojure1.2
策略模式
策略模式是指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,
但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。(维基百科)
在面向对象语言中,这可能是最常见的模式,可能很多人在听说过这个算法之前就已经使用过它了。
首先,我们定义一个策略接口:
/**
* 缴税策略接口
* @author RoySong
*/
public interface IPayTaxStrategy {
/**
* 根据收入计算需要交纳的个人所得税
* @param salary
* @return
*/
public Double payTax(Double salary);
}
然后,我们实现天朝个税和美帝个税的实现策略:
/**
* 天朝个税策略
*@author RoySong
*/
public class ChinaPayTaxStrategy implements IPayTaxStrategy {
/* 应纳个人所得税税额= 应纳税所得额× 适用税率- 速算扣除数,针对月薪
* 应纳税所得额=扣除三险一金后月收入 -扣除标准
* 在这儿,我们假定三险一金为150,扣除标准为3500
* @see strategy.IPayTaxStrategy#payTax(java.lang.Float)
*/
@Override
public Double payTax(Double salary) {
Double oughtPay = salary - 150 - 3500;//应纳税所得额
if(oughtPay <= 0)
return 0.0;
if(oughtPay <= 1500)
return oughtPay * 0.03;
if(oughtPay > 1500 && oughtPay <= 4500)
return oughtPay * 0.1 - 105;
if(oughtPay > 4500 && oughtPay <= 9000)
return oughtPay * 0.2 - 555;
if(oughtPay > 9000 && oughtPay <= 35000)
return oughtPay * 0.25 - 1005;
if(oughtPay > 35000 && oughtPay <= 55000)
return oughtPay * 0.3 - 2755;
if(oughtPay > 55000 && oughtPay <= 80000)
return oughtPay * 0.35 - 5505;
if(oughtPay > 80000)
return oughtPay * 0.45 - 13505;
return null;
}
}
/**
* 美帝个税策略
*@author RoySong
*/
public class USAPayTaxStrategy implements IPayTaxStrategy {
/* 取的是已婚人士的个税策略,针对年薪
* @see strategy.IPayTaxStrategy#payTax(java.lang.Double)
*/
@Override
public Double payTax(Double salary) {
if(salary <= 0)
return 0.0;
if(salary <= 16750)
return salary * 0.1;
if(salary > 16750 && salary <= 68000)
return 1675 + (salary - 16750) * 0.15;
if(salary > 68000 && salary <= 137300)
return 9362.5 + (salary - 68000) * 0.25;
if(salary >= 137300 && salary <= 209250)
return 26687.5 + (salary - 137300) * 0.28;
if(salary >= 209250 && salary <= 373650)
return 46833.5 + (salary - 209250) * 0.33;
if(salary > 373650)
return 101085.5 + (salary - 373650) * 0.35;
return null;
}
}
然后我们开始定义计算实际收入的类:
/**
* 薪水计算类
*@author RoySong
*/
public class PayMent {
private IPayTaxStrategy payTaxStrategy;
/**
* 计算实际收入
* @param incoming
* @return
*/
public Double getRealSalary(Double incoming){
return incoming - payTaxStrategy.payTax(incoming);
}
public void setPayTaxStrategy(IPayTaxStrategy payTaxStrategy) {
this.payTaxStrategy = payTaxStrategy;
}
}
然后我们调用一下看看:
public class StrategyTest {
public static void main(String[] args){
PayMent payMent = new PayMent();
//月收入5000元的国人能够拿到手的钱
payMent.setPayTaxStrategy(new ChinaPayTaxStrategy());
System.out.println("天朝月入5000到手:"+payMent.getRealSalary(5000.0));
//年入50000刀的美帝能够拿到手的钱
payMent.setPayTaxStrategy(new USAPayTaxStrategy());
System.out.println("美帝年入50000到手:"+payMent.getRealSalary(50000.0));
}
}
运行结果是:
天朝月入5000到手:4959.5
美帝年入50000到手:43337.5
嗯,天朝老百姓果然要比美帝轻松很多。这是开玩笑的。从这个例子中,我们可以看到,不变的部分是实际收入=
总收入- 个税。而变化的部分则是个税的计算手段。策略模式就是将变化的部分提取出来,以多态的方式进行封装。如果
需要添加新的个税计算方式,比如说阿三的个税策略,那么我们只需要新添加IPayTaxStrategy接口的一个实现类即可,
而不用去修改原来的个税计算策略;如果天朝的个税计算策略有变动,我们也只需改动或者重写ChinaPayTaxStrategy
类即可,也不会影响到其他的个税计算策略。
在上篇文章 中,我们已经提过Clojure中的函数可以作为参数和返回类型,那么策略模式对于Clojure来说,根本就不是
模式而是常态了。
首先我们先定义一个工具函数,用于判断某个数字的区间范围:
(defn between [source start end] (if (>= source start) (if (< source end) true nil) nil))
然后,定义天朝和美帝的个税策略:
(defn china-tax [salary] (let [ought-pay (- salary 150 3500)] (cond (<= ought-pay 0) 0 (between ought-pay 0 1500) (* ought-pay 0.03) (between ought-pay 1500 4500) (- (* ought-pay 0.1) 105) (between ought-pay 4500 9000) (- (* ought-pay 0.2) 555) (between ought-pay 9000 35000) (- (* ought-pay 0.25) 1005) (between ought-pay 35000 55000) (- (* ought-pay 0.3) 2755) (between ought-pay 55000 80000) (- (* ought-pay 0.35) 5505) (> ought-pay 80000) (- (* ought-pay 0.45) 13505)))) (defn usa-tax [salary] (cond (<= salary 0) 0 (between salary 0 16750) (* salary 0.1) (between salary 16750 68000) (+ (* (- salary 16750) 0.15) 1675) (between salary 68000 137300) (+ (* (- salary 68000) 0.25) 9362.5) (between salary 137300 209250) (+ (* (- salary 137300) 0.28) 26687.5) (between salary 209250 373650) (+ (* (- salary 209250) 0.33) 46833.5) (> salary 373650) (+ (* (- salary 373650) 0.35) 101085.5)))
最后,我们定义计算实际收入的函数:
(defn payment [fn incoming] (- incoming (fn incoming)))
然后用同样的条件来调用看看:
=> (println "天朝月入5000到手:" (payment #'china-tax 5000)) 天朝月入5000到手: 4959.5 nil => (println "美帝年入50000到手:" (payment #'usa-tax 50000)) 美帝年入50000到手: 43337.5 nil
得到了同样的结果,不是吗?策略模式的精髓在于通过在运行中注入不同的对象来采取不同的策略,在java中要达到
这个目的,我们需要抽象出策略接口,添加策略实现类,然后在上下文中设置注入点;而在Clojure中,只需要在调用
策略时将对应的策略函数作为参数传入即可。