六大設計原則--開閉原則

定義

software entities like classes, modules and functions should be open for extension but closed for modifications.
一個軟件實體應該對擴展開放,對修改關閉。

什麼是開閉原則

閉原則的定義已經非常明確告訴我們:軟件實體應該對擴展開放,對修改關閉,其含義是說一個軟件實體應該通過擴展來實現變化,而不是通過修改已有的代碼來實現變化。那什麼又是軟件實體呢?軟實體包括以下部分:

項目或軟件產品中按照一個邏輯規則劃分的模塊
抽象或類
方法

我們思考這樣一個問題:一個軟件產品只要在生命期內,都會發生變化,變化既然是一個既定的事實,我們就應該在設計時候儘量適應這些變化,以提高項目的穩定性和靈活性,真正實現“擁抱變化” ,開閉原則告訴我們通過儘量通過擴展軟件實體的行爲來實現變化,而不通過修改來已有的代碼來完成變化,它是爲軟件實體的未來事件而制定的對現行開發設計進行約束的一個原則,我們舉例什麼是開閉原則,以書店銷售書籍爲例,類圖如下:
這裏寫圖片描述

IBook 是定義了數據的三個屬性:名稱、價格和作者,小說類 NovelBook 是一個具體的實現類,所有小說書籍的總稱,BookStore 指的是書店,我們先來看 IBook接口:

public interface IBook { 
    //書籍有名稱 
    public String getName(); 

    //書籍有售價 
    public int getPrice(); 

    //書籍有作者 
    public String getAuthor(); 
} 

小說書籍的源代碼如下:

public class NovelBook implements IBook { 
    //書籍名稱 
    private String name; 

    //書籍的價格 
    private int price; 

    //書籍的作者 
    private String author; 

    //通過構造函數傳遞書籍數據 
    public NovelBook(String _name,int _price,String _author){ 
        this.name = _name; 
        this.price = _price; 
        this.author = _author; 
    } 

    //獲得作者是誰 
    public String getAuthor() { 
        return this.author; 
    } 

    //書籍叫什麼名字 
    public String getName() { 
        return this.name; 
    } 

    //獲得書籍的價格 
    public int getPrice() {
        return this.price; 
    } 

} 

然後我們看書店是怎麼銷售書籍的:

public class BookStore { 
    private final static ArrayList<IBook> bookList = new ArrayList<IBook>(); 

    //靜態模塊初始化,項目中一般是從持久層初始化產生 
    static{ 
        bookList.add(new NovelBook("天龍八部",3200,"金庸")); 
        bookList.add(new NovelBook("巴黎聖母院",5600,"雨果")); 
        bookList.add(new NovelBook("悲慘世界",3500,"雨果")); 
        bookList.add(new NovelBook("金瓶梅",4300,"蘭陵笑笑生")); 
    } 

    //模擬書店買書 
    public static void main(String[] args) { 
        NumberFormat formatter = NumberFormat.getCurrencyInstance(); 
        formatter.setMaximumFractionDigits(2); 
        System.out.println("------------書店買出去的書籍記錄如下:---------------------"); 
        for(IBook book:bookList){ 
            System.out.println("書籍名稱:" + book.getName()+"\t書籍作者:" + 
            book.getAuthor()+  "\t書籍價格:"  +  formatter.format(book.getPrice()/100.0)+"元"); 
        } 
    } 
} 

注意,我們在 BookStore 中聲明瞭一個靜態模塊,實現了數據的初始化,這部分應該是從持久層產生的,由持久層工具進行管理。運行結果如下:

------------書店買出去的書籍記錄如下:--------------------- 
書籍名稱:天龍八部  書籍作者:金庸  書籍價格:¥32.00元 
書籍名稱:巴黎聖母院 書籍作者:雨果  書籍價格:¥56.00元 
書籍名稱:悲慘世界  書籍作者:雨果  書籍價格:¥35.00元 
書籍名稱:金瓶梅 書籍作者:蘭陵笑笑生 書籍價格:¥43.00元 

