業務系統中的開與閉——分發模式

「對新增開放,對修改關閉。」——開閉原則。git

這裏分享一個我在業務系統設計過程當中經常使用的一個「複合模式」,用做一個在業務系統設計中運用「開閉原則」的例子。github

背景

這是一個帳務系統,負責處理各種業務流程中發生的若干個帳戶之間的轉帳相關邏輯,包括帳戶餘額的變動、以及各帳戶的流水記錄。
這個系統的複雜度在於:不一樣的業務流程,所須要操做的帳戶、金額的計算公式、以及流水的類型,都有很大的差別;即便是同一個業務,裏面也會細分爲多個子業務,帳戶、金額、流水類型又各不相同。並且,業務、子業務還會不斷的新增和變動。這就要求咱們在設計時,必須充分考慮擴展性。app

思路

首先,業務流程雖然種類繁多,可是抽象、歸納以後,其實核心邏輯很是簡單。第一步固然是校驗,而後是帳戶查找、計算金額等數據準備工做,準備好必要的數據以後就能夠記帳了,記帳完成後作一些收尾的操做。
這樣一來,全部的相關業務均可以放到這同一個「抽象」層次內來作處理了。以下圖所示。

p_w_picpath框架

擴展性問題則是這樣解決的。這個「抽象」層次內,最頂層的「服務提供者」只提供一個分發服務。它會根據外部傳入的參數,選擇對應的業務服務提供者,並將參數交給它來處理。
而「業務服務提供者」的最頂層,一樣只提供分發服務。它會根據一些更細緻的規則,選擇對應的子業務服務提供者,而後將參數交給子業務服務提供者去處理。
這樣的服務分發層級,在現實生活中能夠找到很直觀的例子。在一些大醫院的門診部大門口,會有一個分診臺。分診臺的護士會告訴你應該掛哪一個科室的號。而在一些大科室的門口,又會有一名護士負責叫號,並指點被叫到的病人去找哪位醫生。實際上,這也是我作這個「分發」服務的一個靈感來源。
在這個「分發」的框架下,新增一套業務是很是簡單的。而且,因爲真正處理業務功能的服務提供者只須要專一於本身的「一畝三分地」,與其它的業務之間不多發生耦合,所以,在必須對某些功能進行修改時,影響範圍也會很是的小。
理論上,「分發」這個動做能夠無限嵌套,所以不管多複雜的業務邏輯,均可以經過不斷的分發處理來進行簡化。只不過這樣作會使得線程處理棧變得很是深,給理解系統和debug和trouble shooting增長沒必要要的麻煩。咱們的系統作到二級分發,已經有「類爆炸」的困擾了。ide

類圖

p_w_picpath
頂層的BizAccountEventService就是咱們匹配「業務抽象」所創建起來的接口。全部的記帳相關業務功能,不管是一級分發、二級分發,仍是實際的業務功能處理,都是這個接口下的一個實現類。
只有兩個類直接實現了BizAccountEventService接口,其中之一就是BaeServiceAsDispatcher,即業務抽象內最頂層的「服務提供者」——一級分發器。一級分發器須要一個服務工廠,以根據不一樣的參數提供不一樣的接口實現類。這個工廠既能夠是一個簡單的Map,也能夠作得更復雜一些。咱們單首創建了一個工廠類,這樣能夠方便地將一級分發擴展爲二級分發。
在一級分發器中,服務工廠提供的類實際上都是二級分發器。二級分發器與一級分發器的邏輯其實是同樣的,也是利用工廠來根據不一樣的參數得到不一樣的接口實現。於是,二級分發器都繼承自一級分發器,實例之間的差異基本上只是一個工廠。
直接實現BizAccountEventService接口的另外一個類是BaeServiceAsSekeleton。這個類是一個模板類,它定義了真正的業務操做的核心步驟:校驗、預處理、轉帳、後處理。全部的業務處理類,都必須是它、或者它的子類的實例。
可是,與教科書上的「模板模式」不一樣的是,BaeServiceAsSekeleton中的模板模式,並非經過繼承、而是經過組合來實現的。這個模板中的四個步驟,被分別委託給了三個接口(BaeValidator、BaeEditor、BaeTransfer)。這樣作能夠增長不一樣子業務之間的代碼複用性。例如,業務甲的邏輯是ABCD,業務乙的邏輯是EFGH,而業務丙的則是ABGH。這種狀況下,因爲Java單繼承的限制,業務丙沒法經過繼承甲或乙的類來徹底複用代碼,可是組合的方式卻能夠輕鬆作到。
關於這個接口,項目組內曾有過爭論。有同事認爲,應當爲BaeServiceAsSkeleton類提供另外一個接口,以便於保持BizAccountEventService接口的層次簡單。這樣作固然能夠,不過咱們沒有把精力放在這件事上。spa

開閉

那麼,回過頭來看「開閉」。「對新增開放」很好理解,那麼什麼是「閉」呢?
實事求是的說,咱們不可能徹底地把「修改」關在門外。在這個「分發」方案裏,咱們也仍然須要經過修改分發相關代碼,來增長新的業務、或變動原有業務。咱們可以關閉的、也許也是咱們真正想關閉的,是大量的、大範圍的修改代碼、是這樣作所帶來的不可控的風險。
以這個「分發」方案爲例。當一種業務邏輯發生變化時,咱們須要修改的僅僅是分發相關代碼、以及這一種子業務所涉及的部分子類。這種修改的影響範圍被緊緊鎖定在業務抽象之下、甚至是單個子業務的相關抽象之下,於是,其中的風險是清晰明瞭的,於是也是能夠有效控制住的。甚至於,咱們能夠保持原有業務代碼徹底變,而經過繼承、多態等方式擴展出新的業務邏輯,從而用「廢除+新增」的方式徹底替代「修改」——固然,這種方式會帶來不少冗餘代碼,值得商榷。
可是,把不可控的風險關在門外——咱們作到了。線程

相關文章
相關標籤/搜索