C#軟件設計——小話設計模式原則之:接口隔離原則ISP


前言:有朋友問我,設計模式原則這些東西在園子里都討論爛了,一搜一大把的資料,還花這么大力氣去整這個干嘛。博主不得不承認,園子里確實很多這方面的文章,並且不乏出色的博文。博主的想法是,既然要完善知識體系,就不能半途而廢。今天就來看看設計模式原則的另一個:接口隔離原則。

軟件設計原則系列文章索引

一、原理介紹

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里面。理論上來說,這貌似沒錯。我們來看看如何設計。

   public interface IOrder
    {
        //訂單申請操作
        void Apply(object order);

        //訂單審核操作
        void Approve(object order);

        //訂單結束操作
        void End(object order);

    }

剛開始只有銷售訂單,我們只需要實現這個接口就好了。

    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();
        }
    }

后來,隨着系統的不斷擴展,我們需要加入生產訂單,生產訂單也有一些單獨的接口方法,比如:排產、凍結、導入、導出等操作。於是我們向訂單的接口里面繼續加入這些方法。於是訂單的接口變成這樣:

    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(); }

我們生產訂單的實現類如下

    //生產訂單實現類
    public class ProduceOrder : IOrder
    {
        /// <summary>
        /// 對於生產訂單來說無用的接口
        /// </summary>
        /// <param name="order"></param>
        public void Apply(object order)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 對於生產訂單來說無用的接口
        /// </summary>
        /// <param name="order"></param>
        public void Approve(object order)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 對於生產訂單來說無用的接口
        /// </summary>
        /// <param name="order"></param>
        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("訂單導出"); } }

 銷售訂單的實現類也要相應做修改

    //銷售訂單實現類
    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。於是乎接口變成這樣:

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接口分成兩個接口來設計

    //刪除訂單接口
    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);
    }

對應的實現類只需要實現自己需要的接口即可

    //生產訂單實現類
    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("訂單結束");
        }
    }

這樣設計就能完美解決上述“胖接口”導致的問題,如果需要增加訂單操作,只需要在對應的接口和實現類上面修改即可,這樣就不存在依賴不需要接口的情況。通過這種設計,降低了單個接口的復雜度,使得接口的“內聚性”更高,“耦合性”更低。由此可以看出接口隔離原則的必要性。

三、總結

通過以上訂單功能的優化,我們看到了接口隔離原則的必要性,當然,關於接口隔離原則和單一職責原則的細節我們也不必過多追究,不管何種原則,能解決我們的設計問題就是好的原則、我們必須遵守的原則。歡迎園友拍磚斧正。如果園友們覺得本文對你有幫助,請幫忙推薦,博主將繼續努力~~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM