開閉原則是軟件設計最重要的原則之一,設計具有良好可擴展性的業務架構極其依賴該原則。業務品種在增多,已有業務自身也在發展,需要設計一套統一,靈活,互相獨立的業務架構。筆者在多個項目中,多次使用新型領導設計模式來設計可擴展的業務架構,覺得有必要總結出來,以供大家參考。本來是想把該模式歸爲23種設計模式之一,可能是由於能力有限,沒能成功。
新型領導,把握大局(主流程),做事開明。新型領導設計模式算是筆者一種戲謔的叫法,只希望容易記憶。
最差的做法
曾經在N個項目中看到無數個if else,每一個業務種類一個else if,沒有流程,沒有擴展點,所有的東西塞到一個class裏面。做得好一點的會抽取幾個公用方法出來(這是代碼複用,不是可擴展)。可能有的同學會反駁,“我從來不設計,不會寫這樣的代碼”。不過我不信你沒有看到過,或者被動寫過類似的代碼(歷史包袱)。反正我被動寫過,想哭的節奏…
理想狀態
Manager統領全局,控制流程,在具體的任務處理上,交給(委託)合適的人來做。
流程以及流程上一些通用的處理是封閉的,任務處理是開放的,A業務交由A處理器操作,B業務交由B處理器操作。示意圖如下:
流程應該交由類似工作流引擎的系統處理,可擴展性的問題可用新型領導模式解決。
新型領導設計模式
新型領導設計模式要解決兩個問題。
一,Manager如何把多個Employee組織起來?Employee用個List存起來即可。如何發現Employee,最好通過Scan(掃描)或者註冊的方式。二,如何把合適的任務交由合適的人處理?把決策權交給Employee,Manager詢問誰能處理?誰先舉手交給誰處理。
好了,新型領導模式的工作方式如下: Manager接到一個任務,逐個詢問手下的員工,誰能處理這個任務,員工針對任務,評估並回饋Manager是或者否。把任務交給回饋YES的員工。
類圖如下:
一個例子
投資市場上的投資品有很多種,比如債券,股票,房產,基金,信託,保險等等,統稱爲資產,未來可能還需要支持更多的投資品。現在要設計一個資產系統,有一個功能是計算現值。所有資產都有這個功能,可是每一個的計算方式可能有些不一樣。爲簡化起見,假設暫時僅支持債券和股票。
資產的抽象
public interface Asset {
/**
* 資產名稱
*/
String getAssetName();
/**
* 是否固定收益
*/
boolean isFixed();
}
public class BondAsset implements Asset {
@Override
public String getAssetName() {
return "債券";
}
@Override
public boolean isFixed() {
return true;
}
}
public class StockAsset implements Asset {
@Override
public String getAssetName() {
return "股票";
}
@Override
public boolean isFixed() {
return false;
}
}
處理器抽象
public interface Handler {
/**
* 是否能夠處理該種資產
*/
boolean canHandle(Asset asset);
/**
* 計算現值
*/
BigDecimal calculateValue(Asset asset);
}
public class BondHandler implements Handler {
@Override
public boolean canHandle(Asset asset) {
return asset instanceof BondAsset;
}
@Override
public BigDecimal calculateValue(Asset asset) {
// 查詢股票數據庫的上一收盤價
// 省略N多股票邏輯
return new BigDecimal(100);
}
}
public class StockHandler implements Handler {
@Override
public boolean canHandle(Asset asset) {
return asset instanceof StockAsset;
}
@Override
public BigDecimal calculateValue(Asset asset) {
// 查詢債券數據庫的上一收盤日中值
// 省略N多債券邏輯
return new BigDecimal(100);
}
}
統一調用入口
public interface AssetService {
/**
* 計算現值
*/
BigDecimal calculateValue(Asset asset);
}
public class AssetServiceImpl implements AssetService {
@Autowired
private List<Handler> handlers;
@Override
public BigDecimal calculateValue(Asset asset) {
Handler handler = getHandler(asset);
if (handler == null) {
throw new RuntimeException("暫時不支持該類型的資產, 找不到對應的Handler");
}
return handler.calculateValue(asset);
}
/*
* 詢問處理器,誰能處理該類型資產
*/
private Handler getHandler(Asset asset) {
if (handlers == null || handlers.isEmpty()) {
return null;
}
for (Handler handler : handlers) {
if (handler.canHandle(asset)) {
return handler;
}
}
return null;
}
}
例子補充說明
AssetServiceImpl裏面的handlers,是通過Spring的@Autowired運行時注入的。其它的方式包括XML配置註冊,API調用註冊,ClassPath掃描等。
Asset和Handler之間的mapping關係,每一次都調用getHandler,如果handlers數量太多,效率可能比較差。應該嘗試HashMap等其它策略。
理想狀態下,新增資產,只需要實現Asset,實現Handler即可。比如現在需要支持基金,那麼寫FundAsset和FundHandler兩個類,連配置都不需要。完美的體現開閉原則。
後記
新型領導設計模式有Facade和State設計模式的影子,統一的處理入口體現Facade,By上下文來選擇不同的處理方式,體現State。有可擴展性設計需要的時候,不妨用它來驗證一下,把成篇的If Else扔掉吧。