項目投產了,書籍正常銷售出去,書店也盈利了。從 2008年開始,全球經濟都開始下滑,對零售業影響還是比較大,書店爲了生存開始打折銷售:所有 40 元以上的書籍 9折銷售,其他的 8 折銷售。對已經投產的項目來說,這就是一個變化,我們來看看這樣的一個需求變化,我們該怎麼去應對,有三種方法可以解決這個問題:
修改接口。 在 IBook 上新增加一個方法 getOffPrice(), 專門進行打折處理, 所有的實現類實現該方法。但是這樣修改的後果就是實現類 NovelBook 要修改,BookStore 中的main方法也修改, 同時 IBook作爲接口應該是穩定且可靠的,不應該經常發生變化,否則接口做爲契約的作用就失去了效能,——因此,該方案否定。
修改實現類。修改 NovelBook 類中的方法,直接在 getPrice()中實現打折處理,好辦法,我相信大家在項目中經常使用的就是這樣辦法,通過 class 文件替換的方式可以完成部分業務(或是缺陷修復)變化,該方法在項目有明確的章程(團隊內約束)或優良的架構設計時,是一個非常優秀的方法,但是該方法還是有缺陷的,例如採購書籍人員也是要看價格的,由於該方法已經實現了打折處理價格,因此採購人員看到的也是打折後的價格,這就產生了信息的矇蔽效果,導致信息不對稱而出現決策失誤的情況。——因此,該方案也不是一個最優的方案。
通過擴展實現變化。增加一個子類 OffNovelBook,覆寫 getPrice 方法,高層次的模塊(也就是 static靜態模塊區)通過 OffNovelBook 類產生新的對象,完成對業務變化開發任務。——好辦法,修改也少,風險也小,我們來看類圖:
這裏寫圖片描述
OffNovelBook 類繼承了NovelBook,並覆寫了 getPrice 方法,不修改原有的代碼。我們來看新增加的子類 OffNovelBook:

public class OffNovelBook extends NovelBook { 
    public OffNovelBook(String _name,int _price,String _author){ 
        super(_name,_price,_author); 
    } 

    //覆寫銷售價格 
    @Override 
    public int getPrice(){ 
        //原價 
        int selfPrice = super.getPrice(); 
        int offPrice=0; 
        if(selfPrice>4000){  //原價大於40元,則打9折 
            offPrice = selfPrice * 90 /100; 
        }else{ 
            offPrice = selfPrice * 80 /100; 
        } 
        return offPrice; 
    } 
}

很簡單,僅僅覆寫了 getPrice 方法,通過擴展完成了新增加的業務。然後我們來看 BookStore 類的修改:

public class BookStore { 
    private final static ArrayList<IBook> bookList = new ArrayList<IBook>(); 

    //靜態模塊初始化,項目中一般是從持久層初始化產生 
    static{ 
        bookList.add(new OffNovelBook("天龍八部",3200,"金庸")); 
        bookList.add(new OffNovelBook("巴黎聖母院",5600,"雨果")); 
        bookList.add(new OffNovelBook("悲慘世界",3500,"雨果")); 
        bookList.add(new OffNovelBook("金瓶梅",4300,"蘭陵笑笑生")); 
    } 

    //模擬書店買書 
    public static void main(String[] args) { 
        NumberFormat formatter = NumberFormat.getCurrencyInstance(); 
        formatter.setMaximumFractionDigits(2); 
        System.out.println("------------書店買出去的書籍記錄如下:---------------------"); 
        for(IBook book:bookList){ 
            System.out.println("書籍名稱:" + book.getName()+"\t書籍作者:" + 
            book.getAuthor()+  "\t書籍價格:"  +  formatter.format(book.getPrice()/100.0)+"元"); 
        } 
    } 
} 

我們只修改了靜態模塊初始化部分,其他的部分沒有任何改動,看運行結果:

------------書店買出去的書籍記錄如下:--------------------- 
書籍名稱:天龍八部  書籍作者:金庸  書籍價格:¥25.60元 
書籍名稱:巴黎聖母院 書籍作者:雨果  書籍價格:¥50.40元 
書籍名稱:悲慘世界  書籍作者:雨果  書籍價格:¥28.00元 
書籍名稱:金瓶梅 書籍作者:蘭陵笑笑生 書籍價格:¥38.70元 

OK,搞定,打折銷售開發完成了。看到這裏,各位可能有想法了:增加了一個 OffNoveBook 類後,你的業務邏輯還是修改了,你修改了 static 靜態模塊區域,這部分確實修改了,該部分屬於高層次的模塊,是由持久層產生的,在業務規則改變的情況下高層模塊必須有部分改變以適應新業務,改變時儘量的少,壓制變化風險的擴散。注意:開閉原則說是對擴展開放,對修改關閉,並不意味着不做任何的修改,我們可以把變化歸納爲以下幾個類型:

