換一種角度:從架構(gòu)層面來看設(shè)計(jì)模式
大部分講解設(shè)計(jì)模式的書或者文章,都是從代碼層面來講解設(shè)計(jì)模式,看的時(shí)候都懂,但是到真正用的時(shí)候,還是理不清、想不明。
本文嘗試從架構(gòu)層面來聊一聊設(shè)計(jì)模式。通過將使用設(shè)計(jì)模式的代碼和不使用設(shè)計(jì)模式的代碼分別放到架構(gòu)中,來看看設(shè)計(jì)模式對(duì)架構(gòu)所產(chǎn)生的影響。
一般模式講解套路
一般講解設(shè)計(jì)模式的套路是:
- 說明模式的意圖
 - 說明模式的適用場(chǎng)景
 - 給出模式的類結(jié)構(gòu)
 - 給出對(duì)應(yīng)的代碼示例
 
以策略模式為例:
意圖:定義一系列的算法,把它們一個(gè)個(gè)封裝起來, 并且使它們可相互替換。本模式使得算法可獨(dú)立于使用它的客戶而變化。
適用性:
- 許多相關(guān)的類僅僅是行為有異?!覆呗浴固峁┝艘环N用多個(gè)行為中的一個(gè)行為來配置一個(gè)類的方法。
 - 需要使用一個(gè)算法的不同變體。例如,你可能會(huì)定義一些反映不同的空間/時(shí)間權(quán)衡的算法。當(dāng)這些變體實(shí)現(xiàn)為一個(gè)算法的類層次時(shí),可以使用策略模式。
 - 算法使用客戶不應(yīng)該知道的數(shù)據(jù)??墒褂貌呗阅J揭员苊獗┞稄?fù)雜的、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
 - 一個(gè)類定義了多種行為, 并且這些行為在這個(gè)類的操作中以多個(gè)條件語句的形式出現(xiàn)。將相關(guān)的條件分支移入它們各自的Strategy類中以代替這些條件語句。
 
類結(jié)構(gòu):

示例代碼:
- public class Context {
 - //持有一個(gè)具體策略的對(duì)象
 - private Strategy strategy;
 - /**
 - * 構(gòu)造函數(shù),傳入一個(gè)具體策略對(duì)象
 - * @param strategy 具體策略對(duì)象
 - */
 - public Context(Strategy strategy){
 - this.strategy = strategy;
 - }
 - /**
 - * 策略方法
 - */
 - public void invoke(){
 - strategy.doInvoke();
 - }
 - }
 - public interface Strategy {
 - /**
 - * 策略方法
 - */
 - public void doInvoke();
 - }
 - public class StrategyA implements Strategy {
 - @Override
 - public void doInvoke() {
 - System.out.println("InvokeA");
 - }
 - }
 - public class StrategyB implements Strategy {
 - @Override
 - public void doInvoke() {
 - System.out.println("InvokeB");
 - }
 - }
 
從上面的講解,你能理解策略模式嗎?你是否有如下的一些疑問?
- 使用策略模式和我直接寫if-else具體的優(yōu)勢(shì)在哪里?
 - if-else不是挺簡單的,為什么要多寫這么多的類?
 - 如何將Strategy給設(shè)置到Context中?
 - 我該如何判斷將哪個(gè)實(shí)現(xiàn)設(shè)置給Context?還是ifelse?!那拆成這么多的類不是脫褲子放屁嗎?
 
將模式放入架構(gòu)中
產(chǎn)生這些疑問的原因,是我們?cè)诠铝⒌目丛O(shè)計(jì)模式,而沒有把設(shè)計(jì)模式放到實(shí)際的場(chǎng)景中。
當(dāng)我們將其放到實(shí)際項(xiàng)目中時(shí),我們實(shí)際是需要一個(gè)客戶端來組裝和調(diào)用這個(gè)設(shè)計(jì)模式的,如下圖所示:

- public class Client {
 - public static void main(String[] args) {
 - Strategy strategy;
 - if("A".equals(args[0])) {
 - strategy = new StrategyA();
 - } else {
 - strategy = new StrategyB();
 - }
 - Context context = new Context(strategy);
 - context.invoke();
 - }
 - }
 
