換一種角度:從架構(gòu)層面來(lái)看設(shè)計(jì)模式
大部分講解設(shè)計(jì)模式的書或者文章,都是從代碼層面來(lái)講解設(shè)計(jì)模式,看的時(shí)候都懂,但是到真正用的時(shí)候,還是理不清、想不明。
本文嘗試從架構(gòu)層面來(lái)聊一聊設(shè)計(jì)模式。通過將使用設(shè)計(jì)模式的代碼和不使用設(shè)計(jì)模式的代碼分別放到架構(gòu)中,來(lái)看看設(shè)計(jì)模式對(duì)架構(gòu)所產(chǎn)生的影響。
一般模式講解套路
一般講解設(shè)計(jì)模式的套路是:
- 說(shuō)明模式的意圖
- 說(shuō)明模式的適用場(chǎng)景
- 給出模式的類結(jié)構(gòu)
- 給出對(duì)應(yīng)的代碼示例
以策略模式為例:
意圖:定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái), 并且使它們可相互替換。本模式使得算法可獨(dú)立于使用它的客戶而變化。
適用性:
- 許多相關(guān)的類僅僅是行為有異。「策略」提供了一種用多個(gè)行為中的一個(gè)行為來(lái)配置一個(gè)類的方法。
- 需要使用一個(gè)算法的不同變體。例如,你可能會(huì)定義一些反映不同的空間/時(shí)間權(quán)衡的算法。當(dāng)這些變體實(shí)現(xiàn)為一個(gè)算法的類層次時(shí),可以使用策略模式。
- 算法使用客戶不應(yīng)該知道的數(shù)據(jù)。可使用策略模式以避免暴露復(fù)雜的、與算法相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
- 一個(gè)類定義了多種行為, 并且這些行為在這個(gè)類的操作中以多個(gè)條件語(yǔ)句的形式出現(xiàn)。將相關(guān)的條件分支移入它們各自的Strategy類中以代替這些條件語(yǔ)句。
類結(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不是挺簡(jiǎn)單的,為什么要多寫這么多的類?
- 如何將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è)客戶端來(lái)組裝和調(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更加的簡(jiǎn)單明了,不過別急,下面我們來(lái)對(duì)比一下兩種實(shí)現(xiàn)方式的區(qū)別,來(lái)具體看看設(shè)計(jì)模式所帶來(lái)的優(yōu)勢(shì)。
邊界不同
首先,使用策略模式使得架構(gòu)的邊界與使用ifelse編碼方式的架構(gòu)的邊界不同。策略模式將代碼分成了三部分,這里稱為:
- 調(diào)用層:將下層的業(yè)務(wù)邏輯組裝起來(lái),形成完整的可執(zhí)行流程
- 邏輯層:具體的業(yè)務(wù)邏輯流程
- 實(shí)現(xiàn)層:實(shí)現(xiàn)業(yè)務(wù)邏輯中可替換邏輯的具體實(shí)現(xiàn)

而ifelse將代碼分成了兩部分:
- 調(diào)用層:將下層的業(yè)務(wù)邏輯組裝起來(lái),形成完整的可執(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ì)于策略模式來(lái)說(shuō),需要修改的是「邏輯層」;而對(duì)于ifelse來(lái)說(shuō),需要修改的也是「邏輯層」。
假設(shè)現(xiàn)在要新增一個(gè)策略。對(duì)于策略模式來(lái)說(shuō),需要修改的是「實(shí)現(xiàn)層」;而對(duì)于ifelse來(lái)說(shuō),需要修改的還是「邏輯層」。
在軟件開發(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ì)象聚集
我們重新來(lái)觀察一下策略模式的架構(gòu)圖,再對(duì)照上面的調(diào)用代碼,你有沒有發(fā)現(xiàn)缺少了點(diǎn)什么?
在Client中,我們要根據(jù)參數(shù)判定來(lái)實(shí)例化了StategyA或StategyB對(duì)象。也就是說(shuō),「調(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)用層」來(lái)說(shuō),調(diào)用流程確定后,基本就不會(huì)變化了。讓一個(gè)基本不變的層去強(qiáng)依賴一個(gè)頻繁變化的層,顯然是有問題的。
我們先來(lái)解決「對(duì)象分散」的問題,下一節(jié)來(lái)解決「穩(wěn)定層依賴不穩(wěn)定層」的問題!
對(duì)于「對(duì)象分散」的問題來(lái)說(shuō),創(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)層」就是通過接口來(lái)實(shí)現(xiàn)了依賴倒置:「邏輯層」并不強(qiáng)依賴于「實(shí)現(xiàn)層」的任何一個(gè)類。箭頭方向都是從「實(shí)現(xiàn)層」指向「邏輯層」的,所以稱為依賴倒置

但是對(duì)于「調(diào)用層」來(lái)說(shuō),此方法并不適用,因?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ì)模式的代碼來(lái)說(shuō),我們回過頭來(lái)看上面的架構(gòu)圖,從這張圖你就能看出來(lái)對(duì)應(yīng)的邏輯了:
- 由StrategyFactory實(shí)例化所有Strategy的實(shí)現(xiàn)
- Client通過StrategyFactory獲取Strategy實(shí)例,并將其設(shè)置到Context中
- 由Context委托給具體的Strategy來(lái)執(zhí)行具體的邏輯
至于具體的Strategy邏輯是什么樣子的,你可以通過類名或方法名來(lái)將其顯化出來(lái)!
總結(jié)
本文通過將使用設(shè)計(jì)模式的代碼和不使用設(shè)計(jì)模式的代碼分別放到架構(gòu)中,對(duì)比設(shè)計(jì)模式對(duì)架構(gòu)所產(chǎn)生的影響:
- 劃分邊界
- 解耦
- 獨(dú)立進(jìn)化
- 對(duì)象聚集
- 依賴倒置
- 邏輯顯化






