邏輯變化。只變化一個邏輯,而不涉及到其他模塊,比如原有的一個算法是 a * b + c,現在需要修改爲a * b *c, 可以通過修改原有的類中的方法方式來完成, 前提條件是所有依賴或關聯類都按照相同的邏輯處理。

子模塊變化。一個模塊變化,會對其他的模塊產生影響,特別是一個低層次的模塊變化必然引起高層模塊的變化,因此在通過擴展完成變化時,高層次的模塊修改是必然的,剛剛的書籍打折處理就是類似的處理模塊,該部分的變化甚至會引起界面的變化。

可見視圖變化。可見視圖是提供給客戶使用的界面,如 jsp 程序,swing 界面等,該部分的變化一般會引起連鎖反應(特別是在國內做項目,做歐美的外包項目一般不會影響太大) ,如果僅僅是界面上按鈕、文字的重新排布倒是簡單,最司空見慣的是業務耦合變化,什麼意思呢?一個展示數據的列表,按照原有的需求是六列,突然有一天要增加一列,而且這一列要跨度 N 張表,處理 M 個邏輯才能展現出來,這樣的變化是比較恐怖的,但是我們還是可以通過擴展來完成變化,這就依賴我們原有的設計是否靈活。

我們再來回顧一下書店銷售書籍的程序,首先是我們有一個還算靈活的設計(不靈活是什麼樣子?BookStore 中所有使用到 IBook 的地方全部修改爲實現類,然後再擴展一個 ComputerBook書籍, 你就知道什麼是不靈活了) ,然後有一個需求變化,然後我們通過擴展一個子類擁抱了變化,然後把子類投入運行環境中, 新邏輯正式投產。 通過分析, 我們發現我們並沒有修改原有的模塊代碼, IBook 接口沒有改變, NovelBook類沒有改變,這屬於已有的業務代碼,我們保持了歷史的純潔性。放棄修改歷史的想法吧,一個項目的基本路徑應該是這樣的項目開發、重構、測試、投產、運維,其中的重構可以對原有的設計和代碼進行修改,運維儘量減少對原有代碼的修改,保持歷史代碼的純潔性,提高系統得到穩定性。

爲什麼要使用開閉原則

每個事物的誕生都有它存在的必要性,存在即合理,那我們的開閉原則的存在也是合理的,爲什麼這麼說呢?

首先,開閉原則是那麼的著名,只要是做面向對象編程的,甭管是什麼語言,Java 也好C++也好或者是 Smalltalk,在做開發時都會提及開閉原則,如果你是個架構師,沒有聽說過開閉原則,那你絕對可以閉口氣把自己憋死,如果你是一個 Java 程序員,沒有聽說過開閉原則,那你最好喝口水把自己嗆死。

其次,開閉原則是最基礎的一個原則,前邊五個章節介紹的原則(單一職責原則、里氏替換原則、依賴倒置原則、接口隔離原則、迪米特法則)都是開閉原則的具體形態,也就是說前五個原則就是指導設計的工具和方法,而開閉原則纔是其精神領袖,換一個角度來理解,依照 Java 語言的稱謂,開閉原則是抽象類,其他五大原則則是具體的實現類,開閉原則在面向對象設計領域中的地位就類似於的牛頓第一定律在力學界、勾股定律在幾何學、質能方程在狹義相對論中的地位,其地位無人能及。

最後,開閉原則是非常重要的,通過以下幾個方面來理解其重要性:
1. 開閉原則對測試的影響。每個已經投產了的代碼都是有意義的,並且都受系統的規則約束着,這樣的代碼都是經過“千錘百煉”的測試過程,不僅保證邏輯是正確的,還要保證苛刻條件(高壓力、異常、錯誤)下的不產生“有毒代碼” (Poisonous Code) ,因此有變化提出時,我們就需要考慮一下,原有健壯的代碼是否可以不修改,僅僅通過擴展開實現變化呢?否則,就需要把原有的測試過程回籠一遍,需要進行單元測試、功能測試、集成測試,甚至是驗收測試,現在雖然在大力提倡自動化測試工具,但是仍然代替不了人工的測試工作。

以上面我們提到的書店售書爲例,IBook 接口寫完了,實現類 NovelBook 也寫好,我們需要寫一個測試類進行測試,測試類源代碼如下:

