通过下面这个实例对组合方法进行分析:某公司“一卡通”联机交易子系统,类似于银行的交易系统。
IC卡上有以下两种金额:
- 固定金额:员工不能提现的金额,只能用来特定消费,如食堂内吃饭、理发、健身等。
- 自由金额:可以提现的,也可用于消费。
每个月初,总部都会为每个员工的IC卡中打入固定数量的金额,然后提倡大家在集团内的商店消费。
系统内有两套扣款规则:
- 策略一:该类型的扣款分别在固定金额和自由金额上各扣除消费金额的一半。
- 策略二:全部从自由金额上扣除。
系统设计的时候要做到可拆卸(Pluggable),避免日后维护的大量开支。
这是策略模式的实际应用,但使用策略模式,具体策略必须暴露出去,而且还要由上层模块初始化,与迪米特法则有冲突,维护的工作量会非常大。工厂方法模式可以产生指定的对象,由于工厂方法模式要指定一个类,我们引入一个配置文件进行映射以产生对象,这里以枚举类完成该任务。
一个交易的扣款模式是固定的,根据其交易编号而定,那我们可以采用状态模式或责任链模式把交易编号与扣款策略对应起来。如果采用状态模式,则认为交易编号就是一个交易对象的状态,对于一笔确定的交易,它的状态不会从一个状态过渡到另一个状态,也就是说它的状态只有一个,执行完毕后即结束,不存在多状态的问题;如果采用责任链模式,则可以用交易编码作为链中的判断依据,由每个执行节点进行判断,返回相应的扣款模式。但是在实际中,采用了关系型数据库存储扣款规则与交易编码的对应关系,为了简化该部分,我们使用条件判断语句来代替。还有,这么复杂的扣款模块需要对其进行封装,可以使用门面模式。
在这里我们认为所有的交易都是在安全可靠的,并且所有的系统环境都满足我们的要求。
(1)首先定义出卡和交易类
public class Card {
private String cardNo = ""; //IC卡号
private int steadyMoney = 0; //固定金额
private int freeMoney = 0; //自由金额
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public int getSteadyMoney() {
return steadyMoney;
}
public void setSteadyMoney(int steadyMoney) {
this.steadyMoney = steadyMoney;
}
public int getFreeMoney() {
return freeMoney;
}
public void setFreeMoney(int freeMoney) {
this.freeMoney = freeMoney;
}
}
public class Trade {
private String tradeNo = ""; //交易编号
private int amount = 0; //交易金额
public String getTradeNo() {
return tradeNo;
}
public void setTradeNo(String tradeNo) {
this.tradeNo = tradeNo;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
一般非银行的交易系统,比如超市的收银系统,系统内都是存放的int类型,在显示的时候才转换为货币类型。
(2)策略模式实现,定义策略接口及相应的策略实现和对策略的封装
public interface IDeduction {
//扣款,并返回是否扣款成功
public boolean exec(Card card,Trade trade);
}
public class SteadyDeduction implements IDeduction {
//固定性交易扣款
@Override
public boolean exec(Card card, Trade trade) {
//固定金额和自由金额各扣50%
int halfMoney = (int)Math.rint(trade.getAmount() / 2.0);
card.setFreeMoney(card.getFreeMoney() - halfMoney);
card.setSteadyMoney(card.getSteadyMoney() - halfMoney);
return true;
}
}
public class FreeDeduction implements IDeduction {
//自由扣款
@Override
public boolean exec(Card card, Trade trade) {
//直接从自由余额中扣除
card.setFreeMoney(card.getFreeMoney() - trade.getAmount());
return true;
}
}
public class DeductionContext {
//扣款策略
private IDeduction deduction = null;
public DeductionContext(IDeduction deduction) {
this.deduction = deduction;
}
public boolean exec(Card card,Trade trade) {
return this.deduction.exec(card, trade);
}
}
(3)使用工厂方法模式解决策略模式与迪米特法则冲突的问题。
public class StrategyFactory {
//策略工厂
public static IDeduction getDeduction(StrategyMan strategy) {
IDeduction deduction = null;
try {
deduction = (IDeduction)Class.forName(strategy.getValue()).newInstance();
} catch (Exception e) {
// TODO: handle exception
}
return deduction;
}
}
public enum StrategyMan {
SteadyDeduction("myICCard.SteadyDeduction"),
FreeDeduction("myICCard.FreeDeduction");
String value = "";
private StrategyMan(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
(4)使用门面模式将系统封装
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;
}
}
}
(5)模拟交易
import java.io.BufferedReader;
import java.io.InputStreamReader;
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.println("\n是否需要退出?(Y/N)");
if (getInput().equalsIgnoreCase("y")) {
flag = false;
}
}
}
//初始化一张IC卡
private static Card initIC() {
Card card = new Card();
card.setCardNo("1100010001000");
card.setFreeMoney(100000);//1000元
card.setSteadyMoney(80000);//800元
return card;
}
//产生一条交易
private static Trade createTrade() {
Trade trade = new Trade();
System.out.println("请输入交易编号:");
trade.setTradeNo(getInput());
System.out.println("请输入交易金额:");
trade.setAmount(Integer.parseInt(getInput()));
return trade;
}
//键盘输入
public static String getInput() {
String str = "";
try {
str = (new BufferedReader(new InputStreamReader(System.in))).readLine();
} catch (Exception e) {
// TODO: handle exception
}
return str;
}
//打印当前卡内交易金额
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 + "元");
}
}
结果
-----初始卡信息-----
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)
y
案例中使用了几个模式:
- 策略模式:负责对扣款策略进行封装,保证两个策略可以自由切换,而且日后增加扣款策略也非常简单。
- 工厂方法模式:修正策略模式必须对外暴露具体策略的问题,由工厂方法模式直接产生具体策略对象,其他模块不需依赖具体策略。
- 门面模式:负责对复杂的扣款系统进行封装,避免高层模块深入子系统内部,同时提供系统的高内聚、低耦合的特性。