▐ 模式定義
▐ 適用場景
適用於多節點的流程處理,每一個節點完成各自負責的部分,節點之間不知道彼此的存在,好比 OA 的審批流,Java Web 開發中的 Filter 機制。php
多個對象能夠處理同一個請求,但具體由哪一個對象處理則在運行時動態決定。java
在請求處理者不明確的狀況下向對個對象中的一個提交一個請求。web
須要動態處理一組對象處理請求。算法
舉一個生活中的例子,好比你忽然想世界那麼大你想去看看,可是處於現實的你還不能丟了工做,獲得請假的OA申請,請假天數若是是半天到1天,可能直接主管批准便可;若是是1到3天的假期,須要部門經理批准;若是是3天到30天,則須要總經理審批;大於30天,正常不會批准。這種簡單的流程便可試用於咱們當前業務場景。編程
▐ 實踐經驗
業務流程很簡單:設計模式
打電話註銷信用卡微信
工做人員註銷信用卡app
註銷信用卡有個背景是這樣的,若是信用卡存在帳單未還清,存在溢出款,存在特殊年費未使用等狀況是不容許註銷信用卡的,鑑於此,咱們在註銷以前加了一套是否容許註銷的檢驗邏輯。編輯器
大致以下:ide
是否存在帳單未還清,好比有已出帳單未還清,有未出帳單未還清,有年費管理費等未還清等。
是否存在溢出款多餘的錢。
是否存在高額積分未使用,需用戶確認放棄積分等。
針對這幾類狀況創建了三類過濾器,分別是:
UserLogoutUnpaidBillsLimitFilter:是否存在未還清金額。
UserLogoutOverflowLimitFilter:是否存在溢出款。
UserLogoutGiveUpPointsLimitFilter:是否放棄高額金額。
判斷邏輯是先經過UserLogoutUnpaidBillsLimitFilter判斷當前用戶是否能夠註銷信用卡。若是容許繼續由 UserLogoutOverflowLimitFilter 判斷是否存在溢出款,是否能夠註銷信用卡;若是沒有溢出款繼續由UserLogoutGiveUpPointsLimitFilter 判斷當前用戶是否存在高額積分,前面三條判斷,只要有一個不知足就提早返回。
public boolean canLogout(String userId) { //獲取用戶信息 UserInfo userInfo = getUserInfo(userId);
// 構造註銷信用卡限制過濾器鏈條 LogoutLimitFilterChain filterChain = new LogoutLimitFilterChain(); filterChain.addFilter(new UserLogoutUnpaidBillsLimitFilter()); filterChain.addFilter(new UserLogoutOverflowLimitFilter()); filterChain.addFilter(new UserLogoutGiveUpPointsLimitFilter()); boolean checkResult = filterChain.doFilter(filterChain, userInfo);
//filterChain.doFilter方法 public boolean doFilter (LogoutLimitFilterChain filterChain, UserInfo userInfo){ //迭代調用過濾器 if (index < filters.size()) { return filters.get(index++).doFilter(filterChain, userInfo); } } }
//UserLogoutUnpaidBillsLimitFilter.doFilter方法 public boolean doFilter(LogoutLimitFilterChain filterChain, UserInfo userInfo) { //獲取用戶當前欠款金額 UserCardBillInfo userCardBillInfo = findUserCardBillInfo(userInfo);
// 判斷當前卡用戶是否容許消費 if (userCardBillInfo != null) { if ((!CAN_LOGOUT.equals(userCardBillInfo.getEnabledLogout()))) { return false; } } //其他狀況,繼續日後傳遞 return filterChain.doFilter(filterChain, memberInfo, consumeConfig); }
//UserLogoutOverflowLimitFilter.doFilter方法 public boolean doFilter(LogoutLimitFilterChain filterChain, UserInfo userInfo) { //判斷用戶是否存在溢出款 UserCardDeposit userCardDeposit = findUserCardDeposit(userInfo);
// 判斷當前卡用戶是否容許消費 if (userCardDeposit != null) { if (userCardDeposit.getDeposit() != 0) { return false; } } //其他狀況,繼續日後傳遞 return filterChain.doFilter(filterChain, memberInfo, consumeConfig); }
總結:將每種限制條件的判斷邏輯封裝到了具體的 Filter 中,若是某種限制條件的邏輯有修改不會影響其餘條件,若是須要新加限制條件只須要從新構造一個 Filter 織入到 FilterChain 上便可。
責任鏈中一個處理者對象,其中只有兩個行爲,一是處理請求,二是將請求轉送給下一個節點,不容許某個處理者對象在處理了請求後又將請求轉送給上一個節點的狀況。對於一條責任鏈來講,一個請求最終只有兩種狀況,一是被某個處理對象所處理,另外一個是全部對象均未對其處理,前一種狀況稱該責任鏈爲純的責任鏈,對於後一種狀況稱爲不純的責任鏈,實際應用中,多爲不純的責任鏈。
策略設計模式
▐ 模式定義
策略這個詞應該怎麼理解,打個比方說,咱們出門的時候會選擇不一樣的出行方式,好比騎自行車、坐公交、坐火車、坐飛機等等,這些出行方式,每一種都是一個策略。
再好比咱們去逛商場,商場如今正在搞活動,有打折的、有滿減的、有返利的等等,其實無論商場如何進行促銷,說到底都是一些算法,這些算法自己只是一種策略,而且這些算法是隨時均可能互相替換的,好比針對同一件商品,今天打八折、明天滿100減30,這些策略間是能夠互換的。
策略模式(Strategy Pattern)是定義了一組算法,將每一個算法都封裝起來,而且使它們之間能夠互換。
▐ 適用場景
主要是爲了消除大量的 if else 代碼,將每種判斷背後的算法邏輯提取到具體的策略對象中,當算法邏輯修改時對使用者無感知,只須要修改策略對象內部邏輯便可。這類策略對象通常都實現了某個共同的接口,能夠達到互換的目的。
多個類只有算法或行爲上稍有不一樣的場景
算法須要自由切換的場景
須要屏蔽算法規則的場景
▐ 實踐經驗
業務流程很簡單:
挑選商品
選擇不一樣的優惠方式結帳
好比即將到來的雙十一活動某些線下商家舉辦活動,折扣力度以下滿300-80,部分商品5折,根據不一樣會員等級享受不一樣的折扣最低7折,週年慶活動可享8折等等。假如這些活動折扣不可同享,那麼如何去實現以及考慮可擴展性的話策略模式是一種不錯的選擇。
/** * 抽象折扣策略接口 */
public abstract class DiscountStrategy { /** * 計算折扣後的價格 * @param price 原價 * @return 折扣後的價格 */ public abstract CalculationResult getDiscountPrice(Long userId ,BigDecimal price);}
/** * 滿減活動 -- 滿300減80 */public class FullReductionStrategyOne extends DiscountStrategy { /** * 計算折扣後的價格 * @param price 原價 * @return */ @Override public CalculationResult getDiscountPrice(Long userId ,BigDecimal price) { if (price.doubleValue() < 300) { return price; } BigDecimal dealPrice= price.subtract(BigDecimal.valueOf(80)); return getCalculationResult(userId,dealPrice); }}
/** * 部分商品5折 */public class MerchandiseDiscountStrategy extends DiscountStrategy { /** * 計算折扣後的價格 * @param price 原價 * @return */ @Override public CalculationResult getDiscountPrice(BigDecimal price) { BigDecimal dealPrice= price.multiply(BigDecimal.valueOf(0.5)); return getCalculationResult(userId,dealPrice); }}
/***當有新的需求式,咱們只須要添加一個新的接口便可,不須要修改原有的具體策略實現代碼便可完成。*定義完策略後,咱們再定義一個」環境角色」,假設咱們這個環境角色就使用價格對象吧*/
public class Price {
private DiscountStrategy discountStrategy;
/** * 定義一個無參構造,用於實例對象 */ private Price(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; }
/** * 獲取折扣後的價格 * * @param price 原始價格 * @return */ public CalculationResult discount(Long userId,BigDecimal price) { return discountStrategy.getDiscountPrice(userId ,price); }}
模板設計模式
▐ 模式定義
模板的價值就在於骨架的定義,骨架內部將問題處理的流程已經定義好,通用的處理邏輯通常由父類實現,個性化的處理邏輯由子類實現。
好比炒土豆絲和炒麻婆豆腐,大致邏輯都是:
一、切菜
二、放油
三、炒菜
四、出鍋
1,2,4 都差很少,可是第 3 步是不同的,炒土豆絲得拿鏟子翻炒,可是炒麻婆豆腐得拿勺子輕推,不然豆腐會爛。
▐ 使用場景
不一樣場景的處理流程,部分邏輯是通用的,能夠放到父類中做爲通用實現,部分邏輯是個性化的,須要子類去個性實現。
模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類能夠按須要重寫方法實現,但調用將以抽象類中定義的方式進行。這種類型的設計模式屬於行爲型模式。
▐ 實踐經驗
仍是接着以前商品折扣的例子來講,後期咱們新加了兩個需求:
用戶享受不一樣折扣增長 trace。
用戶享受折扣後是否升級會員等級。
因此如今的流程變成了這樣:
一、trace 開始。
二、計算用戶不一樣折扣力度。
三、是否容許升級會員等級,若是容許執行升級會員等級邏輯。
四、trace 結束。
其中 1 和 4 是通用的,2 和 3 是個性化的,鑑於此能夠在折扣策略以前增長了一層父類的策略,將通用邏輯放到了父類中。
修改後的代碼以下:
abstract class AbstractDiscountStrategy implements DiscountStrategy{
public CalculationResult getDiscountPrice(Long userId ,BigDecimal price) { //1.構造span Span span = buildSpan(); //2.具體通道推送邏輯由子類實現 CalculationResult calculationResult =getCalculationResult(userId,price); //3.是否容許升級會員等級,若是容許執行升級邏輯 if(!calculationResult.isSuccess() && canUpgrade()){ upgradeLevel(userId,calculationResult); }
//4.trace結束 span.finish(); return calculationResult; }
//具體推送邏輯由子類實現 protected abstract CalculationResult getCalculationResult(Long userId,BigDecimal price) ;
//是否容許升級會員等級由子類實現 protected abstract boolean canUpgrade(Long userId,CallResult callResult);
}
/** * 滿減活動 -- 滿300減80 */public class FullReductionStrategyOne extends AbstractDiscountStrategy { protectedCalculationResult getCalculationResult(Long userId,BigDecimal price){ //執行折扣邏輯 }
protected boolean canUpgrade(Long userId,CallResult callResult){ return false }}
觀察者設計模式
▐ 模式定義
拍賣的時候,拍賣師觀察最高標價,而後通知給其餘競價者競價 這種模式就可使用觀察者模式。顧名思義,此模式須要有觀察者(Observer)和被觀察者(Observable)兩類角色。當 Observable 狀態變化時會通知 Observer,Observer 通常會實現一類通用的接口。
好比 java.util.Observer,Observable 須要通知 Observer 時,逐個調用 Observer 的 update 方法便可,Observer 的處理成功與否不該該影響 Observable 的流程。
▐ 使用場景
當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。好比,當一個對象被修改時,則會自動通知依賴它的對象。觀察者模式屬於行爲型模式。
一個對象(Observable)狀態改變須要通知其餘對象,Observer 的存在不影響 Observable 的處理結果,Observer 的增刪對 Observable 無感知。
好比 Kafka 的消息訂閱,Producer 發送一條消息到 Topic,至因而 1 個仍是 10 個 Consumer 訂閱這個 Topic,Producer 是不須要關注的。
▐ 實踐經驗
在責任鏈設計模式那塊我經過三個 Filter 解決了註銷信用卡限制檢驗的問題,其中有一個 Filter 是用來檢驗用戶積分的,我這裏只是讀取用戶的積分總額和次數,那麼消費次數得到積分的累加是怎麼完成的呢?
其實累加這塊就用到了觀察者模式,具體來說是這樣,當交易系統收到支付成功回調時會經過 Spring 的事件機制發佈「支付成功事件」。
這樣負責積分消費次數累加和負責語音播報的訂閱者就會收到「支付成功事件」,進而作各自的業務邏輯。
畫個簡單的圖描述一下:
/**支付回調處理者*/PayCallBackController implements ApplicationContextAware { private ApplicationContext applicationContext;
//若是想獲取applicationContext須要實現ApplicationContextAware接口,Spring容器會回調setApplicationContext方法將applicationContext注入進來 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
"/pay/callback.do") (value = public View callback(HttpServletRequest request){ if(paySuccess(request){ //構造支付成功事件 PaySuccessEvent event = buildPaySuccessEvent(...); //經過applicationContext發佈事件,從而達到通知觀察者的目的 this.applicationContext.publishEvent(event); } }}/** * 語音播報處理者 * */public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{ public void onApplicationEvent(PaySuccessEvent event) { //語音播報邏輯 }}
//其餘處理者的邏輯相似
總結:觀察者模式將被觀察者和觀察者之間作了解耦,觀察者存在與否不會影響被觀察者的現有邏輯。
裝飾器設計模式
▐ 模式定義
裝飾器模式(Decorator Pattern)容許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是做爲現有的類的一個包裝。這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
裝飾器用來包裝原有的類,在對使用者透明的狀況下作功能的加強,好比 Java 中的 BufferedInputStream 能夠對其包裝的 InputStream 作加強,從而提供緩衝功能。
▐ 使用場景
在不影響其餘對象的狀況下,以動態、透明的方式給單個對象添加職責。須要動態地給一個對象增長功能,這些功能也能夠動態地被撤銷。 當不能採用繼承的方式對系統進行擴展或者繼承。但願對原有類的功能作加強,但又不但願增長過多子類時,可使用裝飾器模式來達到一樣的效果。
▐ 實踐經驗
有一個咖啡店,銷售各類各樣的咖啡,拿鐵,卡布奇洛,藍山咖啡等,在沖泡前,會詢問顧客是否要加糖,加奶,加薄荷等。這樣不一樣的咖啡配上不一樣的調料就會賣出不一樣的價格。
/*** 抽象類Coffee*/public abstract class Coffee { /** * 獲取咖啡得名字 */ public abstract String getName();
/** * 獲取咖啡的價格 */ public abstract double getPrice();}
/***利用繼承和組合的結合,如今咱們能夠考慮設計出一個裝飾類,它也繼承自coffee,*而且它內部有一個coffee的實例對象*/public abstract class CoffeeDecorator implements Coffee { private Coffee delegate;
public CoffeeDecorator(Coffee coffee) { this.delegate = coffee; }
@Override public String getName() { return delegate.getName(); }
@Override public double getPrice() { return delegate.getPrice(); }}
/***牛奶咖啡的裝飾者模式的案例*/public class MilkCoffeeDecorator extends CoffeeDecorator { public MilkCoffeeDecorator(Coffee coffee) { super(coffee); }
@Override public String getName() { return "牛奶, " + super.getName(); }
@Override public double getPrice() { return 1.1 + super.getPrice(); }}
//其餘咖啡的模式相似
/***測試案例 能夠經過加入不用內容 咖啡名稱和價格都是不一樣的*/public class App { public static void main(String[] args) { // 獲得一杯原始的藍山咖啡 Coffee blueCoffee = new BlueCoffee(); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 加入牛奶 blueCoffee = new MilkCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入薄荷 blueCoffee = new MintCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice());
// 再加入糖 blueCoffee = new SugarCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); }}
總結:使用裝飾器模式作了功能的加強,對使用者來講只須要作簡單的組合就能繼續使用原功能。裝飾器模式充分展現了組合的靈活。利用它來實現擴展。它同時也是開閉原則的體現。若是相對某個類實現運行時功能動態的擴展。這個時候你就能夠考慮使用裝飾者模式!
橋接設計模式
▐ 模式定義
橋接模式是一種結構型設計模式, 可將一個大類或一系列緊密相關的類拆分爲抽象和實現兩個獨立的層次結構, 從而能在開發時分別使用。
橋接(Bridge)是用於把抽象化與實現化解耦,使得兩者能夠獨立變化。這種類型的設計模式屬於結構型模式,它經過提供抽象化和實現化之間的橋接結構,來實現兩者的解耦。
▐ 使用場景
若是一個系統須要在抽象類和具體類之間增長更多的靈活性,避免在兩個層次之間創建靜態的繼承關係,經過橋接模式可使它們在抽象層創建一個關聯關係。
抽象部分和實現部分能夠以繼承的方式獨立擴展而互不影響,在程序運行時能夠動態的將一個抽象類子類的對象和一個實現類子類的對象進行組合,及系統須要對抽象類角色和實現類角色進行動態耦合。
一個類存在兩個(或多個)獨立變化的維度,且這兩個(或多個)維度都須要獨立進行擴展。
對於那些不但願使用繼承或由於多層繼承致使系統的個數急劇增長的系統,橋接模式尤其適用。
▐ 實踐經驗
性能管理系統中,數據產生後須要通過採集,匯聚,入庫三個流程,用戶才能查詢使用。採集能夠是snmp採集,也能夠是ems採集;匯聚可使storm匯聚,也能夠是spark匯聚;入庫能夠是hdfs入庫,也能夠是mppdb入庫。針對不一樣場景,咱們能夠靈活選擇不一樣的採集,匯聚,入庫方式。這種一個功能須要多種服務支持,每種服務又有不一樣類型的實現,使用橋接模式再適合不過。
橋接模式,顧名思義,就是把每種服務看作一座橋,咱們能夠根據實際場景選擇不一樣的橋。上述例子表示數據產生到可使用以前須要通過三座橋:採集橋->匯聚橋->入庫橋。每座橋能夠選擇不一樣的構造。
/** * * 採集橋採集服務 * */public abstract class CollectionService { abstract void execute(); public void run(){ execute(); }}
/** * 匯聚橋 匯聚服務 * */public abstract class AggregationService { public void run(){ if(null != collectionService) { collectionService.run(); } execute(); } abstract void execute(); CollectionService collectionService; public AggregationService(CollectionService collectionService){ this.collectionService = collectionService; }}
/** * * 入庫橋 入庫服務 * */public abstract class StoreService { public void run(){ if(null != aggregationService) { aggregationService.run(); } execute(); } abstract void execute(); AggregationService aggregationService; public StoreService(AggregationService aggregationService){ this.aggregationService = aggregationService; }}
/**** EMS採集橋**/
public class EMSCollectionService extends CollectionService{ void execute() { System.out.println("EMS collection."); }}
/**** SNMP採集橋**/public class SNMPCollectionService extends CollectionService{ void execute() { System.out.println("SNMP collection."); }}
/**** Storm匯聚橋**/public class StormAggregationService extends AggregationService{ public StormAggregationService(CollectionService collectionService) { super(collectionService); }
void execute() { System.out.println("Storm aggregation."); }}
/**** Spark匯聚橋**/public class SparkAggregationService extends AggregationService{ public SparkAggregationService(CollectionService collectionService) { super(collectionService); }
void execute() { System.out.println("Spark aggregation."); }}
/**** MPPDB匯聚橋**/public class MPPDBStoreService extends StoreService{ public MPPDBStoreService(AggregationService aggregationService){ super(aggregationService); }
void execute() { System.out.println("MPPDB store."); }}
/**** HDFS匯聚橋**/public class HDFSStoreService extends StoreService{ public HDFSStoreService(AggregationService aggregationService) { super(aggregationService); }
void execute() { System.out.println("HDFS store."); }}
/** * * 類功能說明: 橋接模式測試 */public class BridgeTest { public static void main(String[] args){ CollectionService snmpService = new SNMPCollectionService(); AggregationService stormService = new StormAggregationService(snmpService); StoreService hdfsService = new HDFSStoreService(stormService); hdfsService.run(); }}
總結:橋接模式能夠將系統中穩定的部分和可擴展的部分解耦,使得系統更加容易擴展,且知足OCP原則,對調用者修改關閉。
✿ 拓展閱讀
本文分享自微信公衆號 - 淘系技術(AlibabaMTT)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。