【面向對象設計原則】之接口隔離原則(ISP)

接口隔離原則(Interface  Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不該該依賴那些它不須要的接口。html

從接口隔離原則的定義能夠看出,他彷佛跟SRP有許多類似之處。 是的其實ISP和SRP都是強調職責的單一性, 接口隔離原則告訴咱們在定義接口的時候要根據職責定義「較小」的接口,不要定義「高大全」的接口。也就是說接口要儘量的職責單一,這樣更容易複用,暴露給客戶端的方法更具備「針對性」, 好比定義一個接口包括一堆訪問數據庫的方法, 有包括一堆訪問網絡的方法,還包括一些權限認證的方法。 把這麼一攤子風牛馬不相及的方法封裝到一個接口裏面,顯然是不合適的, 若是客戶程序只想用到數據訪問的一些功能,可是調用接口的時候你把訪問網絡的方法和權限認證的方法暴露給客戶,這使得客戶程序感到「疑惑」,那麼這個接口就不ISP,它很顯然的構成了接口污染。sql

注意: 這裏所說的接口是廣義上的接口,他是一組契約, 是提供給程序交互的一組約定,並不是各類語言interface 關鍵字定義的一組方法的結集合。可是這裏所說的接口能夠用各類語言的關鍵字interface 來定義,固然也能夠用抽象類,類等等來定義。數據庫

假設有個客戶提出了軟件系統的需求:微信

1. 用戶可使用第三方QQ,微信,微博登陸到系統。網絡

2.系統中包括人員管理人員管理。數據庫設計

3.訪問第三方的API獲取一些數據。ide

好了拿到這個需求後首先通過分析,簡單的原型設計,數據庫設計以後開始編寫代碼了。 一般第一步定義接口。很快接口就定義出來了以下:url

    public interface IObject
    {
        void Connection(string connectionString);
        SqlDataReader ExcuteSql(string sql);
        string LoginWithQQ(string token);
        string LoginWithWeibo(string token);
        string LoginWithWeiXin(string token);
        string GetDataFromAPI(string url, string token);
    }

這個看起來還不錯,接口已經定義了,寫個具體類繼承一下這個接口並實現全部的方法,如今就能夠實現業務,寫界面了。 等過了幾天客戶說 在給我加上支付寶登陸。那好再加一個支付寶登陸接口,代碼如今長這樣子:spa

    public interface IObject
    {
        void Connection(string connectionString);
        SqlDataReader ExcuteSql(string sql);
        string LoginWithQQ(string token);
        string LoginWithWeibo(string token);
        string LoginWithWeiXin(string token);
        string GetDataFromAPI(string url, string token);
        string LoginWithAlipay(string token);
    }

再在實現類中實現一下LoginWithAlipay方法 就行了。設計

時間在推移,一天客戶說再給我加個百度登陸,好吧套路有了加一個就是了,有啥了不得。 時間依舊。。。 客戶說加個 facebook 登陸, 。。。加個 Linkedin。。。, 尼瑪 沒完沒了了, 如今接口已經變成這樣子了:

    public interface IObject
    {
        void Connection(string connectionString);
        SqlDataReader ExcuteSql(string sql);
        string LoginWithQQ(string token);
        string LoginWithWeibo(string token);
        string LoginWithWeiXin(string token);
        string GetDataFromAPI(string url, string token);
        string LoginWithAlipay(string token);
        string LoginWithTwitter(string token);
        string LoginWithFaceBook(string token);
        string LoginWithRenRen(string token);
        string LoginWithBaidu(string token);
        string LoginWithDropbox(string token);
        string LoginWithGithub(string token);
        //這裏省略10000字       
stringLoginWithLinkedin(string token);
    }

有一天這個接口本身都不想看了,太多方法了,更況且實現類中的代碼都七八千行了。

因而決定重構, 如今回頭看看這個接口早就應該重構了,甚至一開始定義的時候就應該拆分,接口的名字都不知道怎麼命名(通常在寫代碼的時候類,接口,方法的名字不知道怎麼命名的時候就是該重構的時候了)居然起了IObject這麼奇葩的名字,這個設計顯然是爛到家了, 他幾乎違背了咱們講過的全部設計原則, 必須到了要重構的時候了。

來吧,重構吧,通過分析第一步先根據功能來劃分將IObject接口拆分紅三個「小」接口:

1.數據庫操做相關的抽取到一個接口中(IDatabaseProvider)。

2.第三方API調用相關的方法抽取到一個接口中(IThirdpartyAPIProvider)。

3.第三方登錄相關的方法抽取到一個接口中(IThirdpartyAuthenticationProvider)。

如今代碼變成這個樣子:

    public interface IDatabaseProvider
    {
        SqlDataReader ExcuteSql(string sql);
        string LoginWithQQ(string token);
    }

    public interface IThirdpartyAPIProvider
    {
        string Get(string url, string token);
    }

    public interface IThirdpartyAuthenticationProvider
    {
        string LoginWithQQ(string token);
        string LoginWithWeibo(string token);
        string LoginWithWeiXin(string token);
        string LoginWithAlipay(string token);
        string LoginWithTwitter(string token);
        string LoginWithFaceBook(string token);
        string LoginWithRenRen(string token);
        string LoginWithBaidu(string token);
        string LoginWithDropbox(string token);
        string LoginWithGithub(string token);
        //這裏省略10000字
        string LoginWithLinkedin(string token);
    }

這下看起來好多了, 可是IThirdpartyAuthenticationProvider 代碼還不少,還很醜陋,有沒有辦法再進一步重構呢? 答案是確定。 第二步 咱們能夠將第三方登陸的接口中的LogigWithxxx方法提到一個單獨的接口中,其餘具體站點的接口再繼承這個接口,代碼以下:

    public interface IThirdpartyAuthenticationProvider
    {
        string Login(string token);
    }

    public interface IQQAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IWeiboAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IWeiXinAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IAlipayAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface ITwitterAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IFaceBookAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IRenRenAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IBaiduAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    public interface IDropboxAuthenticationProvider : IThirdpartyAuthenticationProvider { }
    public interface IGitHubAuthenticationProvider:IThirdpartyAuthenticationProvider{}
    //這裏省略10000字
    public interface ILinkedinAuthenticationProvider : IThirdpartyAuthenticationProvider { }

這這下就好多了。 咱們分析一下重構後的代碼有什麼好處:

1. 接口的職責更單一了,調用目標更清晰了,每個接口就專門作一件事情。符合SRP了。

2. 在操做數據庫的時候不會在IDatabase接口中調到其它的第三方API調用和第三方登陸認證相關的方法,每個幾口更專一了。符合ISP了。

3.在添加新的第三方登陸的時候不須要在修改原來的實現 類了,核心業務邏輯只須要加一個接口和接口的實現類就能夠了。符合OCP了。

4. 提高了代碼的穩定性,可維護性和可擴展性。

固然任何事情都具備兩面性,若是將一件好事作到極端有可能就會走向反面, 比方說定義一個User實體的接口:

public interface IIdProperty { int Id { get; set; } }
public interface IFirstNameProperty { string FirstName { get; set; } }
public interface ILastNameProperty { string LastName { get; set; } }
public interface IAgeProperty { int Age { get; set; } }
public interface IBirthdayProperty { DateTime Birthday { get; set; } }

public class User:IIdProperty,IFirstNameProperty,ILastNameProperty,IAgeProperty,IBirthdayProperty
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public DateTime Birthday { get; set; }
}

把每一個屬性都定義成一個接口那就不可取了,也是沒有意義的,反而給維護或擴展帶來沒必要要的麻煩,這就是使用接口時要注意的地方:

在使用接口時要注意控制接口的粒度,接口定義的粒度不能太細,也不能太粗。 接口粒度太細,系統中就會出現接口氾濫,接口和實現類急劇膨脹,反而不易維護;接口粒度太粗,就會違背ISP,系統的靈活性就會下降,不易維護和擴展。

關聯閱讀:

【面向對象設計原則】之原則概述

【面向對象設計原則】之單一職責原則(SRP)

【面向對象設計原則】之開閉原則(OCP)

【面向對象設計原則】之里氏替換原則(LSP)

【面向對象設計原則】之依賴倒置原則(DIP)

相關文章
相關標籤/搜索