就自己实际产品中用的的模式进行分析:
策略模式
本系统中的还款模块涉及到多种还款方式的算法,并且后期可能需要经常性的调整或增减算法,因此本系统采用策略模式来定义这一系列的算法,把它们一个个封装起来,并且使它们可相互替换。使得算法可独立于使用它的客户而变化。
策略模式的结构示意图:
图4-1 策略模式结构图
(1) RepaymentModeI:
策略接口,用来约束一系列具体的策略算法。Context使用这个接口来调用具体的策略实现定义的算法。
public interface RepaymentModeI { /** * 获得应还利息 *@param annualRate 年利率 *@param loanAount 借款金额 *@param deadline 借款期限 *@param num 还款月数 *@return Double 利息 * */ public Double interest(double annualRate, double loanAount, int deadline, int num); /** * 获得应还本金 *@param annualRate 年利率 *@param loanAount 借款金额 *@param deadline 借款期限 *@param num 还款月数 *@return Double 本金 * */ public Double principal(double annualRate, double loanAount, int deadline, int num); /** * 获得应还本息 *@param annualRate 年利率 *@param loanAount 借款金额 *@param deadline 借款期限 *@return Double 本息 * */ public Double interAndPri(double annualRate, double loanAount, int deadline, int num); /** * 计算利率 * @param annualRate 年利率 * @return 利率 * */ public double rate(double annualRate); }
(2) RepaymentModeBy____:
具体的策略实现,也就是具体的算法实现。目前系统对应有:按天到期还款,按月到期还款(是指每月还息,到期还本的借款方式),按月分期还款(分期还款采用的是通用的"等额本息还款法"),按季分期付款(采用每月付息,按季等额还本的计算方式),实现代码以按月分期还款的计算本金方法为例:
/** * 按月分期还款:分期还款采用的是通用的"等额本息还款法",即借款人每月以相等的金额偿还贷款本息。也是银行房贷等采用的方法。 * 这里要注意区分 等额本息还款法和等额本金还款法 * @author sl * */ public Double interAndPri(double annualRate, double loanAount, int deadline, int num) { /** * 等额本息还款公式推导 设贷款总额为A,银行月利率为β,总期数为m(个月),月还款额设为X, * 则各个月所欠银行贷款为: * 第一个月A(1+β)-X * 第二个月[A(1+β)-X](1+β)-X = A(1+β)^2-X[1+(1+β)] * 第三个月{[A(1+β)-X](1+β)-X}(1+β)-X = A(1+β)^3-X[1+(1+β)+(1+β)^2] * … * 由此可得第n个月后所欠银行贷款为: * A(1+β)^n-X[1+(1+β)+(1+β)^2+…+(1+β)^(n-1)] = A(1+β)^n-X[(1+β)^n-1]/β * 由于还款总期数为m,也即第m月刚好还完银行所有贷款,因此有: * A(1+β)^m-X[(1+β)^m-1]/β = 0 * 由此求得: * X = Aβ(1+β)^m/[(1+β)^m-1] * */ Return loanAount*rate(annualRate)*Math.pow((1+rate(annualRate)), deadline)/(Math.pow((1+rate(annualRate)), deadline)-1); }
(3) Context:
上下文,负责和具体的策略类交互,通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法。
public class Context { private RepaymentModeI repaymentModeI; private Double annualRate; //年利率 private Double loanAount; //借款金额 private int deadline; //截止期限(按月计算) private int num; //月数 /** * 构造方法,传入一个具体的策略对象 * @param repaymentMode 具体的策略对象 */ public Context(RepaymentModeI repaymentMode, Double annualRate, Double loanAount,int deadline, int num) { this.repaymentModeI = repaymentMode; this.annualRate = annualRate; this.loanAount = loanAount; this.deadline = deadline; this.num = num; } /** * 上下文对客户端提供的操作接口,可以有参数和返回值 */ public Double getInterest() { //获得利息 //通常会转调具体的策略对象进行算法运算 return repaymentModeI.interest(annualRate, loanAount, deadline, num); } }
策略模式的本质:分离算法,选择实现。
仔细思考策略模式的结构和实现的功能,会发现,如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程的,那么就能够享受到接口的封装隔离带来的好处。也就是通过一个统一的策略接口来封装和隔离具体的策略算法,面向接口编程的话,自然不需要关心具体的策略实现,也可以通过使用不同的实现类来实例化接口,从而实现切换具体的策略。
看起来好像没有上下文什么事情,但是如果没有上下文,那么就需要客户端来直接与具体的策略交互,尤其是当需要提供一些公共功能,或者是相关状态存储的时候,会大大增加客户端使用的难度。因此,引入上下文还是很必要的,有了上下文,这些工作就由上下文来完成了,客户端只需要与上下文交互就可以了,这样会让整个设计模式更独立、更有整体性,也让客户端更简单。
但纵观整个策略模式实现的功能和设计,它的本质还是“分离算法,选择实现”,因为分离并封装了算法,才能够很容易的修改和添加算法;也能很容易的动态切换使用不同的算法,也就是动态选择一个算法来实现需要的功能了。