public class NovelBookTest extends TestCase { 
    private String name = "平凡的世界"; 
    private int price = 6000; 
    private String author = "路遙"; 

    private IBook novelBook = new NovelBook(name,price,author); 

    //測試getPrice方法 
    public void testGetPrice() { 
        //原價銷售,判斷輸入和輸出的值是否相等進行斷言 
        super.assertEquals(this.price, this.novelBook.getPrice()); 
    } 
}

單元測試通過,顯示綠條,當然是綠條了,這麼簡單的邏輯再不是綠條,那就找塊豆腐把自己撞死。在單元測試中,有一句非常有名的話,叫做“Keep the bar green to keep the code clean” ,保持綠條有利於代碼整潔,這是什麼意思呢?綠條就是 Junit 運行的兩種結果中的一種:要麼是紅條,單元測試失敗;要麼是綠條,單元測試通過。一個方法的測試方法一般不少於 3個,爲什麼呢?首先是正常的業務邏輯要保證測試到,其次是邊界條件要測試到,然後是異常要測試到,比較重要的方法的測試方法甚至有十多個,而且單元測試是對類的測試,類內的方法耦合是允許的,在這樣的條件下,如果再想着通過修改一個方法或多個方法代碼來完成變化,基本上就是癡人說夢,該類的所有測試方法都要重構,想象一下你在一堆你並不熟悉的代碼中進行重構時的感覺吧!

在書店售書的例子中,增加了一個打折銷售的需求,如果我們直接修改 getPrice方法,來實現業務需求的變化,那我們就要修改單元測試類,想想看我舉這個例子是非常簡單的,如果是一個複雜的邏輯,你的測試類就要修改的面目全非,還有,在實際的項目中一般一個類只有一個測試類,其中可以有很多的測試方法,在一堆本來就很複雜的斷言中進行大量修改,難免就會出現測試遺漏情況,這是一個項目經理很難容忍的事情。

所以,我們需要通過擴展來實現業務邏輯的變化,而不是修改。上面的例子中通過增加一個子類OffNovelBook 來完成了業務需求的變化,對測試有什麼好處呢?我們重新生成一個測試文件OffNovelBookTest,然後對 getPrice進行測試,單元測試是孤立測試,我只要保證我提供的方法正確就成了,其他的我不管,這不是單元測試的範疇。OK,源代碼如下:

public class OffNovelBookTest extends TestCase { 

    private IBook below40NovelBook = new OffNovelBook("平凡的世界",3000,"路遙"); 
    private IBook above40NovelBook = new OffNovelBook("平凡的世界",6000,"路遙"); 

    //測試低於40元的數據是否是打8折 
    public void testGetPriceBelow40() { 
        super.assertEquals(2400, this.below40NovelBook.getPrice()); 
    } 

    //測試大於40的書籍是否是打9折 
    public void testGetPriceAbove40(){ 
        super.assertEquals(5400, this.above40NovelBook.getPrice()); 
    } 

}

新增加的類,新增加的測試方法,只要保證新增加類就是正確的就可以了。

2. 開閉原則可以提高複用性。在面向對象的設計中,我們所有的邏輯都是從原子邏輯組合而來的,而不是在一個類中獨立實現一個業務邏輯,只要這樣代碼纔可以複用,粒度越小,被複用的可能性就越大。那爲什麼要複用呢?較少代碼量,避免相同的邏輯分散在多個角落,避免日後的維護人員爲了修改一個微小的缺陷或新增加新功能,而要在整個項目中到處找相關的代碼,然後發出對開發人員“極度失望”的感慨。那怎麼才能提高複用率呢?縮小邏輯粒度,直到一個邏輯不可再拆分爲止。

3. 開閉原則可以提高可維護性。一個軟件投產後,維護人員的工作不僅僅是對數據進行維護,還可能對程序進行擴展,那維護人員最樂意做的事情,就是擴展一個類,而不是修改一個類,甭管原有的代碼寫的多麼優秀還是寫的多麼糟糕,讓維護人員讀懂原的代碼,然後再修改是一件很痛苦的事情,不要讓他在原有的代碼海洋裏徜徉完畢後再修改,這是對維護人員的一種折磨和摧殘。

4. 面向對象開發的要求。萬物皆對象,我們需要把所有的事物都抽象成對象,然後針對對象進行操作,但是萬物皆運動,有運動就有變化,有變化就要有策略去應對,怎麼快速的應對?就需要在設計之初考慮到所有可能變化的因素,然後留下接口,等待可變因素轉變真正的變化時輕鬆應對。

