物件導向的六大原則

sydMobile發表於2020-11-15

前言

我們都知道物件導向有六大原則,23種設計模式。它們可以指導我們如何寫出更加優秀的程式碼。六大原則是基礎,我們物件導向程式設計應該儘量遵從這六大原則,才能寫出優秀的程式碼。

23種設計模式是前人為我們總結出的解決某一類問題的辦法,通過使用這些模式,我們可以更好的解決這一類問題。當然 23 種設計模式的設計前提也是建立在六大原則基礎之上的。

目錄

六大原則概念

六大原則是物件導向的六大原則,也就是說,我們在編寫面嚮物件語言的時候,只有遵守了這六大原則才能寫出真正的物件導向。才能擁有物件導向的思想。我們寫的程式碼符合了這六大原則,有助有提高我們程式碼的質量,寫出可擴充套件、可維護、可讀性好、低耦合、高內聚的程式碼。

當然不符合這些原則,同樣也是可以寫程式碼的,只不過寫出的程式碼質量就沒有保證了,只能說勉強算是個程式碼。這好比做人一樣,人也是有原則的,符合了這些原則,那就可以做一個堂堂正正的好人,不符合這些原則,也能活著。但是當不斷的觸犯原則,最後成了一個沒有原則的人了,那麼結果顯然可見。如果隨著程式不斷的變大,程式碼不斷的沒有原則,那麼最終的結果就是你的程式無法進行下一步維護了。

總之我們在寫程式碼的時候要儘量符合這些原則!才能寫出高質量程式碼!

單一職責

單一職責是我們優化程式碼的第一步

概念

概念:就一個類而言,應該僅有一個引起它變化的原因

概念可能不太好懂,簡單來說就是一個類中應該是一組相關性很高的函式、資料的封裝。

下面舉例子:

public class Activity{
  
  // 請求網路載入
  public void requestNet(){
    String url = editText.getText();
    String parmas = editText.getText();
    // 判斷是否符合某個條件
    if(xx){
      
    }
    // 繼續判斷
    if(xxx){
      
    }
    .... 等等省略1000行
  }
  
  class Adapter{
    
  }
  
  // 資料類
  class Data{
    
  }
  
  class Xxx{
    
  }
  
  .....
}

像上面的例子就是一個很好的反例代表,把所有的職責全部放到了 Activity 中,把所有的函式功能都放到了 requestNet 中。這樣勢必造成 Activity 異常的臃腫,只要一個職責發生變化就能引起 Activity 的變化。比如 Adapter 變化,要去 Activity 中修改,等等都會對 Activity 造成變化。requestNet 函式中的功能不夠純粹,裡面又包含了很多其他的功能,也會導致同樣的問題。

這也就是概念中提到的,就一個類而言,應該僅有一個引起它變化的原因。

好處

單一職責的好處很明顯,讓一個類、函式只負責某一項任務或者功能,可以達到很好的複用效果,程式碼的可讀性也會增強,可讀性好了,對應的可維護性也會增加。

當然關於職責的劃分是一個很抽象的概念,每個人的劃分都會不同,單一職責的劃分界限並不總是那麼清晰,有的時候劃分的很細也會帶來不方便。這是一個靈活掌握的問題,關鍵是設計程式碼的時候有沒有考慮到這種思想。

開閉原則

Java 世界裡最基礎的設計原則,指導我們如何建立一個穩定的、靈活的系統。

概念

軟體中的物件(類、模組、函式等)應該對於擴充套件是開放的,但是對於修改是封閉的。

在編寫程式碼的過程中,不是一成不變的,需求的變化、升級、維護等等都需要對程式碼進行修改,修改的時候就不可避免地將錯誤引入原本已經測試過的舊程式碼中,破壞原有系統。因此,當軟體需求發生變化的時候,我們應該優先考慮通過擴充套件的方式來實現變化,而不是通過修改已有程式碼來實現。

當然實際開發中擴充套件和修改是同時存在的。應該儘量少的去修改程式碼,想法去擴充套件程式碼。

《物件導向軟體構造》一書中提到這一原則---開閉原則。這一想法認為,程式一旦開發完成,程式中的一個類的實現只應該因錯誤而被修改,新的或者改變的特性應該通過新建不同的類實現,新建的類可以通過繼承的方式來重用原有類。

舉個簡單的例子:

public class Hello{
  BlackPen pen = new BlackPen();
  
  void writeHello(){
    pen.write("hello world");
  }
}

// Pen 類可以寫出字
public class BlackPen{
  public void write(String content){
    System.out.println("content");
  }
}

上面這個程式中我們可以通過 BlackPen 類寫出字,有一天需求變了要求寫出紅色的字。

public class Hello{
  BlackPen pen = new BlackPen();
  RedPen redPen = new RedPen();
  void writeHello(String flag){
    switch(flag){
        "XXX":
        	pen.wiite("hello world");
        "YYY":
        	redPen.write("hello world")
    }
  }
}

// BlackPen 類可以寫出黑字
public class BlackPen{
  public void write(String content){
    System.out.println(content);
  }
}

// RedPen 類可以寫出紅字
public class RedPen{
  public void write(String content){
    System.out.println(content);
  }
}

這樣寫通過 switch 來判斷要呼叫那一個,如果繼續新增其他顏色的筆就繼續新增。這樣貌似不錯。但是試想 Hello 是你提供給別人的一個框架,那麼別人想要繼續新增可以寫出黃色的 Hello Wrold ,是不是就沒有辦法了,非得讓你去修改 Hello 方法才可以,沒有了擴充套件性。

現在優化成

