設計模式鋪鋪路(面向對象設計的原則一二)

mp.weixin.qq.com/s/OdwIswrJF…
java

咱們的知識星球立刻就要開始更新設計模式了,在更新設計模式以前,咱們是否是須要作一些準備呢?不然設計模式中一些遵循的原則你們會一頭霧水,因此我今天來給你們說一些面向對象的七種原則,有人說是6種有人說是7種,我我的認爲是7種,我就按照7種來講,今天我就介紹2種,下一篇文章將會繼續介紹剩下的五種原則,這些原則也會在設計模式中出現,各位技術人,歡迎你們的踊躍參加呦。git

前言

在面向對象的軟件設計中,只有儘可能下降各個模塊之間的耦合度,才能提升代碼的複用率,系統的可維護性、可擴展性才能提升。面向對象的軟件設計中,有23種經典的設計模式,是一套前人代碼設計經驗的總結,若是把設計模式比做武功招式,那麼設計原則就比如是內功心法。經常使用的設計原則有七個,下文將具體介紹。編程

設計原則簡介

  • 單一職責原則:專一下降類的複雜度,實現類要職責單一;設計模式

  • 開放關閉原則:全部面向對象原則的核心,設計要對擴展開發,對修改關閉;api

  • 裏式替換原則:實現開放關閉原則的重要方式之一,設計不要破壞繼承關係;bash

  • 依賴倒置原則:系統抽象化的具體實現,要求面向接口編程,是面向對象設計的主要實現機制之一;ide

  • 接口隔離原則:要求接口的方法儘可能少,接口儘可能細化;測試

  • 迪米特法則:下降系統的耦合度,使一個模塊的修改儘可能少的影響其餘模塊,擴展會相對容易;ui

  • 組合複用原則:在軟件設計中,儘可能使用組合/聚合而不是繼承達到代碼複用的目的。this

這些設計原則並不說咱們必定要遵循他們來進行設計,而是根據咱們的實際狀況去怎麼去選擇使用他們,來讓咱們的程序作的更加的完善。

單一職責原則

解釋

就一個類而言,應該僅有一個引發它變化的緣由,通俗的說,就是一個類只負責一項職責。

此原則的核心就是解耦和加強內聚性

那麼爲何要使用單一職責原則:

若是一個類承擔的職責過多,就等於把這些職責耦合在一塊兒,一個職責的變化可能會削弱或者抑制這個類完成其餘職責的能力。這種耦合會致使脆弱的設計。

這也是他的優勢,咱們總結一下

優勢

(1)下降類的複雜度;

(2)提升類的可讀性,提升系統的可維護性;

(3)下降變動引發的風險(下降對其餘功能的影響)。

咱們來舉一些簡單的例子來講明一下這個單一職責原則

實例

//咱們用動物生活來作測試    class Animal{        public void breathe(String animal){            System.out.println(animal+"生活在陸地上");        }    }    public class Client{        public static void main(String[] args){            Animal animal = new Animal();            animal.breathe("羊");            animal.breathe("牛");            animal.breathe("豬");        }    }    運行結果 羊生活在陸地上 牛生活在陸地上 豬生活在陸地上複製代碼

可是問題來了,動物並非都生活在陸地上的,魚就是生活在水中的,修改時若是遵循單一職責原則,須要將 Animal 類細分爲陸生動物類 Terrestrial,水生動物 Aquatic,代碼以下:

class Terrestrial{    public void breathe(String animal){        System.out.println(animal+"生活在陸地上");    }}class Aquatic{    public void breathe(String animal){        System.out.println(animal+"生活在水裏");    }}    public class Client{        public static void main(String[] args){            Terrestrial terrestrial = new Terrestrial();            terrestrial.breathe("羊");            terrestrial.breathe("牛");            terrestrial.breathe("豬");                Aquatic aquatic = new Aquatic();            aquatic.breathe("魚");        }運行結果:羊生活在陸地上牛生活在陸地上豬生活在陸地上魚生活在水裏複製代碼

可是問題來了若是這樣修改花銷是很大的,除了將原來的類分解以外,還須要修改客戶端。而直接修改類 Animal 來達成目的雖然違背了單一職責原則,但花銷卻小的多,代碼以下:

class Animal{    public void breathe(String animal){        if("魚".equals(animal)){            System.out.println(animal+"生活在水中");        }else{            System.out.println(animal+"生活在陸地上");        }    }}public class Client{    public static void main(String[] args){        Animal animal = new Animal();        animal.breathe("羊");        animal.breathe("牛");        animal.breathe("豬");        animal.breathe("魚");    }複製代碼

能夠看到,這種修改方式要簡單的多。可是卻存在着隱患:有一天須要將魚分爲生活在淡水中的魚和生活在海水的魚,則又須要修改 Animal 類的 breathe 方法,而對原有代碼的修改會對調用「豬」「牛」「羊」等相關功能帶來風險,也許某一天你會發現程序運行的結果變爲「牛生活在水中」了。

這種修改方式直接在代碼級別上違背了單一職責原則,雖然修改起來最簡單,但隱患倒是最大的。還有一種修改方式:

class Animal{    public void breathe(String animal){        System.out.println(animal+"生活在陸地上");    }    public void breathe2(String animal){        System.out.println(animal+"生活在水中");    }}public class Client{    public static void main(String[] args){        Animal animal = new Animal();        animal.breathe("牛");        animal.breathe("羊");        animal.breathe("豬");        animal.breathe2("魚");    }}複製代碼

能夠看到,這種修改方式沒有改動原來的方法,而是在類中新加了一個方法,這樣雖然也違背了單一職責原則,但在方法級別上倒是符合單一職責原則的,由於它並無動原來方法的代碼。

這三種方式各有優缺點,那麼在實際編程中,採用哪一中呢?其實這真的比較難說,須要根據實際狀況來肯定。個人原則是:只有邏輯足夠簡單,才能夠在代碼級別上違反單一職責原則;只有類中方法數量足夠少,才能夠在方法級別上違反單一職責原則;

例如本文所舉的這個例子,它太簡單了,它只有一個方法,因此,不管是在代碼級別上違反單一職責原則,仍是在方法級別上違反,都不會形成太大的影響。實際應用中的類都要複雜的多,一旦發生職責擴散而須要修改類時,除非這個類自己很是簡單,不然仍是遵循單一職責原則的好。

以上就是我所說的單一職責原則了,不少書中介紹的說它並不屬於面向對象設計原則中的一種,可是我認爲它是,因此我就把他解釋出來了。

開放關閉原則

開放關閉原則又稱爲開放封閉原則。

定義

一個軟件實體應該對擴展開放,對修改關閉,這個原則也是說,在設計一個模塊的時候,應當使這個模塊能夠在不被修改的前提下被擴展,換句話說,應當能夠在沒必要修改源碼的狀況下改變這個模塊的行爲。

這句話其實剛開始看上去是有些矛盾的,接下來在我後邊文章解釋裏面,我會把他解釋清楚一點。

咱們先用個比較好玩的例子來講一下。

西遊記你們都看過,玉帝招安孫悟空的時候的橋段,你們還有沒有印象?

當年大鬧天宮的時候美猴王對玉皇大帝作了個挑戰,美猴王說:皇帝輪流作,明年到我家,只叫他搬出去,將天宮讓給我,對於這個挑戰,太白金星給玉皇大帝提了個意見,咱們把它招上來,給他個小官作,他不就不鬧事了?

換一句話說,不用興師動衆的,不破壞天庭的規矩這就是閉,可是收他爲官,即是開,招安的方法即是符合開閉原則的,給他個‘弼馬溫’的官,即可以讓這個系統正常不受威脅,是吧,何樂而不爲?我給你們畫個圖。

招安的方法的關鍵就是不容許更改現有的天庭秩序,可是容許將妖猴歸入現有的秩序中,從而擴展了這個秩序,用面向對象的語言來講:不容許更改的是系統的抽象層,而容許擴展的是系統的實現層。

咱們寫一些簡單的代碼來進行一個完整的展現來進行一下理解。

實例

//書店賣書    interface Books{        //書籍名稱        public Sting getName();        //書籍價格        public int getPrice();        //書籍做者        public String getAuthor();    }    public class NovelBook implements Books {    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;    }    @Override    public String getName() {        return name;    }    @Override    public int getPrice() {        return price;    }    @Override    public String getAuthor() {        return author;    }}複製代碼

以上的代碼是數據的實現類和書籍的類別;

下面咱們將要開始對書籍進行一個售賣活動:

public class BookStore {    private final static ArrayList<Books> sBookList = new ArrayList<Books>();    static {        sBookList.add(new NovelBook("天龍八部", 4400, "金庸"));        sBookList.add(new NovelBook("射鵰英雄傳", 7600, "金庸"));        sBookList.add(new NovelBook("鋼鐵是怎麼煉成的", 7500, "保爾·柯查金"));        sBookList.add(new NovelBook("紅樓夢", 3300, "曹雪芹"));    }    public static void main(String[] args) throws IOException {        NumberFormat format = NumberFormat.getCurrencyInstance();        format.setMaximumFractionDigits(2);       System.out.println("----書店賣出去的書籍記錄以下---");        for (Books book : sBookList) {            System.out.println("書籍名稱:" + book.getName()                    + "\t書籍做者:" + book.getAuthor()                    + "\t書籍價格:" + format.format(book.getPrice() / 100.00) + "元");        }    }}複製代碼

運行結果以下:

D:\develop\JDK8\jdk1.8.0_181\bin\java.exe "-javaagent:D:\develop\IDEA\IntelliJ IDEA 2018.2.4\lib\idea_rt.jar=62787:D:\develop\IDEA\IntelliJ IDEA 2018.2.4\bin" -Dfile.encoding=UTF-8 -classpath D:\develop\JDK8\jdk1.8.0_181\jre\lib\charsets.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\deploy.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\dnsns.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\jaccess.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\localedata.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\nashorn.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunec.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\ext\zipfs.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\javaws.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jce.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jfr.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jfxswt.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\jsse.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\management-agent.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\plugin.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\resources.jar;D:\develop\JDK8\jdk1.8.0_181\jre\lib\rt.jar;D:\develop\IDEA_Workspace\CloudCode\out\production\PattemMoudle com.yldyyn.test.BookStore----書店賣出去的書籍記錄以下---書籍名稱:天龍八部  書籍做者:金庸  書籍價格:¥44.00元書籍名稱:射鵰英雄傳  書籍做者:金庸  書籍價格:¥76.00元書籍名稱:鋼鐵是怎麼煉成的  書籍做者:保爾·柯查金  書籍價格:¥75.00元書籍名稱:紅樓夢  書籍做者:曹雪芹  書籍價格:¥33.00元Process finished with exit code 0複製代碼

可是若是說如今書店賣書的時候要求打折出售,40以上的咱們要7折售賣,40如下的咱們打8折。

方法有三種,第一個辦法:修改接口。在 Books 上新增長一個方法 getOnSalePrice(),專門進行打折,全部實現類實現這個方法。可是這樣修改的後果就是實現類 NovelBook 要修改, BookStore 中的 main 方法也修改,同時 Books 做爲接口應該是穩定且可靠的,不該該常常發生變化,不然接口作爲契約的做用就失去了效能,其餘不想打折的書籍也會由於實現了書籍的接口必須打折,所以該方案被否認。

第二個辦法:修改實現類。修改 NovelBook 類中的方法,直接在 getPrice() 中實現打折處理,這個應該是你們在項目中常用的就是這樣辦法,經過 class 文件替換的方式能夠完成部分業務(或是缺陷修復)變化,可是該方法仍是有缺陷的,例如採購書籍人員也是要看價格的,因爲該方法已經實現了打折處理價格,所以採購人員看到的也是打折後的價格,這就產生了信息的矇蔽效果,致使信息不對稱而出現決策失誤的狀況。該方案也不是一個最優的方案。

第三個辦法,經過擴展實現變化增長一個子類 OffNovelBook,覆寫 getPrice 方法,高層次的模塊(也就是 static 靜態模塊區)經過 OffNovelBook 類產生新的對象,完成對業務變化開發任務。好辦法,風險也小。

public class OnSaleBook extends NovelBook {    public OnSaleBook(String name, int price, String author) {        super(name, price, author);    }    @Override    public String getName() {        return super.getName();    }    @Override    public int getPrice() {        int OnsalePrice = super.getPrice();        int salePrce = 0;        if (OnsalePrice >4000){            salePrce = OnsalePrice * 70/100;        }else{            salePrce = OnsalePrice * 80/100;        }        return  salePrce;    }    @Override    public String getAuthor() {        return super.getAuthor();    }}複製代碼

上面的代碼是擴展出來的一個類,而不是在原來的類中進行的修改。

public class BookStore {    private final static ArrayList<Books> sBookList = new ArrayList<Books>();    static {        sBookList.add(new OnSaleBook("天龍八部", 4400, "金庸"));        sBookList.add(new OnSaleBook("射鵰英雄傳", 7600, "金庸"));        sBookList.add(new OnSaleBook("鋼鐵是怎麼煉成的", 7500, "保爾·柯查金"));        sBookList.add(new OnSaleBook("紅樓夢", 3300, "曹雪芹"));    }    public static void main(String[] args) throws IOException {        NumberFormat format = NumberFormat.getCurrencyInstance();        format.setMaximumFractionDigits(2);       System.out.println("----書店賣出去的書籍記錄以下---");        for (Books book : sBookList) {            System.out.println("書籍名稱:" + book.getName()                    + "\t書籍做者:" + book.getAuthor()                    + "\t書籍價格:" + format.format(book.getPrice() / 100.00) + "元");        }    }}複製代碼

結果展現:

----書店賣出去的書籍記錄以下---書籍名稱:天龍八部  書籍做者:金庸  書籍價格:¥30.80元書籍名稱:射鵰英雄傳  書籍做者:金庸  書籍價格:¥53.20元書籍名稱:鋼鐵是怎麼煉成的  書籍做者:保爾·柯查金  書籍價格:¥52.50元書籍名稱:紅樓夢  書籍做者:曹雪芹  書籍價格:¥26.40元Process finished with exit code 0複製代碼

在開閉原則中,抽象化是一個關鍵,解決問題的關鍵在於抽象化,在 JAVA 語言這種面向對象的語言中,能夠給系統定義出一個一勞永逸的,再也不更改的抽象化的設計,此設計容許擁有無窮無盡的實現層被實現。

在 JAVA 語言中,能夠給出一個或者多個抽象的 JAVA 類或者是JAVA接口,規定全部的具體類必須提供方法特徵做爲系統設計的抽象層,這個抽象層會碰見全部的可能出現的擴展,所以,在任何擴展狀況下都不回去改變,這就讓系統的抽象層不須要修改,從而知足開閉原則的第二條,對修改進行閉合。

同時,從抽象層裏面導出一個或者多個新的具體類能夠改變系統的行爲,這樣就知足了開閉原則的第一條。

儘管不少時候咱們沒法百分百的作到開閉原則,可是若是向着這個方向去努力,就可以有部分的成功,這也是能夠改善系統的結構的。

我是懿,一個正在被打擊還在努力前進的碼農。歡迎你們關注咱們的公衆號,加入咱們的知識星球,咱們在知識星球中等着你的加入。

相關文章
相關標籤/搜索