設計模式七大原則—依賴倒置原則
1.基本介紹
1.1.概念
高層模組不能依賴於一個“具體化、細節化”的低層模組,而是通過一個抽象的“規範/標準”建立兩者之間的依賴關係,簡言之就是:不依賴於實現,而是依賴於抽象。這裡“實現”一詞有的地方也稱為“細節”,在編碼中主要體現的是我們根據業務模型具體自定義的普通類,比如:員工類、商品類等。而其中的“抽象”一詞是指定的介面或抽象類。
1.2.高層與低層
下面我們通過傳統的三層架構作為背景來理解“依賴倒置原則”中的高層與低層的含義。
在分層架構中,高層是相對而言的,對於上面三層架構圖中而言最高層是“表示層”,相對於“業務邏輯層”它的高層是“表示層UI”,相對於“資料訪問層”它的高層則是“業務邏輯層”。
低層同樣也是相對而言的,對於上面三層架構圖中而言最低層是“資料訪問層”,相對於“業務邏輯層”它的低層是“資料訪問層”,相對於“表示層”它的低層則是“業務邏輯層”。
那麼簡單來說高層就相對於一個使用者,低層就相當於一個被使用者。
1.3.依賴倒置原則在分層架構中的體現
在早期比較傳統的專案中,三層架構的分層通常都是上圖的形式:表示層直接依賴於一個具體實現、非抽象的業務邏輯層,業務邏輯層對下層的依賴同樣如此。這種分層實際上在依賴上就是違背了“依賴倒置原則”,因為它都是依賴的一些具體的實現,而非抽象。
那麼對於傳統的三層架構,使用了“依賴倒置原則”的思想,就可以發揮其中的優勢,使其易於維護和擴充套件。比如說你負責的某個專案的DAL層是使用SqlServer資料庫,而需求要變更為Mysql資料庫,如果使用了“依賴倒置原則”建立了抽象的規範,那麼你就可以在不影響其他層的情況下,單獨實現抽象規範就可以進行變更,這樣的變更對於“依賴於具體實現”改動是最小的。
1.4.依賴倒置原則體現的緩衝性
例如你寫了一個計算統計的程式,其中某個數值的計算比較複雜,你將演算法封裝成了一個具體實現類,作為比引數變數引入到計算統計的程式。由於第一次寫的演算法不是很理想,你後面會面臨嘗試更多新的演算法,這就意味著每個新演算法的使用,都要去“計算統計程式”中進行協調改動。
如果你在演算法設計之初就通過介面或抽象類建立一個演算法規範/標準,那麼計算統計程式通過介面或抽象類作為引數變數引入演算法物件就能起到一個緩衝性,也可以更好的支援演算法的迭代,利於程式擴充套件和優化。
2.通過程式碼示例說明依賴倒置重要性
下面通過兩種程式碼示例來理解依賴倒置原則以及它的重要性,第一種介紹的是“未使用”依賴倒置原則的程式碼,並描述在“未使用”依賴倒置原則會出現怎樣的利害關係。第二種則是針對第一種出現的問題使用依賴倒置原則對其進行改良,從而體現出依賴倒置原則的優勢。
2.1.示例一(依賴實現)
1 //武器劍 2 class Sword 3 { 4 //攻擊的方法 5 public void Attack() 6 { 7 Console.WriteLine("使用劍進行刺殺"); 8 } 9 } 10 11 //遊戲角色類 12 class GameRole 13 { 14 //使用武器 15 public void UseWeapon(Sword sword) 16 { 17 sword.Attack(); 18 } 19 } 20 21 internal class Program 22 { 23 static void Main(string[] args) 24 { 25 GameRole gameRole = new GameRole(); 26 gameRole.UseWeapon(new Sword()); 27 } 28 }
上面程式碼中分別定義了兩個類,一個是遊戲角色類另一個是劍類,遊戲角色類其中有一個“使用武器”的方法,該方法需要傳入一個劍類的物件,並呼叫劍類中的攻擊方法。
這個示例使用了我們日常玩電子遊戲來作為背景,玩過遊戲的朋友應該知道,遊戲的更新迭代也是習以為常的事情。如果遊戲中使用了以上的程式碼,並且遊戲中的角色使用的武器經常會發生一些改變,那麼程式碼中的遊戲角色類要針對每一個新武器新增對應的使用方法。如果無法窮舉使用的武器,那我們將會寫N個使用新武器的方法。
方式一這種形式的程式碼,實際上就是在依賴具體的實現,體現在使用武器的方法上,方法總是必須要傳入一個具體的武器類(劍、斧頭等),而不是依賴一個抽象的標準/規範。我們可以通過示例很直觀的看出來,這種依賴具體實現的程式碼並不適應需求變化。接下來我們則通過使用依賴倒置原則來消除這種“被病”。
2.2.示例二(依賴抽象)
1 //武器介面 2 public interface IWeapon 3 { 4 void Attack(); 5 } 6 7 //武器劍 8 class Sword : IWeapon 9 { 10 public void Attack() 11 { 12 Console.WriteLine("使用劍進行刺殺"); 13 } 14 } 15 16 //斧頭 17 class Axe : IWeapon 18 { 19 public void Attack() 20 { 21 Console.WriteLine("使用斧頭進行劈砍"); 22 } 23 } 24 25 //遊戲角色類 26 class GameRole 27 { 28 //使用武器 29 public void UseWeapon(IWeapon weapon) 30 { 31 weapon.Attack(); 32 } 33 34 } 35 36 37 internal class Program 38 { 39 static void Main(string[] args) 40 { 41 GameRole gameRole = new GameRole(); 42 gameRole.UseWeapon(new Sword()); 43 gameRole.UseWeapon(new Axe()); 44 } 45 }
示例二基於依賴倒置原則對示例一進行改良,將多種武器抽象成了一個介面,並且根據武器種類現狀建立實現了“武器介面”的實現類(劍、斧頭),遊戲角色類將多個使用不同武器的方法縮減成一個方法,該方法接收一個“武器介面”型別的例項物件(也就是實現了該介面的類)作為引數。
遊戲角色類從原本依賴的具體武器轉變成依賴一個抽象武器(依賴具體類轉變為依賴介面),從而體現出了從依賴實現到依賴抽象的轉變。那麼此時不管遊戲對角色使用的武器進行新增或刪減,“遊戲角色類”不用做大量的改動變化,而是將這個變化單獨抽離了出去,作為了一個獨立的介面。而這個介面不在單單隻對遊戲角色類服務,還可以用於其他的類(妖怪類、魔獸類等),從而降低了程式碼的變化性和耦合性,且提高程式碼的複用性。
2.3.示例總結
基於上面的兩個示例的分析對比,我們就應該更加謹記“依賴倒置原則”在編碼中的重要性,如果我們的程式碼只是使用面向具體實現程式設計,那麼程式結構上並不能更好的適應變化,更好的擴充套件、更好的維護,大量的冗餘程式碼會導致程式越來越臃腫。
3.總結
一開始學習“依賴倒置原則”的時候,在明白其中的含義和作用後,往往都在糾結這個“倒置”到底是什麼意思,它倒置的是個啥,如果感覺不搞懂總感覺差點意思。經過反覆的思考,我個人的理解是:將原本高層對低層依賴具體細節,顛倒反義,轉為依賴於抽象。
依賴倒置原則在面向物件程式設計和框架的設計上都廣泛運用,它其中主要的核心思想就是:面向介面程式設計,而不是面向具體實現程式設計。使用介面或抽象類的目的是制定好規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。