public class Hello{
  Pen pen = new BlackPen();
  public void setPen(Pen pen){
    this.pen = pen;
  }
  void writeHello(){
 		pen.write("hello world")
  }
}

public interface Pen{
  write(String content);
}

// BlackPen 類可以寫出黑字
public class BlackPen implement Pen{
  public void write(String content){
    System.out.println(content);
  }
}

// RedPen 類可以寫出紅字
public class RedPen implement Pen{
  public void write(String content){
    System.out.println(content);
  }
}

這樣就可以擴充套件而不用修改 Hello 內的程式碼了。

開閉原則,對修改關閉,對擴充套件開放。並不是說完全的不能修改,比如上面內容,一開始只有一個 BlackPen 的時候,你沒有想到擴充套件,可以那樣寫,但是隨著業務變化,出現了不同的 Pen。這個時候就需要考慮 Pen 要有可擴充套件性。就不能重複的在 Hello 類中不斷去修改了。而是換一種思路,讓其變得具有可擴充套件。

好處

可以使用我們的程式更加穩定,避免修改帶來的錯誤,增加可擴充套件性。當一個類中的業務不斷的發生變化需求,不斷的增加業務判斷,就需要考慮到擴充套件性了。

里氏替換原則

構建擴充套件性更好的系統

概念

所用引用基類的地方必須能透明地使用其子類的物件。

只要父類能出現的地方,子類就可以出現,而且替換為子類也不會產生任何錯誤或者異常,使用者可能根本就不需要知道是父類還是子類。

里氏替換原則就是依賴於繼承、多型這兩大特性。

其實就是將依賴變成抽象,不依賴於具體的實現。

比如:

public class Window{
  public void show(View child){
    child.draw();
  }
}

public abstract class View{
  public abstract void draw();
  
  public void measure(int width,int height){
    // 測量檢視大小
  }
}

public class Button extends View{
  public void draw(){
    // 繪製按鈕
  }
}

public class TextView extends View{
  public void draw(){
    // 繪製文字
  }
}

Window 是依賴於 View 的,是一個抽象類,Window 是依賴於一個抽象,而不是具體的物件。這個時候傳入任何 View 的具體物件都是可以的。

好處

提高擴充套件性,使其不依賴具體的實現,依賴抽象。

依賴倒置原則

讓專案擁有變化的能力

概念

依賴倒置原則指代了一種特定的解耦形式,使得高層次的模組不依賴於低層的模組的實現細節,依賴模組被顛倒了。

依賴倒置的關鍵點:

  • 高層模組不應該依賴低層模組,兩者應該依賴其抽象
  • 抽象不應該依賴細節
  • 細節應該依賴抽象

在 Java 語言中,抽象就是指介面或抽象類。兩者都是不能直接被例項化的;細節就是實現類,實現介面或者繼承抽象類而產生的類就是細節。

高層模組就是呼叫端,低層模組就是具體的實現類。也就是呼叫端不要依賴具體的實現類,而是通過依賴抽象的方式。其實就是面向介面程式設計。

其實和上面里氏替換原則類似

好處

降低耦合性,不依賴具體細節,依賴抽象,提高可擴充套件性

介面隔離原則

系統有更好的靈活性

概念

客戶端不應該依賴它不需要的介面。另一種定義:類間的依賴關係應該建立在最小的介面上。介面隔離原則將非常龐大、臃腫的介面拆分成更小的和更具體的介面,這樣客戶將會只需要知道他們感興趣的方法。介面隔離原則的目的是系統解開耦合,從而容易重構、更改和重新部署。

其實就是讓一個介面儘可能的小,方法少,使使用者使用起來方便。

比如:一個物件實現了多個介面,有個介面是關閉功能,那麼當這個物件想要關閉的時候,呼叫關閉方法就可以了,因為它實現了多個介面,有多個方法,呼叫的時候就暴露了其他介面函式。這個時候我們僅需要它暴露關閉的介面就可以了,隱藏其他介面資訊。

好處

使用起來更加方便靈活

迪米特原則

概念

迪米特原則也稱為最少知道原則。一個物件應該對其他物件有最少的瞭解。通俗地講,一個類應該對需要耦合或呼叫的類知道得最少,類的內部如何實現與呼叫者或者依賴者沒有關係,呼叫者或者依賴者只需要知道它的需要的方法就可以了,其他的可一概不管。類與類之間關係越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。

迪米特原則還可以解釋為:只與直接朋友通訊。

也就是說,應該儘可能少的與別的朋友通訊,僅與最直接的朋友通訊。

兩個物件成為朋友的方式有多種:組合、聚合、依賴等等。

好處

降低依賴、使用簡單

總結

這六大原則不是相互獨立的,而是互相融合,你中有我,我中有你。

單一職責告訴我們要儘量的分離程式碼,不斷的精分程式碼,不同的模組實現不同的功能。這樣不會所有功能都融合在一塊,方便閱讀、維護程式碼。

開閉原則、里氏替換原則、依賴倒置原則:本質上都是通過抽象來提高擴充套件性。不依賴具體的實現而依賴抽象,就會增加很多擴充套件性,抽象可以有需要不同的實現。

介面隔離原則:和單一職責有類似,就是通過介面細分化,暴露最少的方法。要想有某個功能,只需要實現這個介面就可以了,與其他介面無關。

迪米特原則:儘量依賴更少的類,儘量對外界暴露更少的方法

實現這六大原則主要是通過面向介面程式設計,面向抽象程式設計。不依賴具體的實現。每個類都有一個抽象(抽象類、介面)。當高層級模組需要依賴這個類的時候,依賴它的抽象,而不是具體。這個時候就可以靈活的改變其實現了。

相關文章