通過下面這個實例對組合方法進行分析:某公司“一卡通”聯機交易子系統,類似於銀行的交易系統。
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
案例中使用了幾個模式:
- 策略模式:負責對扣款策略進行封裝,保證兩個策略可以自由切換,而且日後增加扣款策略也非常簡單。
- 工廠方法模式:修正策略模式必須對外暴露具體策略的問題,由工廠方法模式直接產生具體策略對象,其他模塊不需依賴具體策略。
- 門面模式:負責對複雜的扣款系統進行封裝,避免高層模塊深入子系統內部,同時提供系統的高內聚、低耦合的特性。