Java中避免if-else-if:策略模式
本文僅僅為入門,高手勿噴。
實際工作中,我們總會遇到類似如下的需求:
某支付系統接入以下幾種商戶進行充值:易寶網易,快線網銀,19pay手機支付,支付寶支付,駿網一卡通,由于每家充值系統的結算比例不一樣,而且 同一家商戶的不同充值方式也有所不同,具體系統情況比較復雜,像支付寶既有支付寶賬號支付和支付寶網銀支付等這些暫時不考慮,為了講述策略模式這里簡單描 述,假如分為四種,手機支付,網銀支付,商戶賬號支付和點卡支付。因為沒個支付結算比例不同,所以對手續費低的做一些優惠活動,盡可能讓用戶使用手續費低 的支付方式來充值,這樣降低渠道費用,增加收入,具體優惠政策如下:
網銀充值,8.5折
商戶充值,9折
手機充值,沒有優惠
點卡充值,收取1%的渠道費
作為一個新手的代碼基本如下:
public class Example {public Double calRecharge(Double charge ,RechargeTypeEnum type ){if(type.equals(RechargeTypeEnum.E_BANK)){return charge*0.85;}else if(type.equals(RechargeTypeEnum.BUSI_ACCOUNTS)){return charge*0.90;}else if(type.equals(RechargeTypeEnum.MOBILE)){return charge;}else if(type.equals(RechargeTypeEnum.CARD_RECHARGE)){return charge+charge*0.01;}else{return null;}}} public enum RechargeTypeEnum {E_BANK(1, "網銀"),BUSI_ACCOUNTS(2, "商戶賬號"),MOBILE(3,"手機卡充值"),CARD_RECHARGE(4,"充值卡");private int value;private String description;private RechargeTypeEnum(int value, String description) {this.value = value;this.description = description;}public int value() {return value;}public String description() {return description;}public static RechargeTypeEnum valueOf(int value) {for(RechargeTypeEnum type : RechargeTypeEnum.values()) {if(type.value() == value) {return type;}}return E_BANK;} }以上代碼雖然實現了基本功能,但是四種計算方式都在一個方法內部,如果優惠模式又增加了幾十種,代碼會變成什么樣?你是否愿意來維護和擴展這樣的代碼?此時有的同學或許會說,我用switch-case來實現:
public class Example {public Double calRecharge(Double charge ,RechargeTypeEnum type ){switch(type){case RechargeTypeEnum.E_BANK:return charge*0.85;case RechargeTypeEnum.BUSI_ACCOUNTS:return charge*0.90;case RechargeTypeEnum.MOBILE:return charge; case RechargeTypeEnum.CARD_RECHARGE:retrun charge+charge*0.01;default:return null;}}}這段代碼在簡潔性確實要比if-else簡潔一些,但實際上是換湯不換藥,并沒有從本質上解決問題。
我們使用if-else事實上也是為了重用,但這只是面向過程的重用,程序員只看到代碼重用,因為他看到if-else幾種情況下大部分代碼都是重復的,只有個別不同,因此使用if-else可以避免重復代碼,并且認為這是模板Template模式。我們會從代碼運行順序來看待代碼,這種思維類似水管或者串行電路,水沿著水管流動(代碼運行次序),當遇到幾個分管(子管),就分到這幾個分管子在流動,這里就相當于碰到代碼的if-else處了。這樣的壞處就是,一旦業務發生了變化將給我們維護工作帶來極大的困難。
那么有沒有更好的實現方式呢?當然有,我們可以通過工廠模式或者策略模式避免如上的面向過程的重用。而本文將要介紹的是 策略模式
策略模式(Policy)
定義一系列的算法,把每一個算法封裝起來,并且使它們可相互替換。本模式使得算法可獨立于使用它的客戶而變化。也稱為政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it.)UML圖如下
sss1.jpg由上圖可看出策略模式由以下角色構成:
- 抽象策略角色: 策略類,通常由一個接口或者抽象類實現。
- 具體策略角色:包裝了相關的算法和行為。
- 環境角色:持有一個策略類的引用,最終給客戶端調用。
策略模式讓算法獨立于使用它的客戶而獨立變化。策略模式重點是封裝不同的算法和行為,不同的場景下可以相互替換。策略模式是開閉原則的體現,開閉原則講的是一個軟件實體應該對擴展開放對修改關 閉。策略模式在新的策略增加時,不會影響其他類的修改,增加了擴展性,也就是對擴展是開放的;對于場景來說,只依賴于抽象,而不依賴于具體實現,所以對修改是關閉的。策略模式的認識可以借助《java與模式》一書中寫到諸葛亮的錦囊妙計來學習,在不同的場景下趙云打開不同的錦囊,便化險為夷,錦囊便是抽象策略,具體的錦囊里面的計策便是具體的策略角色,場景就是趙云,變化的處境選擇具體策略的條件。
Strategy模式有下面的一些優點:
Strategy模式缺點:
1)客戶端必須知道所有的策略類,并自行決定使用哪一個策略類: 本模式有一個潛在的缺點,就是一個客戶要選擇一個合適的Strategy就必須知道這些Strategy到底有何不同。此時可能不得不向客戶暴露具體的實現問題。因此僅當這些不同行為變體與客戶相關的行為時 , 才需要使用Strategy模式。
2 ) Strategy和Context之間的通信開銷 :無論各個ConcreteStrategy實現的算法是簡單還是復雜, 它們都共享Strategy定義的接口。因此很可能某些 ConcreteStrategy不會都用到所有通過這個接口傳遞給它們的信息;簡單的 ConcreteStrategy可能不使用其中的任何信息!這就意味著有時Context會創建和初始化一些永遠不會用到的參數。如果存在這樣問題 , 那么將需要在Strategy和Context之間更進行緊密的耦合。
3 )策略模式將造成產生很多策略類:可以通過使用享元模式在一定程度上減少對象的數量。 增加了對象的數目 Strategy增加了一個應用中的對象的數目。有時你可以將 Strategy實現為可供各Context共享的無狀態的對象來減少這一開銷。任何其余的狀態都由 Context維護。Context在每一次對Strategy對象的請求中都將這個狀態傳遞過去。共享的 Strategy不應在各次調用之間維護狀態。
策略模式在實際工作中也很常用,在博客你還在用if-else嗎有過很好的闡述,策略模式不僅是繼承的代替方案,還能很好地解決if-else問題。下面結合本文之前的例子來說明一下如何使用策略模式。
策略模式下的UML圖如下:
創建抽象策略角色Strategy:
public interface Strategy {public Double calRecharge(Double charge ,RechargeTypeEnum type ); }根據需求,分別實現Strategy即具體策略角色:
public class EBankStrategy implements Strategy{@Overridepublic Double calRecharge(Double charge, RechargeTypeEnum type) {return charge*0.85;} public class BusiAcctStrategy implements Strategy{@Overridepublic Double calRecharge(Double charge, RechargeTypeEnum type) {return charge*0.90;}} public class MobileStrategy implements Strategy {@Overridepublic Double calRecharge(Double charge, RechargeTypeEnum type) {return charge;}} public class CardStrategy implements Strategy{@Overridepublic Double calRecharge(Double charge, RechargeTypeEnum type) {return charge+charge*0.01;}}創建環境角色Context:
public class Context {private Strategy strategy;public Double calRecharge(Double charge, Integer type) {strategy = StrategyFactory.getInstance().creator(type);return strategy.calRecharge(charge, RechargeTypeEnum.valueOf(type));}public Strategy getStrategy() {return strategy;}public void setStrategy(Strategy strategy) {this.strategy = strategy;}}StrategyFactory工廠,負責Strategy實例的創建:
public class StrategyFactory {private static StrategyFactory factory = new StrategyFactory();private StrategyFactory(){}private static Map strategyMap = new HashMap<>();static{strategyMap.put(RechargeTypeEnum.E_BANK.value(), new EBankStrategy());strategyMap.put(RechargeTypeEnum.BUSI_ACCOUNTS.value(), new BusiAcctStrategy());strategyMap.put(RechargeTypeEnum.MOBILE.value(), new MobileStrategy());strategyMap.put(RechargeTypeEnum.CARD_RECHARGE.value(), new CardStrategy());}public Strategy creator(Integer type){return strategyMap.get(type);}public static StrategyFactory getInstance(){return factory;} }開始測試:
public class Client {public static void main(String[] args) {Context context = new Context();// 網銀充值100 需要付多少Double money = context.calRecharge(100D,RechargeTypeEnum.E_BANK.value());System.out.println(money);// 商戶賬戶充值100 需要付多少Double money2 = context.calRecharge(100D,RechargeTypeEnum.BUSI_ACCOUNTS.value());System.out.println(money2);// 手機充值100 需要付多少Double money3 = context.calRecharge(100D,RechargeTypeEnum.MOBILE.value());System.out.println(money3);// 充值卡充值100 需要付多少Double money4 = context.calRecharge(100D,RechargeTypeEnum.CARD_RECHARGE.value());System.out.println(money4);}}運行結果:
85.0
90.0
100.0
101.0
從上面類圖和代碼可以看出,策略模式把具體的算法封裝到了具體策略角色內部,增強了可擴展性,隱蔽了實現細節;它替代繼承來實現,避免了if- else這種不易維護的條件語句。當然我們也可以看到,策略模式由于獨立策略實現,使得系統內增加了很多策略類;對客戶端來說必須知道兜友哪些具體策略, 而且需要知道選擇具體策略的條件。
總結
凡事具有兩面性,策略模式也不例外,下面簡單列舉一下策略模式的優缺點。
優點:
缺點:
2 . Strategy和Context之間的通信開銷 :無論各個ConcreteStrategy實現的算法是簡單還是復雜, 它們都共享Strategy定義的接口。因此很可能某些 ConcreteStrategy不會都用到所有通過這個接口傳遞給它們的信息;簡單的 ConcreteStrategy可能不使用其中的任何信息!這就意味著有時Context會創建和初始化一些永遠不會用到的參數。如果存在這樣問題 , 那么將需要在Strategy和Context之間更進行緊密的耦合。
3 . 策略模式將造成產生很多策略類:可以通過使用享元模式在一定程度上減少對象的數量。 增加了對象的數目 Strategy增加了一個應用中的對象的數目。有時你可以將 Strategy實現為可供各Context共享的無狀態的對象來減少這一開銷。任何其余的狀態都由 Context維護。Context在每一次對Strategy對象的請求中都將這個狀態傳遞過去。共享的 Strategy不應在各次調用之間維護狀態。
最后,是否應該重構一下你的代碼了?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Java中避免if-else-if:策略模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google play billing(
- 下一篇: Java对象及相关