作為比較,這里也給出直接使用ifelse時(shí)的結(jié)構(gòu)和代碼:

- public class Client {
 - public static void main(String[] args) {
 - Context context = new Context(args[0]);
 - context.invoke();
 - }
 - }
 - public class Context {
 - public void invoke(String type) {
 - if("A".equals(type)) {
 - System.out.println("InvokeA");
 - } else if("B".equals(type)) {
 - System.out.println("InvokeB");
 - }
 - }
 - }
 
乍看之下,使用ifelse更加的簡單明了,不過別急,下面我們來對(duì)比一下兩種實(shí)現(xiàn)方式的區(qū)別,來具體看看設(shè)計(jì)模式所帶來的優(yōu)勢(shì)。
邊界不同
首先,使用策略模式使得架構(gòu)的邊界與使用ifelse編碼方式的架構(gòu)的邊界不同。策略模式將代碼分成了三部分,這里稱為:
- 調(diào)用層:將下層的業(yè)務(wù)邏輯組裝起來,形成完整的可執(zhí)行流程
 - 邏輯層:具體的業(yè)務(wù)邏輯流程
 - 實(shí)現(xiàn)層:實(shí)現(xiàn)業(yè)務(wù)邏輯中可替換邏輯的具體實(shí)現(xiàn)
 

而ifelse將代碼分成了兩部分:
- 調(diào)用層:將下層的業(yè)務(wù)邏輯組裝起來,形成完整的可執(zhí)行流程
 - 邏輯層:具體的業(yè)務(wù)邏輯流程及具體邏輯
 

解耦
在ifelse實(shí)現(xiàn)中,「邏輯流程」和「邏輯實(shí)現(xiàn)」是硬編碼在一起的,明顯的緊耦合。而策略模式將「邏輯流程」和「邏輯實(shí)現(xiàn)」拆分開,對(duì)其進(jìn)行了解耦。
解耦后,「邏輯流程」和「邏輯實(shí)現(xiàn)」就可以獨(dú)立的進(jìn)化,而不會(huì)相互影響。
獨(dú)立進(jìn)化
假設(shè)現(xiàn)在要調(diào)整業(yè)務(wù)流程。對(duì)于策略模式來說,需要修改的是「邏輯層」;而對(duì)于ifelse來說,需要修改的也是「邏輯層」。
假設(shè)現(xiàn)在要新增一個(gè)策略。對(duì)于策略模式來說,需要修改的是「實(shí)現(xiàn)層」;而對(duì)于ifelse來說,需要修改的還是「邏輯層」。
在軟件開發(fā)中,有一個(gè)原則叫單一職責(zé)原則,它不僅僅是針對(duì)類或方法的,它也適用于包、模塊甚至子系統(tǒng)。
對(duì)應(yīng)到這里,你會(huì)發(fā)現(xiàn),ifelse的實(shí)現(xiàn)方式違背了單一職責(zé)原則。使用ifelse實(shí)現(xiàn),使得邏輯層的職責(zé)不單一了。當(dāng)業(yè)務(wù)流程需要調(diào)整時(shí),需要調(diào)整邏輯層的代碼;當(dāng)具體的業(yè)務(wù)邏輯實(shí)現(xiàn)需要調(diào)整時(shí),也需要調(diào)整邏輯層。
而策略模式將業(yè)務(wù)流程和具體的業(yè)務(wù)邏輯拆分到了不同的層內(nèi),使得每一層的職責(zé)相對(duì)的單一,也就可以獨(dú)立的進(jìn)化。
對(duì)象聚集
我們重新來觀察一下策略模式的架構(gòu)圖,再對(duì)照上面的調(diào)用代碼,你有沒有發(fā)現(xiàn)缺少了點(diǎn)什么?
在Client中,我們要根據(jù)參數(shù)判定來實(shí)例化了StategyA或StategyB對(duì)象。也就是說,「調(diào)用層」使用了「實(shí)現(xiàn)層」的代碼,實(shí)際調(diào)用邏輯應(yīng)該是這樣的:

