前言:有朋友問我,設計模式原則這些東西在園子裡都討論爛了,一搜一大把的資料,還花這麼大力氣去整這個幹嘛。博主不得不承認,園子裡確實很多這方面的文章,並且不乏出色的博文。博主的想法是,既然要完善知識體系,就不能半途而廢。今天就來看看設計模式原則的另一個:介面隔離原則。
一、原理介紹
1、官方定義
介面隔離原則,英文縮寫ISP,全稱Interface Segregation Principle。
原始定義:Clients should not be forced to depend upon interfaces that they don’t use,還有一種定義是The dependency of one class to another one should depend on the smallest possible interface。
官方翻譯:其一是不應該強行要求客戶端依賴於它們不用的介面;其二是類之間的依賴應該建立在最小的介面上面。簡單點說,客戶端需要什麼功能,就提供什麼介面,對於客戶端不需要的介面不應該強行要求其依賴;類之間的依賴應該建立在最小的介面上面,這裡最小的粒度取決於單一職責原則的劃分。
2、自己理解
2.1、原理解釋
- 不應該強行要求客戶端依賴於它們不用的介面。語句很好理解,即客戶端需要什麼介面,就依賴什麼介面,不需要的就不依賴。那麼我們反過來說,如果客戶端依賴了它們不需要的介面,那麼這些客戶端程式就面臨不需要的介面變更引起的客戶端變更的風險,這樣就會增加客戶端和介面之間的耦合程度,顯然與“高內聚、低耦合”的思想相矛盾。
- 類之間的依賴應該建立在最小的介面上面。何為最小的介面,即能夠滿足專案需求的相似功能作為一個介面,這樣設計主要就是為了“高內聚”。那麼我們如何設計最小的介面呢?那就要說說粒度的劃分了,粒度細化的程度取決於我們上一章講的的單一職責原則裡面介面劃分的粒度。從這一點來說,介面隔離和單一職責兩個原則有一定的相似性。
2.2、介面隔離原則和單一職責原則
從功能上來看,介面隔離和單一職責兩個原則具有一定的相似性。其實如果我們仔細想想還是有區別的。
(1)從原則約束的側重點來說,介面隔離原則更關注的是介面依賴程度的隔離,更加關注介面的“高內聚”;而單一職責原則更加註重的是介面職責的劃分。
(2)從介面的細化程度來說,單一職責原則對介面的劃分更加精細,而介面隔離原則注重的是相同功能的介面的隔離。介面隔離裡面的最小介面有時可以是多個單一職責的公共介面。
(3)單一職責原則更加偏向對業務的約束,介面隔離原則更加偏向設計架構的約束。這個應該好理解,職責是根據業務功能來劃分的,所以單一原則更加偏向業務;而介面隔離更多是為了“高內聚”,偏向架構的設計。
二、場景示例
下面就以訂單的操作為例來說明下介面隔離的必要性。
1、胖介面
軟體設計最初,我們的想法是相同功能的方法放在同一個介面裡面,如下,所有訂單的操作都放在訂單介面IOrder裡面。理論上來說,這貌似沒錯。我們來看看如何設計。
1 2 3 4 5 6 7 8 9 10 11 12 |
public interface IOrder { //訂單申請操作 void Apply(object order); //訂單稽核操作 void Approve(object order); //訂單結束操作 void End(object order); } |
剛開始只有銷售訂單,我們只需要實現這個介面就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class SaleOrder:IOrder { public void Apply(object order) { throw new NotImplementedException(); } public void Approve(object order) { throw new NotImplementedException(); } public void End(object order) { throw new NotImplementedException(); } } |
後來,隨著系統的不斷擴充套件,我們需要加入生產訂單,生產訂單也有一些單獨的介面方法,比如:排產、凍結、匯入、匯出等操作。於是我們向訂單的介面裡面繼續加入這些方法。於是訂單的介面變成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public interface IOrder { //訂單申請操作 void Apply(object order); //訂單稽核操作 void Approve(object order); //訂單結束操作 void End(object order); //訂單下發操作 void PlantProduct(object order); //訂單凍結操作 void Hold(object order); //訂單刪除操作 void Delete(object order); //訂單匯入操作 void Import(); //訂單匯出操作 void Export(); } |
我們生產訂單的實現類如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
//生產訂單實現類 public class ProduceOrder : IOrder { /// /// 對於生產訂單來說無用的介面 /// /// public void Apply(object order) { throw new NotImplementedException(); } /// /// 對於生產訂單來說無用的介面 /// /// public void Approve(object order) { throw new NotImplementedException(); } /// /// 對於生產訂單來說無用的介面 /// /// public void End(object order) { throw new NotImplementedException(); } public void PlantProduct(object order) { Console.WriteLine("訂單下發排產"); } public void Hold(object order) { Console.WriteLine("訂單凍結"); } public void Delete(object order) { Console.WriteLine("訂單刪除"); } public void Import() { Console.WriteLine("訂單匯入"); } public void Export() { Console.WriteLine("訂單匯出"); } } |
銷售訂單的實現類也要相應做修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
//銷售訂單實現類 public class SaleOrder:IOrder { public void Apply(object order) { Console.WriteLine("訂單申請"); } public void Approve(object order) { Console.WriteLine("訂單稽核處理"); } public void End(object order) { Console.WriteLine("訂單結束"); } #region 對於銷售訂單無用的介面方法 public void PlantProduct(object order) { throw new NotImplementedException(); } public void Hold(object order) { throw new NotImplementedException(); } public void Delete(object order) { throw new NotImplementedException(); } public void Import() { throw new NotImplementedException(); } public void Export() { throw new NotImplementedException(); } #endregion } |
需求做完了,上線正常執行。貌似問題也不大。系統執行一段時間之後,新的需求變更來了,要求生成訂單需要一個訂單撤銷排產的功能,那麼我們的介面是不是就得增加一個訂單撤排的介面方法CancelProduct。於是乎介面變成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public interface IOrder { //訂單申請操作 void Apply(object order); //訂單稽核操作 void Approve(object order); //訂單結束操作 void End(object order); //訂單下發操作 void PlantProduct(object order); //訂單撤排操作 void CancelProduct(object order); //訂單凍結操作 void Hold(object order); //訂單刪除操作 void Delete(object order); //訂單匯入操作 void Import(); //訂單匯出操作 void Export(); } |
這個時候問題就來了,我們的生產訂單隻要實現這個撤銷的介面貌似就OK了,但是我們的銷售訂單呢,本來銷售訂單這一塊我們不想做任何的變更,可是由於我們IOrder介面裡面增加了一個方法,銷售訂單的實現類是不是也必須要實現一個無效的介面方法?這就是我們常說的“胖介面”導致的問題。由於介面過“胖”,每一個實現類依賴了它們不需要的介面,使得層與層之間的耦合度增加,結果導致了不需要的介面發生變化時,實現類也不得不相應的發生改變。這裡就凸顯了我們介面隔離原則的必要性,下面我們就來看看如何通過介面隔離來解決上述問題。
2、介面隔離
我們將IOrder介面分成兩個介面來設計
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
//刪除訂單介面 public interface IProductOrder { //訂單下發操作 void PlantProduct(object order); //訂單撤排操作 void CancelProduct(object order); //訂單凍結操作 void Hold(object order); //訂單刪除操作 void Delete(object order); //訂單匯入操作 void Import(); //訂單匯出操作 void Export(); } //銷售訂單介面 public interface ISaleOrder { //訂單申請操作 void Apply(object order); //訂單稽核操作 void Approve(object order); //訂單結束操作 void End(object order); } |
對應的實現類只需要實現自己需要的介面即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
//生產訂單實現類 public class ProduceOrder : IProductOrder { public void PlantProduct(object order) { Console.WriteLine("訂單下發排產"); } public void CancelProduct(object order) { Console.WriteLine("訂單撤排"); } public void Hold(object order) { Console.WriteLine("訂單凍結"); } public void Delete(object order) { Console.WriteLine("訂單刪除"); } public void Import() { Console.WriteLine("訂單匯入"); } public void Export() { Console.WriteLine("訂單匯出"); } } //銷售訂單實現類 public class SaleOrder : ISaleOrder { public void Apply(object order) { Console.WriteLine("訂單申請"); } public void Approve(object order) { Console.WriteLine("訂單稽核處理"); } public void End(object order) { Console.WriteLine("訂單結束"); } } |
這樣設計就能完美解決上述“胖介面”導致的問題,如果需要增加訂單操作,只需要在對應的介面和實現類上面修改即可,這樣就不存在依賴不需要介面的情況。通過這種設計,降低了單個介面的複雜度,使得介面的“內聚性”更高,“耦合性”更低。由此可以看出介面隔離原則的必要性。
三、總結
通過以上訂單功能的優化,我們看到了介面隔離原則的必要性,當然,關於介面隔離原則和單一職責原則的細節我們也不必過多追究,不管何種原則,能解決我們的設計問題就是好的原則、我們必須遵守的原則。
軟體設計原則系列文章