怎麼使用開閉原則

開閉原則是一個非常虛的原則,前邊五個原則是對開閉原則的具體解釋,但是開閉原則並不侷限於這麼多,它“虛”的沒有邊界,就像“好好學習,天天向上”的口號一樣,告訴我們要好好學習,但是學什麼,怎麼學並沒有告訴我們,需要去體會去掌握,開閉原則也是一個口號,那我們怎麼把這個口號應用到我們實際的工作中呢?

抽象約束。抽象是對一組事物的通用描述,沒有具體的實現,也就表示它可以有非常多可能性,可以跟隨需求的變化而變化,因此通過接口或抽象類可以約束一組行爲,並且能夠實現擴展開放,其包含三層含義:一是通過接口或抽象類約束擴展,對擴展進行邊界限定,不允許出現在接口或抽象類中不存在的public 方法;二是在參數類型定義、輸入輸出參數儘量使用接口或者抽象類,而不是實現類,三是抽象層儘量保持穩定,一旦確定即不允許修改。還是以書店爲例,目前只是銷售小說書籍,單一經營畢竟是有風險的,於是書店新增加了一種書籍:計算機書籍,它不僅僅包含書籍名稱、作者、價格等信息,還有一個獨特的屬性:面向的是什麼領域,也就是它的範圍(scope),比如是講語言的,數據庫,還是硬件等等,我們先來看類圖的修改:
這裏寫圖片描述
增加了一個接口 IComputerBook 和實現類 ComputerBook,而 BookStore 不用做任何修改就可以完成書店銷售計算機書籍的業務,我們來看源代碼:

public interface IComputerBook extends IBook{ 

    //計算機書籍是有一個範圍 
    public String getScope(); 
} 

很簡單,計算機數據增加了一個方法,就是獲得該書籍的範圍,同時繼承 IBook接口,畢竟計算機書籍也是書籍。其實現類如下:

public class ComputerBook implements IComputerBook { 
    private String name; 
    private String scope; 
    private String author; 
    private int price; 

    public ComputerBook(String _name,int _price,String _author,String _scope){ 
        this.name=_name; 
        this.price = _price; 
        this.author = _author; 
        this.scope = _scope; 
    } 
    public String getScope() { 
        return this.scope; 
    } 

    public String getAuthor() { 
        return this.author; 
    } 

    public String getName() { 
        return this.name; 
    } 

    public int getPrice() { 
        return this.price; 
    } 

} 
public class BookStore { 
    private final static ArrayList<IBook> bookList = new ArrayList<IBook>(); 

    //靜態模塊初始化,項目中一般是從持久層初始化產生 
    static{ 
        bookList.add(new OffNovelBook("天龍八部",3200,"金庸")); 
        bookList.add(new OffNovelBook("巴黎聖母院",5600,"雨果")); 
        bookList.add(new OffNovelBook("悲慘世界",3500,"雨果")); 
        bookList.add(new OffNovelBook("金瓶梅",4300,"蘭陵笑笑生")); 
        //增加計算機書籍 
        bookList.add(new ComputerBook("Think in Java",4300,"Bruce Eckel","編程語言")); 
    } 

    //模擬書店買書 
    public static void main(String[] args) { 
        NumberFormat formatter = NumberFormat.getCurrencyInstance(); 
        formatter.setMaximumFractionDigits(2); 
        System.out.println("------------書店買出去的書籍記錄如下:---------------------"); 
        for(IBook book:bookList){ 
            System.out.println("書籍名稱:" + book.getName()+"\t書籍作者:" + 
            book.getAuthor()+  "\t書籍價格:"  +  formatter.format(book.getPrice()/100.0)+"元"); 
        } 
    } 
} 

運行結果如下:


------------書店買出去的書籍記錄如下:--------------------- 
書籍名稱:天龍八部  書籍作者:金庸  書籍價格:¥32.00元 
書籍名稱:巴黎聖母院 書籍作者:雨果  書籍價格:¥56.00元 
書籍名稱:悲慘世界  書籍作者:雨果  書籍價格:¥35.00元 
書籍名稱:金瓶梅 書籍作者:蘭陵笑笑生 書籍價格:¥43.00元 
書籍名稱:Think in Java  書籍作者:Bruce Eckel  書籍價格:¥43.00元 

