第35章 工厂方法模式+策略模式
35.1 迷你版的交易系统
典型,太典型的策略上下文角色。扣款模块的策略已经定义完毕了,然后我们需要想办法解决策略模式的缺陷:它把所有的策略类都暴露出去,这不行,暴露 得越多以后的修改风险也就越大(这是不是类似于女人的衣服:暴露得越多,被男同胞意淫的可能性就越大呢),怎么修改呢?增加一个映射配置文件,实现策略类 的隐藏,我们使用枚举担当此任,对策略类进行映射处理,避免高层模块直接访问策略类,同时由工厂方法模式根据映射产生策略对象,解决得很优秀,类图如图 35-3所示。
图35-3 策略工厂类图 |
又是一个简单得不能再简单的模式--工厂方法模式,通过StrategyMan负责对具体策略的映射,我们看它的代码,如代码清单35-7所示。
代码清单35-7 策略枚举
- public enum StrategyMan {
- SteadyDeduction("com.cbf4life.common.SteadyDeduction"),
- FreeDeduction("com.cbf4life.common.FreeDeduction");
- String value = "";
- private StrategyMan(String _value){
- this.value = _value;
- }
- public String getValue(){
- return this.value;
- }
- }
类似的代码解释过很多遍了,不多说,它就是一个登记容器,所有的具体策略都在这里登记,然后提供给工厂方法模式。策略工厂如代码清单35-8所示。
代码清单35-8 策略工厂
- public class StrategyFactory {
- //策略工厂
- public static IDeduction getDeduction(StrategyMan strategy){
- IDeduction deduction = null;
- try {
- deduction = (IDeduction)Class.forName
(strategy.getValue()).newInstance();- } catch (Exception e) {
- // 异常处理
- }
- return deduction;
- }
- }
一个简单的工厂,根据策略管理类的枚举项创建一个策略对象,简单而实用,策略模式的缺陷也弥补成功,那~~这么复杂的系统怎么让高层模块访问?你看 不出复杂?那是因为我们写的都是快乐路径,太多情况都没有考虑,在实际项目中有仅仅就并发处理和事务管理这两部分就够让你头疼了。既然系统很复杂,是不是 需要封装一下,我们请出门面模式由它进行封装,如代码清单35-9所示。
代码清单35-9 扣款模块封装
- public class DeductionFacade {
- //对外公布的扣款信息
- public static Card deduct(Card card,Trade trade){
- //获得消费策略
- StrategyMan reg = getDeductionType(trade);
- //初始化一个消费策略对象
- IDeduction deduction = StrategyFactory.getDeduction(reg);
- //产生一个策略上下问
- DeductionContext context = new DeductionContext(deduction);
- //进行扣款处理
- context.exec(card, trade);
- //返回扣款处理完毕后的数据
- return card;
- }
- //获得对应的商户消费策略
- private static StrategyMan getDeductionType(Trade trade){
- //模拟操作
- if(trade.getTradeNo().contains("abc")){
- return StrategyMan.FreeDeduction;
- }else{
- return StrategyMan.SteadyDeduction;
- }
- }
- }
这次为什么要先展示代码而后写类图呢?那是因为这段代码比写类图更能让你理解。读者注意一下getDeductionType方法,这个方法在实际 的项目中是存在的,但是与上面的写法有天壤之别,为啥?在实际项目中,数据库中保存了策略代码与交易编码的对应关系,直接通过数据库的SQL语句就可以返 回对应的扣款策略。这里我们采用大家最熟悉的条件转移来实现,也是比较清晰和容易理解的。
可能读者要问了,在门面模式中已经明确地说明,门面类中不允许有业务逻辑存在,但是你这里还是有了一个getDeductionType方法,它可 代表的是一个判断逻辑呀,这是为什么呢?是的,该方法完全可以移到其他Hepler类中,由于我们是示例代码,暂没有明确的业务含义,故编写在此处,读者 在实际应用中,请把该方法放置到其他类中。
好,所有用到的模式都介绍完毕了,我们把完整的类图整理一下,如图35-4所示。
图35-4 扣款子模块完整类图 |
蜘蛛网了?这还不是复杂的,真实系统比这复杂得多,幸好我们在之前已经分析过,还是比较容易看懂的。我们所有的开发都完成了,是不是应该写一个测试 类来展示一下我们的成果,如代码清单35-10所示。
代码清单35-10 场景类
- public class Client {
- //模拟交易
- public static void main(String[] args) {
- //初始化一张IC卡
- Card card = initIC();
- //显示一下卡内信息
- System.out.println("========初始卡信息:=========");
- showCard(card);
- //是否停止运行标志
- boolean flag = true;
- while(flag){
- Trade trade = createTrade();
- DeductionFacade.deduct(card, trade);
- //交易成功,打印出成功处理消息
- System.out.println("/n======交易凭证========");
- System.out.println(trade.getTradeNo()+" 交易成功!");
- System.out.println("本次发生的交易金额为:
"+ trade.getAmount()/100.0 + " 元");- //展示一下卡内信息
- showCard(card);
- System.out.print("/n是否需要退出?(Y/N)");
- if(getInput().equalsIgnoreCase("y")){
- flag = false; //退出;
- }
- }
- }
- //初始化一个IC卡
- private static Card initIC(){
- Card card = new Card();
- card.setCardNo("1100010001000");
- card.setFreeMoney(100000); //一千元
- card.setSteadyMoney(80000); //八百元
- return card;
- }
- //产生一条交易
- private static Trade createTrade(){
- Trade trade = new Trade();
- System.out.print("请输入交易编号:");
- trade.setTradeNo(getInput());
- System.out.print("请输入交易金额:");
- trade.setAmount(Integer.parseInt(getInput()));
- //返回交易
- return trade;
- }
- //打印出当前卡内交易余额
- public static void showCard(Card card){
- System.out.println("IC卡编号:" + card.getCardNo());
- System.out.println("固定类型余额:"+
card.getSteadyMoney()/100.0 + " 元");- System.out.println("自由类型余额:"+
card.getFreeMoney()/100.0 + " 元");- }
- //获得键盘输入
- public static String getInput(){
- String str ="";
- try {
- str = (new BufferedReader(new
InputStreamReader(System.in))).readLine();- } catch (IOException e) {
- //异常处理
- }
- return str;
- }
- }
类比较长,耐心一点,还是非常简单的,对其中Client类的方法说明如下:
initIC 方法
初始化一张IC卡,方便我们进行测试。
createTrade 方法
创建一笔交易,完成我们测试任务。
showCard 方法
显示IC卡内的信息,你到商店买东西,刷完卡了总要给你个纸条吧,上面记录你消费了多少,现在卡内剩余多少等等,该方法的作用既是如此。
getInput 方法
获得从键盘输入的字符,以回车符作为终结标志。
方法介绍完毕了,我们运行一下看看,结果如下所示:
- ========初始卡信息:=========
- IC卡编号:1100010001000
- 固定类型余额:800.0 元
- 自由类型余额:1000.0 元
- 请输入交易编号:abcdef
- 请输入交易金额:10000
- ======交易凭证========
- abcdef 交易成功!
- 本次发生的交易金额为:100.0 元
- IC卡编号:1100010001000
- 固定类型余额:800.0 元
- 自由类型余额:900.0 元
- 是否需要退出?(Y/N)
我们模拟了一笔自由消费,直接从自由类型金额中扣除了,我们再模拟一笔固定类型的消费,运行结果如下所示:
- ========初始卡信息:=========
- IC卡编号:1100010001000
- 固定类型余额:800.0 元
- 自由类型余额:1000.0 元
- 请输入交易编号:abcdef
- 请输入交易金额:10000
- ======交易凭证========
- abcdef 交易成功!
- 本次发生的交易金额为:100.0 元
- IC卡编号:1100010001000
- 固定类型余额:800.0 元
- 自由类型余额:900.0 元
- 是否需要退出?(Y/N)n
- 请输入交易编号:1001
- 请输入交易金额:1234
- ======交易凭证========
- 1001 交易成功!
- 本次发生的交易金额为:12.34 元
- IC卡编号:1100010001000
- 固定类型余额:793.83 元
- 自由类型余额:893.83 元
- 是否需要退出?(Y/N)
交易成功!到这里为止,我们的联机交易中扣款子模块开发完毕了!是不是很简单,银行业的交易系统也就是这么回事,想做交易系统?去吧!