七種設計原則(一) 開閉原則(設計模式的基石)

面向對象和麪向過程的區別:

面向過程: git

吃 (狗.屎)算法

下雨了,我打開了雨傘.編程

面向對象:設計模式

狗 吃 (屎)架構

屬性:我 雨傘 雨
動做:我打開雨傘框架

面向過程強調的是一件事情「該怎麼作」 強調一次成型,是連續性的。好比製造一臺高達,面向過程更像是流水線的形式。經過制定一系列規則來完成高達的拼裝。ide

面向對象是一件事情「該讓誰作」,而後那個誰就是對象。好比製造一臺高達,面對對象更像是模塊化的形式,四肢和頭均可以做爲那個"對象",而後拼裝起來。模塊化

前輩們寫的:函數

a) 認識問題角度:面向過程,死物受規則被動操控;面向對象,活物主動交互。學習

b) 解決問題模塊:面向過程,函數;面向對象,對象。

c) 解決問題中心角度:面向過程,Hwo,「如何作?」(流程封裝爲函數,「如何」就是過程,誰來作僅是參數);面向對象,Who,「誰來作?」(「誰」就是對象,如何作是他本身的操做,多個對象協同完成任務)。

d) 解決問題步驟角度:面向過程,先具體邏輯細節,後抽象問題總體;面向對象,先抽象問題總體,後具體邏輯細節。

e) 數傳遞角度:面向過程,參數或全局變量;面向對象,方法。

f) 關係角度:面向過程,找不到對象;面向對象,可找到過程。

g) 複用層次角度:面向過程,方法層複用;面向對象,對象層複用。

h) 新概念角度:面向過程,句柄;面向對象,構造&析構。
連接:https://www.zhihu.com/question/19701980/answer/22817355
來源:知乎

內涵和外延

說到面向對象,必定要理解兩個邏輯學中的概念:內涵和外延   (比較抽象)

內涵:概念中所反映的事物的特有屬性
外延:具備概念所反映的特有屬性的全部事物

打個比方,你面前有一些梨子、蘋果、香蕉、菠蘿,它們雖然樣子不一樣,但它們都富含"水分",以及它們都是植物的"果實",咱們抓住它們這兩個特徵,就把它們簡化叫"水果"。當咱們面對這堆東西,腦海中對這兩個特徵有一種模糊的感受,爲了把這種感受說出來,咱們用了一個漢語詞彙"水果",不過咱們也能夠用英語詞彙"fruit",用什麼樣的詞彙不重要,重要的是這個詞彙表明了一種咱們對這兩種特徵的總結。
除了水果之外,咱們對不少東西都有這樣的總結,這些總結彼此不一樣,但它們又有兩個共同特徵:"特色的總結"、"能和其餘總結區別"。咱們就把對這些總結的總結取個了名字,叫"概念"。
再拿"水果"這個概念來講,"富含水分"和"植物果實"兩個特徵,是被"水果"一詞包含在內了的,咱們叫它"內涵",當把這個內涵發散出去,對應到具體的例子:蘋果梨子香蕉菠蘿,這裏的具體例子,就是"水果"這個概念的"外延"。

面向對象的角度來講,內涵就是類的定義,外延就是類的實例

連接:https://www.zhihu.com/question/22267682/answer/134411093
來源:知乎


理解了上面的概念咱們再來學習設計模式效果會更佳~

開閉原則(設計模式的基石):

開閉原則是面向對象中可複用設計的基石,是面向對象中最重要的原則之一。

1.定義

開閉原則強調一個軟件實體(如:類,模塊和函數)應該對外擴展開放,對修改關閉

對外擴展開放:模塊對外擴展開放,意味着需求變化時,能夠對模塊擴展,使其具備改變的新行爲。

模塊經過擴展應對需求的變化。

對修改關閉:模塊對修改關閉,表示當需求變化時,關閉對模塊源碼的修改,固然這裏的「關閉」是儘量不修改的意思,儘可能在不修改源代碼的基礎上面擴展組件。

 

2.問題和解決方案

一個軟件產品在聲明週期內都會發生變化的,既然變化是一個事實,咱們就應該在設計時儘可能適應變化。以提升項目的穩定性和靈活性。開閉原則告訴咱們應該儘可能經過擴展軟件實體的行爲來完成新的變化,而不是經過修改現有代碼來完成。

好比支付接口,設計時 支付流程 只有一套,而後在這一套流程的基礎上擴展出來,當和某一家銀行合做,就新添加一個實現,當和某一家銀行終止合做時,就停用該實現。不用更改支付流程這個模塊。這樣在咱們有新的需求變動時,是不須要修改支付模塊的(修改源代碼的風險很大,你可能看不懂別人的代碼),只須要在源代碼的基礎上進行擴展(由於源代碼已經通過成千上萬次的訪問測試,是正確的)。這也是咱們常說的面向接口編程。先定義一個抽象類,而後抽象去界定擴展。

再好比 張全蛋開了一家書店 類圖以下:

這裏寫圖片描述

BookStore 指的是張全蛋的書店

IBook 定義了書籍的三個屬性:名稱、價格和做者

NovelBook 是一個具體的實現類,全部小說書籍的總稱。
 

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)+"元");

        }

    }

}

------------書店買出去的書籍記錄以下:---------------------

書籍名稱:天龍八部 書籍做者:金庸 書籍價格:¥32.00元

書籍名稱:巴黎聖母院 書籍做者:雨果 書籍價格:¥56.00元

書籍名稱:悲慘世界 書籍做者:雨果 書籍價格:¥35.00元

書籍名稱:金瓶梅 書籍做者:蘭陵笑笑生 書籍價格:¥43.00元

到這裏 張全蛋的書店上線了,全蛋很開心,項目完成了。