可以看到,Client與StategyA和StategyB是強(qiáng)依賴的。這會(huì)導(dǎo)致兩個(gè)問題:
- 對(duì)象分散:如果StategyA或StategyB的實(shí)例化方法需要調(diào)整,所有實(shí)例化代碼都需要進(jìn)行調(diào)整?;蛘呷绻略隽薙tategyC,那么所有將Stategy設(shè)置到Context的相關(guān)代碼都需要調(diào)整。
 - 穩(wěn)定層依賴不穩(wěn)定層:一般情況下,「實(shí)現(xiàn)層」的變動(dòng)頻率較高;而對(duì)于「調(diào)用層」來說,調(diào)用流程確定后,基本就不會(huì)變化了。讓一個(gè)基本不變的層去強(qiáng)依賴一個(gè)頻繁變化的層,顯然是有問題的。
 
我們先來解決「對(duì)象分散」的問題,下一節(jié)來解決「穩(wěn)定層依賴不穩(wěn)定層」的問題!
對(duì)于「對(duì)象分散」的問題來說,創(chuàng)建型的設(shè)計(jì)模式基本能解決這個(gè)問題,對(duì)應(yīng)到這里,可以直接使用工廠方法!

使用了工廠方法后,構(gòu)建代碼被限制在了工廠方法內(nèi)部,當(dāng)策略對(duì)象的構(gòu)造邏輯調(diào)整時(shí),我們只需要調(diào)整對(duì)應(yīng)的工廠方法就可以了。
依賴倒置
現(xiàn)在「調(diào)用層」只和「實(shí)現(xiàn)層」的StategyFactoryImpl有直接的關(guān)系,解決了「對(duì)象分散」的問題。但是即使只依賴一個(gè)類,調(diào)用層依然和實(shí)現(xiàn)層是強(qiáng)依賴關(guān)系。
該如何解決這個(gè)問題呢?我們需要依賴倒置。一般方法是使用接口,例如這里的「邏輯層」和「實(shí)現(xiàn)層」就是通過接口來實(shí)現(xiàn)了依賴倒置:「邏輯層」并不強(qiáng)依賴于「實(shí)現(xiàn)層」的任何一個(gè)類。箭頭方向都是從「實(shí)現(xiàn)層」指向「邏輯層」的,所以稱為依賴倒置

但是對(duì)于「調(diào)用層」來說,此方法并不適用,因?yàn)樗枰獙?shí)例化具體的對(duì)象。那我們?cè)撊绾翁幚砟兀?/p>
相信你已經(jīng)想到了,就是我們一直在用的IOC!通過注入的方式,使得依賴倒置!我們可以直接替換掉工廠方法。

可以看到,通過依賴注入,使得「調(diào)用層」和「實(shí)現(xiàn)層」都依賴于「邏輯層」。由于「邏輯層」也是相對(duì)較穩(wěn)定的,所以「調(diào)用層」也就不會(huì)頻繁的變化,現(xiàn)在需要變化的只有「實(shí)現(xiàn)層」了。
邏輯顯化
最后一個(gè)區(qū)別就是設(shè)計(jì)模式使得邏輯顯化。什么意思呢?
當(dāng)你使用ifelse的時(shí)候,實(shí)際上你需要深入到具體的ifelse代碼,你才能知道它的具體邏輯是什么。
對(duì)于使用設(shè)計(jì)模式的代碼來說,我們回過頭來看上面的架構(gòu)圖,從這張圖你就能看出來對(duì)應(yīng)的邏輯了:
- 由StrategyFactory實(shí)例化所有Strategy的實(shí)現(xiàn)
 - Client通過StrategyFactory獲取Strategy實(shí)例,并將其設(shè)置到Context中
 - 由Context委托給具體的Strategy來執(zhí)行具體的邏輯
 
至于具體的Strategy邏輯是什么樣子的,你可以通過類名或方法名來將其顯化出來!
總結(jié)
本文通過將使用設(shè)計(jì)模式的代碼和不使用設(shè)計(jì)模式的代碼分別放到架構(gòu)中,對(duì)比設(shè)計(jì)模式對(duì)架構(gòu)所產(chǎn)生的影響:
- 劃分邊界
 - 解耦
 - 獨(dú)立進(jìn)化
 - 對(duì)象聚集
 - 依賴倒置
 - 邏輯顯化
 















 
 
 











 
 
 
 