如果我是做維護的,我就非常樂意做這樣的事情,簡單而且不需要與其他的業務做耦合,我只需要做的事情就是在原有的代碼上進行添磚加瓦,就可以實現業務的變化。我們來看看這段代碼給我們帶來了幾層意思。

首先, ComputerBook 類必須實現 IBook 的三個方法,是通過 IComputerBook 接口傳遞進來的約束,也就是我們制定的 IBook接口對擴展類 ComputerBook 產生了約束力,這個約束力同時對我們擴展提供非常好的幫助,否則 BookStore 類就需要進行大量的修改了。

其次,如果我們原有的程序設計時採用的不是接口,而是實現類,那會出現什麼問題呢?我們把 BookStore 類中的私有變量 bookList修改爲:

private final static ArrayList<NovelBook> bookList = new ArrayList<NovelBook>(); 

把原有IBook的依賴修改爲對NovelBook實現類的依賴, 想想看我們這次的擴展是否還能繼續下去呢?一旦這樣設計,我們就根本沒有辦法擴展,需要修改原有的業務邏輯(也就是 main 方法) ,這樣的擴展基本上就是形同虛設。

最後,如果我們在 IBook 上增加一個方法 getScope 是否可以呢?答案是不可以,因爲原有的實現類NovelBook 已經在投產運行中,它不需要改方法,而且接口是與其他模塊交流的契約,修改契約就等於讓其他模塊修改。因此,我們的接口或抽象類一旦定義,就應該立即執行,不能有修改接口的思想,除非是徹底的大返工。

所以,要實現對擴展開放,首要的前提條件就是:抽象約束。

參數控制模塊行爲。程序員是一個很苦很累的活,那怎麼才能減輕我們的壓力呢?答案是儘量是參數來控制我們的程序的行爲,減少重複開發。參數可以從文件中獲得,也可以從數據庫中獲得,舉個非常簡單的例子,login 方法中提供了這樣的邏輯:先檢查 IP 地址是否在允許訪問的列表中,然後再決定是否需要到數據庫中驗證密碼(如果採用 SSH 架構,則可以通過 Struts 的攔截器來實現) ,該行爲就是一個典型的參數控制模塊行爲的例子,其中達到極致的就是控制翻轉(Inversion of Control) ,使用最多的就是Spring 容器,在 SpringContext 配置文件中,有這樣一段配置:

  <bean id="father" class="xxx.xxx.xxx.Father" /> 
  <bean id="xx" class="xxx.xxx.xxx.xxx"> 
    <property name="biz" ref="father"></property> 
  </bean> 

然後,通過建立一個 Father 類的子類 Son,完成一個新的業務,修改一下配置文件:

  <bean id="son" class="xxx.xxx.xxx.Son" /> 
  <bean id="xx" class="xxx.xxx.xxx.xxx"> 
    <property name="biz" ref="son"></property> 
  </bean> 

通過擴展一個子類,修改配置文件,完成了業務變化,這也是採用框架的好處。

制定項目章程。在一個團隊中,項目章程的建立是非常重要的,因爲在章程中指定了所有人員都必須遵守的約束,而對項目來說約定是優於配置。相信大家都做過項目,會發現一個項目會產生非常多的配置文件,舉個簡單的例子,以 SSH 項目開發爲例,一個項目中的 Bean 配置文件是非常多的,管理非常麻煩,如果需要擴展就需要增加子類,並修改 SpringContext 文件,而如果你在項目中指定這樣一個章程:所有的 Bean 都自動注入,使用 Annotation 進行裝配,進行擴展時,甚至只用寫一個子類,然後由持久層生成對象,其他的都不需要修改,這就需要項目內約束,每個項目成員都必須遵守,該方法需要一個團隊有較高的自覺性,需要一個較長時間的磨合,一旦項目成員都熟悉這樣的規則,比通過接口或抽象類進行約束效率更高,而且擴展性一點也沒有減少。

封裝變化。對變化的封裝包含兩層含義:一是對相同的變化封裝到一個接口或抽象類中,二是對不同的變化封裝到不同的接口或抽象類中,不應該出現兩個不同的變化出現同一個接口或抽象類中。封裝變化,準確的講就是封裝可能發生的變化,一旦預測到或“第六感”發覺有變化,就可以進行封裝,23 個設計模式都是從各個不同的角度對變化進行封裝,我們會在各個模式中逐步講解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章