過了一段時間,全蛋的書店不多有人光顧。全蛋打算打折出售這批書。對於已經運行的項目來講這就是一個新的需求。

咱們有三種方法來完成全蛋的要求:

修改接口:

在IBook上新增一個方法getOffPrice(); 在 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();

    // 打8折

    int offPrice=0;

    offPrice = selfPrice * 80 /100;

    return offPrice;

    }

}

很簡單,僅僅覆寫了 getPrice 方法.

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元

經過擴展完成了打折的業務,知足了全蛋的要求。

 

3.概括變化

邏輯變化:

只變化一個邏輯,而不涉及到其餘模塊,好比原有的一個算法是a*b+c,如今要求a*b*c,可能經過修改原有類中的方法方式來完成,前提條件是全部依賴或關聯類都按此相同邏輯處理。

子模塊變化:

一個模塊變化,會對其餘模塊產生影響,特別是一個低層次的模塊變化必然引發高層模塊的變化,所以在經過擴展完成變化時,高層次的模塊修改是必然的。

可見視圖變化:

可見視圖是提供給客戶使用的界面,該部分的變化通常會引發連鎖反應,若是僅僅是界面上按鈕、文字的從新排布卻是簡單,最司空見慣的是業務耦合變化,什麼意思呢?一個展現數據的列表,按照原有的需求是六列,忽然有一天要增長一列,並且這一列要跨度N張表,處理M個邏輯才能展示出來,這樣的變化是比較恐怖的,可是咱們仍是能夠經過擴展來完成變化,這就依賴咱們原有的設計是否靈活。

 

4.優勢:開閉原則提升了系統的可維護性和代碼的重用性

可複用性好。

咱們能夠在軟件完成之後,仍然能夠對軟件進行擴展,加入新的功能,很是靈活。所以,這個軟件系統就能夠經過不斷地增長新的組件,來知足不斷變化的需求。

可維護性好。

因爲對於已有的軟件系統的組件,特別是它的抽象底層不去修改,所以,咱們不用擔憂軟件系統中原有組件的穩定性,這就使變化中的軟件系統有必定的穩定性和延續性。

 

5.如何使用開閉原則:

寫代碼以前必定要多想,多考慮。要對可能擴展的需求有前瞻性和預見性(此處須要經驗...)

實現開閉原則的關鍵就在於「抽象」。把系統的全部可能的行爲抽象成一個抽象底層,這個抽象底層規定出全部的具體實現必須提供的方法的特徵。做爲系統設計的抽象層,要預見全部可能的擴展,從而使得在任何擴展狀況下,系統的抽象底層不需修改;同時,因爲能夠從抽象底層導出一個或多個新的具體實現,能夠改變系統的行爲,所以系統設計對擴展是開放的。

咱們在軟件開發的過程當中,一直都是提倡需求導向的。這就要求咱們在設計的時候,要很是清楚地瞭解用戶需求,判斷需求中包含的可能的變化,從而明確在什麼狀況下使用開閉原則。

關於系統可變的部分,還有一個更具體的對可變性封裝原則(Principle of Encapsulation of Variation, EVP),它從軟件工程實現的角度對開閉原則進行了進一步的解釋。EVP要求在作系統設計的時候,對系統全部可能發生變化的部分進行評估和分類,每個可變的因素都單獨進行封裝。

咱們在實際開發過程的設計開始階段,就要羅列出來系統全部可能的行爲,並把這些行爲加入到抽象底層,根本就是不可能的,這麼去作也是不經濟的。所以咱們應該現實的接受修改擁抱變化,使咱們的代碼能夠對擴展開放,對修改關閉。

儘可能不修改原來的代碼

除非是修改原來代碼中的錯誤,不然儘可能不要去修改原來的代碼,可是也有例外,好比擴展了底層模塊,高層模塊仍是須要發生一些變化的,否則低層模塊的擴展就是沒有任何意義的代碼片斷;

以抽象代替實現

這也是咱們一直所說的面向接口編程,固然這裏的抽象並不只僅是指接口,還能夠是抽象類;

以抽象隔離變化

首先,不管模塊是多麼的「封閉」,都會存在一些沒法對之封閉的變化,既然不可能徹底封閉,那麼設計人員必須對於其餘設計的模塊應該對哪一種變化封閉進行選擇,他必須猜想出最有可能發生的變化種類,而後構造抽象來隔離變化,其次,咱們並無未卜先知的能力,因此在最初編寫代碼時能夠假設變化不會發生,可是當變化發生時,咱們就須要去建立抽象來隔離之後發生的同類的變化;

抽象層設計到整個項目的架構,所以抽象層須要儘可能保持穩定,一旦肯定就不要輕易修改;

避免不合理的抽象

開閉原則須要使用抽象,可是過分的抽象或者說不合理的抽象一樣會帶來很大的問題,所以抽象應該作到合理的抽象;

其餘設計原則是實現開閉原則的一種手段

其中單一原則要求作到類的職責單一,裏式替換原則要求不能破壞繼承體系,依賴倒置原則要求咱們要面向接口編程,接口隔離原則要求作到接口要精簡單一,迪米特法則則是要求作到下降耦合度,若是遵循了前面的五個法則,那麼天然的也就作到了開閉原則,所以說開閉原則是設計原則的總綱

6.總結:

開閉原則:用抽象構建框架,用實現擴展細節。 

感謝百度百科~

看只有一丟丟做用,仍是要多寫,多用,多總結,多思考才能體會其中的奧祕。 小夥子當你看到這裏說明你已經深得朕的真傳~

連接自http://blog.csdn.net/zhengzhb/article/details/7296944

看完這些前輩寫的,感受收穫滿滿~    仍是要多寫,多用才能體會其中的奧祕。

相關文章
相關標籤/搜索