大部分講解設計模式的書或者文章,都是從代碼層面來說解設計模式,看的時候都懂,可是到真正用的時候,仍是理不清、想不明。算法
本文嘗試從架構層面來聊一聊設計模式。經過將使用設計模式的代碼和不使用設計模式的代碼分別放到架構中,來看看設計模式對架構所產生的影響。設計模式
通常講解設計模式的套路是:markdown
以策略模式爲例:數據結構
意圖:定義一系列的算法,把它們一個個封裝起來, 而且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。架構
適用性:ide
類結構:函數
示例代碼:oop
public class Context {
//持有一個具體策略的對象
private Strategy strategy;
/**
* 構造函數,傳入一個具體策略對象
* @param strategy 具體策略對象
*/
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");
}
}
複製代碼
從上面的講解,你能理解策略模式嗎?你是否有以下的一些疑問?this
產生這些疑問的緣由,是咱們在孤立的看設計模式,而沒有把設計模式放到實際的場景中。編碼
當咱們將其放到實際項目中時,咱們實際是須要一個客戶端來組裝和調用這個設計模式的,以下圖所示:
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時的結構和代碼:
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更加的簡單明瞭,不過別急,下面咱們來對比一下兩種實現方式的區別,來具體看看設計模式所帶來的優點。
首先,使用策略模式使得架構的邊界與使用ifelse編碼方式的架構的邊界不一樣。策略模式將代碼分紅了三部分,這裏稱爲:
而ifelse將代碼分紅了兩部分:
在ifelse實現中,「邏輯流程」和「邏輯實現」是硬編碼在一塊兒的,明顯的緊耦合。而策略模式將「邏輯流程」和「邏輯實現」拆分開,對其進行了解耦。
解耦後,「邏輯流程」和「邏輯實現」就能夠獨立的進化,而不會相互影響。
假設如今要調整業務流程。對於策略模式來講,須要修改的是「邏輯層」;而對於ifelse來講,須要修改的也是「邏輯層」。
假設如今要新增一個策略。對於策略模式來講,須要修改的是「實現層」;而對於ifelse來講,須要修改的仍是「邏輯層」。
在軟件開發中,有一個原則叫單一職責原則,它不只僅是針對類或方法的,它也適用於包、模塊甚至子系統。
對應到這裏,你會發現,ifelse的實現方式違背了單一職責原則。使用ifelse實現,使得邏輯層的職責不單一了。當業務流程須要調整時,須要調整邏輯層的代碼;當具體的業務邏輯實現須要調整時,也須要調整邏輯層。
而策略模式將業務流程和具體的業務邏輯拆分到了不一樣的層內,使得每一層的職責相對的單一,也就能夠獨立的進化。
咱們從新來觀察一下策略模式的架構圖,再對照上面的調用代碼,你有沒有發現缺乏了點什麼?
在Client中,咱們要根據參數斷定來實例化了StategyA或StategyB對象。也就是說,「調用層」使用了「實現層」的代碼,實際調用邏輯應該是這樣的:
能夠看到,Client與StategyA和StategyB是強依賴的。這會致使兩個問題:
咱們先來解決「對象分散」的問題,下一節來解決「穩定層依賴不穩定層」的問題!
對於「對象分散」的問題來講,建立型的設計模式基本能解決這個問題,對應到這裏,能夠直接使用工廠方法!
使用了工廠方法後,構建代碼被限制在了工廠方法內部,當策略對象的構造邏輯調整時,咱們只須要調整對應的工廠方法就能夠了。
如今「調用層」只和「實現層」的StategyFactoryImpl有直接的關係,解決了「對象分散」的問題。可是即便只依賴一個類,調用層依然和實現層是強依賴關係。
該如何解決這個問題呢?咱們須要依賴倒置。通常方法是使用接口,例如這裏的「邏輯層」和「實現層」就是經過接口來實現了依賴倒置:「邏輯層」並不強依賴於「實現層」的任何一個類。箭頭方向都是從「實現層」指向「邏輯層」的,因此稱爲依賴倒置
可是對於「調用層」來講,此方法並不適用,由於它須要實例化具體的對象。那咱們該如何處理呢?
相信你已經想到了,就是咱們一直在用的IOC!經過注入的方式,使得依賴倒置!咱們能夠直接替換掉工廠方法。
能夠看到,經過依賴注入,使得「調用層」和「實現層」都依賴於「邏輯層」。因爲「邏輯層」也是相對較穩定的,因此「調用層」也就不會頻繁的變化,如今須要變化的只有「實現層」了。
最後一個區別就是設計模式使得邏輯顯化。什麼意思呢?
當你使用ifelse的時候,實際上你須要深刻到具體的ifelse代碼,你才能知道它的具體邏輯是什麼。
對於使用設計模式的代碼來講,咱們回過頭來看上面的架構圖,從這張圖你就能看出來對應的邏輯了:
至於具體的Strategy邏輯是什麼樣子的,你能夠經過類名或方法名來將其顯化出來!
本文經過將使用設計模式的代碼和不使用設計模式的代碼分別放到架構中,對比設計模式對架構所產生的影響: