面向對象設計之魂的六大原則

版權申明】非商業目的可自由轉載
博文地址:
出自:shusheng007

前言

常言道實踐是需要理論來指導的,而理論又是需要實踐來檢驗和修正的,理論和實踐就這樣相互促進,最後將一個領域推向新的高度。從面向對象編程的出現到現在好像已經有半個世紀了(於1950s第一次出現在MIT),所以這六大原則是在無數先輩的理論與實踐中產生的。
身爲一名主要使用面向對象編程軟件從業員(碼農),這六大原則是必須要掌握的,它就是設計模式的理論,設計模式是它的實踐。

六大原則

這六大原則應該成爲你在日常開發中的理論指導,只要你或多或少的遵循這六大設計原則,那麼寫出的代碼就不會太爛,慢慢的你會發現你竟然理解了那些吊炸天的設計模式意圖及設計思路。

1 單一職責(Single Responsibility Principle)

這個原則顧名就可以思義,就是一個類應該只負責一個職責,術語叫:僅有一個引起其變化的原因。簡單點說:一個類中應該是一組相關性很高的函數及數據的封裝,箇中含義請自行意會。看起來簡單,但是做起來就難了,這可能是六大原則中最難以熟練掌握的一個原則了,它高度依賴程序員的自身素質及業務場景。

例如兩個碼農能爲是否應該將一個函數寫進某個類裏面吵一天,最後誰也沒有說服誰,最後他兩成了

2 開閉原則(Open Close Principle)

它是面向對象最重要的設計原則,由Bertrand Meyer(勃蘭特.梅耶)在1988年出版的《面向對象軟件構造》。中提出的。
在這裏插入圖片描述
本尊照片,圖片來源

定義如下

開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應儘量在不修改原有代碼的情況下進行擴展。

提倡一個類一旦開發完成,後續增加新的功能就不應該通過修改這個類來完成,而是通過繼承,增加新的類。
大家想必都聽過軟件需求不斷變化的那個段子,在軟件開發這個行當唯一不變的就是變化本身。那爲什麼應該對修改關閉呢,因爲你一旦修改了某個類就有可能破壞系統原來的功能,就需要重新測試。其實我知道你們此刻在想什麼,回憶一下自己的日常工作,有幾個遵守了這個原則,都是需求來了就找到原來的類,進去改代碼唄,_。看看有指導原則尚且如此,沒有的話就更加亂套了。

那麼是不是就一定不能修改原來的類的,當然不是了,我們都是成年人了,要清楚的認識到,這個世界不是非黑即白的。當我們發現原來的類已經爛到家了,當然在有條件的情況下及時重構,避免系統加速腐敗。

3 里氏替換原則(Liskov Substitution Principle)

這個原則的的提出則可以是一位女性Barbara Liskov,下圖爲她2010年的照片,現在應該還健在吧,其實計算機這個行當的從業人員比較幸福,我們的祖師爺基本都健在,不像一些其他行業,都死了不知道多少年了,顯得很神祕。
在這裏插入圖片描述
定義如下:

里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對象。

簡單點說,一個軟件系統中所有用到一個類的地方都替換成其子類,系統應該仍然可以正常工作。這個原則依賴面向對象的繼承特性和多態特性,這個原則我們有意無意中使用的就比較多了。因爲一個優秀的程序員一定面向抽象(接口)編程的,如果你不是,說明你還有很大的進步空間。

例如我們有如下的代碼,一個圖形的基類Shap,以及它的兩個子類RectangleTriangle,安裝裏式替換原則,所有使用Shape的地方都可以安全的替換成其子類。

//基類
public abstract class Shape {
    public abstract void draw();
}
//子類矩形
public class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("繪製矩形");
    }
}
//子類三角形
public class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("繪製三角形");
    }
}

寫一個使用Shape類的函數

    public static void main(String[] args) {
        //使用Shape的子類Triangle 的實例來替換Shape的實例,程序工作正常
        drawShape(new Triangle());
    }
    private static void drawShape(Shape shape){
        System.out.println("開始畫圖");
        shape.draw();
        System.out.println("結束畫圖");
    }

輸出結果:

開始畫圖
繪製三角形
結束畫圖

如上代碼所示:本來drawShape()函數需要一個Shape的實例,而我們卻傳給他一個其子類的實例,但是它正常工作了。我們使用Shape的子類Triangle的實例來替換Shape的實例,程序工作正常。這個原則也非常重要而常用,面向抽象編程。

4 依賴倒置原則(Dependence Inversion Principle)

這個原則的提倡者正是大名鼎鼎的 Robert C. Martin,人稱Bob大叔
在這裏插入圖片描述
定義

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對接口編程,而不是針對實現編程。

關鍵點:

  1. 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象
  2. 抽象不應該依賴細節
  3. 細節應該依賴抽象

看到上面的介紹是不是已經懵逼了,反正我剛開始學習編程(半路大齡非科班出身)的時候是徹底懵逼的。因爲這個概念人家是從軟件設計的角度說的,我們應該將其對應到我們具體的實踐當中去理解,例如Java領域

抽象:java中的抽象類或者接口 (如上面代碼中的Shape 抽象類)
細節:java中的具體實現類(如上面代碼中的Rectangle 和Triangle 實體類)
高層模塊:java中的調用類(例如上面代碼中drawShape(Shape shape)函數的類)
低層模塊:java中的實現類(細節)

依賴倒置又叫依賴倒轉,關鍵在倒置上,啥叫倒置,那不倒置的時候是什麼樣的?如下面圖所示
正常情況下,調用類(高層模塊)應該依賴具體實現類(低層模塊實現細節),但是現在依賴了實現類的接口(低層模塊的細節抽象),所以叫依賴倒置了。
在這裏插入圖片描述
例如菜鳥程序員(牛翠花)會這麼寫代碼

    private static void drawRectangle (Rectangle rectangle){        
        rectangle.draw();
    }
    private static void drawTriangle  (Triangle triangle){        
        triangle.draw();
    }

而老鳥(王二狗)則會

    private static void drawShape(Shape shape){
        shape.draw();
    }

那麼菜鳥的代碼會有什麼問題呢,假設現在產品經理覺得矩形不好看,讓牛翠花將矩形換成五角形,那麼牛翠花就要同時修改調用類和增加一個繪製類,而王二狗的代碼只需要增加一個五角形的繪製類,這就遵循了開關閉原則

所以我們要對接口編程,舉幾個具體的例子:聲明方法參數的類型,實例變量的類型,方法的返回值類型,類型強制轉換等等場景。

牛翠花的代碼直接依賴了實現細節,而王二狗的代碼依賴的是實現細節的抽象(依賴倒置了)。剛入門時候我們都是牛翠花,但是幾年後有的人變成了王二狗,有的人仍然是牛翠花。。。

5 接口隔離原則(Interface Segregation Principle)

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

其實這個原則是很容易理解的,就是讓調用者依賴的接口儘可能的小。例如人類分男人和女人,男人和女人都要吃飯,但是隻有女人每個月來大姨媽,那麼如果你設計一個接口裏面除了吃飯還有來大姨媽同時給男人和女人用就不合適了。

interface IHuman{
    void eat();
    void sleep();
    void laiDaYiMa();//來大姨媽
}

這你讓男人情何以堪,萬一有個菜鳥程序員抽風了,直接給把來大姨媽的方法實現了,那後果就。。。

//男人類不需要接口中的laiDaYiMa方法
class man implements IHuman{
    @Override
    public void eat() {        
    }
    @Override
    public void laiDaYiMa() {
        //老子不來大姨媽,所以方法置空,啥也不幹!
    }
}
class woman implements IHuman{
    @Override
    public void eat() {        
    }
    @Override
    public void laiDaYiMa() {
        System.out.println("王二狗,給老孃倒一杯熱水");
    }
}

上面的例子就違反了接口隔離原則,正確的做法是申明兩個接口,使接口保持最小

interface IHuman{
    void eat();
}
interface ISpecialForWoman{
    void laiDaYiMa();//來大姨媽
}

男人只實現IHuman,女人實現IHumanISpecialForWoman

class man implements IHuman{
    @Override
    public void eat() {
    }
}
class woman implements IHuman,ISpecialForWoman{
    @Override
    public void eat() {
    }

    @Override
    public void laiDaYiMa() {
        System.out.println("王二狗,給老孃倒一杯熱水");
    }
}

6 迪米特原則(Law of Demeter 又名Least Knowledge Principle)

迪米特法則來自於1987年美國東北大學(Northeastern University)一個名爲“Demeter”的研究項目,又稱最少知識原則(LeastKnowledge Principle, LKP),其定義如下:

迪米特法則(Law of Demeter, LoD):一個軟件實體應當儘可能少地與其他實體發生相互作用。

一個類應該對自己需要調用的類知道得最少,類的內部如何實現、如何複雜都與調用者或者依賴者沒關係,調用者或者依賴者只需要知道他需要的方法即可,其他的我一概不關心。

總結

以上6大原則全部是以構建靈活可擴展可維護的軟件系統爲目的的,所以說它的重要性是高於設計模式的,也應該是程序員時刻印在腦子裏的,設計模式也是它的具體實踐而已。

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