1. 程式人生 > 實用技巧 >[轉]Java面向物件的七大原則

[轉]Java面向物件的七大原則

前言

其實沒有設計模式我們也能完成開發工作。但是為什麼需要設計模式呢?讓你看起來很牛,沒錯這個算一個。讓你的程式碼層次感分明,可讀性強而且容易維護。讓你像我一樣有更多的摸魚划水時間。
可能有人說我一個類或者方法就幹完的東西,你搞了七八個。當然使用設計模式也是要斟酌的。一些簡單穩定的業務也不推薦使用設計模式。設計模式多用於複雜多變的業務或者要求適配性、擴充套件性更強的場景中。不要為了設計模式而設計模式。

接下來我們結合實際開探討一下設計模式的一些原則。

1、開閉原則

public class Seller {
    public BigDecimal sellCar(Car car) {
        return car.getPrice();
    }
}

上面模擬4S店一個銷售在賣車。突然老闆搞了一個促銷:在雙十一要開展打折活動。在sellCar方法內增加一個計算可行嗎?這勢必影響整個業務,導致所有車都打折。不行不行!那麼在Car裡面操作?然後你改啊改!結果各種邏輯流程判斷。才實現了業務要求。如果後續打折活動結束了或者升級了,你還要再進行各種改動。你發現一個打折讓你的程式碼面目全非、臃腫不堪。上面說了對於複雜而多變的業務使用設計模式就可以解決。
那麼設計模式最重要的一個原則就是開閉原則。也就是說一個軟體模型實體如類、模組和函式應該對擴充套件開放,對修改關閉。也就是需要我們將業務行為抽象出來,使用抽象來構建。具體的業務通過抽象的實現來解決。那麼我們就搞一個DiscountCar來extends Car.這樣sellCar是什麼具體的實現就執行什麼具體的邏輯。不會影響以前的邏輯,而且不會因為改動原來的程式碼影響其他邏輯。保證介面可靠性和穩定性。如下:

public class DiscountCar extends Car{
       
   private BigDecimal price;
   private BigDecimal discount;
   @Override
   public BigDecimal getPrice() {
       return price.multiply(discount);
   }
}

2、依賴倒置原則

還拿上面的例子來說。經過一系列的打折活動4S店的生意蒸蒸日上。老闆突然想擴充套件一下週邊,同時壓榨一下銷售。讓他們賣車的同時賣點玻璃水、防凍液之類的。這個需求當然又拋給了苦逼的程式設計師。sellCar太具體了不能滿足需要了。很多情況下你會增加一個賣玻璃水、賣防凍液的方法。如果以後增加了賣大米,甚至買起了雞蛋餅呢?總不能一直增加方法吧。我們需要考慮這種問題。我們可以抽象所有賣東西的場景。然後我們把賣的物品抽象成了一個抽象化的概念(java對應的是介面,把賣的行為抽象成了sell方法:

public interface Any {
String getName();
BigDecimal getPrice();
}
public class Seller {
public BigDecimal sell(Any any) {
return any.getPrice();
    }
}

這樣隨便老闆以後賣什麼你都可以通過該方法進行處理了,只需要關注於Any的實現。

3、職責單一原則

4S店銷售賣了一段東西后,發現對客戶的吸引力度不大。突然腦子比較靈活的老闆又想起了電影中的一句臺詞:少林功夫加唱歌跳舞有沒有搞頭?對啊你們銷售能不能搞搞什麼唱、跳、Rap,當然打籃球就不要了別砸壞了車玻璃。但是人與人是不一樣的,有的人只會唱,有的人只會跳,有的人可能唱跳Rap都會甚至籃球都很溜。所以為了適配這麼多情況,我們必須把每種技能獨立出來,根據不同的人來組合這些技能。

public class Seller implements Sing, Jump, Rap {
public BigDecimal sell(Any any) {
return any.doSell();
    }
@Override
public void sing() {
        System.out.println("seller sing ");
    }
@Override
public void jump() {
        System.out.println("seller jumping ");
    }
@Override
public void rap() {
        System.out.println("seller raping ");
    }
}

但是注意一定要適度,根據業務來細分。否則會導致介面過多反而增大開發難度以及程式碼的複雜度。

4、迪米特法則

新的銷售方法搞了一段時間後,老闆想看看檢驗一下他這個餿主意的效果。於是就叫了一個銷售讓他提供一份報表出來看看。那麼程式設計師該如何實現老闆檢視報表功能呢,很可能有人會這麼寫:

public class Boss {
    private Seller seller;
    private Report report;
    public void read() {
        seller.apply(report);
    }
}

乍看功能實現了,細看會發現邏輯不對。哪裡不對呢?老闆已經持有了報表,如果老闆已經知道了你的業績還叫你幹什麼?這種邏輯肯定是不對的!也就是說Boss直接依賴了Report;而這個Report不應該直接由Boss處理,而應由Seller控制。

public class Boss {
    private Seller seller;
    
    public void read(){
         seller.apply();
    }   
}
public class Seller {
      private Report report;
      
      public void apply(){
         report.show();
      }
}

這種最大化隔離了類與類之間的關係。降低了類之間的耦合。迪米特法則因此又得名最少知道原則。

5、介面隔離原則

用多個專門的介面,而不使用單一的總介面,客戶端不應該依賴它不需要的介面。一個類對一個類的依賴應該建立在最小的介面上。
建立單一介面,不要建立龐大臃腫的介面儘量細化介面,介面中的方法儘量少,儘量細化介面。注意適度原則,一定要適度。不能濫用
就像上面的唱跳 rap,分離是最好的。

6、里氏代換原則

這裡主要針對類的繼承關係而言。比較正式的定義:如果對每一個型別為S的物件o1,都有型別為T的物件o2,使得以T定義的所有程式P在所有的物件o1都代換成o2 時,程式P的行為沒有發生變化,那麼型別 S 是型別 T 的子型別。
在4S店老闆眼裡,只要新來的能在銷售崗位上像銷售老手一樣賣出汽車,他就是一名合格的銷售。感覺這種定義就像一句名言:不管你黑貓白貓,能抓老鼠的都是好貓。
從某種含義上里氏代換有著以下的契約:

  1. 子類必須完全實現父類的方法。父類出現的地方子類都可以代替父類。
  2. 子類可以有自己的個性定義。里氏替換原則 可以正著用,但是不能反過來用。在子類出現的地方,父類未必就可以勝任。子類一般比父類有個性。
  3. 覆蓋或實現父類的方法時輸入引數可以被放大。如果4S店老闆規定基礎車談價的折扣最多九折,銷售打個九五折沒有問題,打八折老闆肯定要跟你說道說道了。
  4. 覆寫或實現父類的方法時輸出結果可以被縮小。同樣是15W本來只能賣出給客戶一個乞丐版,結果換了個銷售結果給出了一輛旗艦版。怕不是過不了試用期哦。

7、合成/複用原則

它要求在軟體複用時,要儘量先使用組合或者聚合等關聯關係來實現,其次才考慮使用繼承關係來實現。
如果要使用繼承關係,則必須嚴格遵循里氏替換原則。合成複用原則同里氏替換原則相輔相成的,兩者都是開閉原則的具體實現規範。
歡迎大家關注我的公種浩【程式設計師追風】,文章都會在裡面更新,整理的資料也會放在裡面。

總結

這七種設計原則是軟體設計模式必須儘量遵循的原則,各種原則要求的側重點不同。其中,開閉原則是總綱,它告訴我們要對擴充套件開放,對修改關閉;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向介面程式設計;單一職責原則告訴我們實現類要職責單一;介面隔離原則告訴我們在設計介面的時候要精簡單一;迪米特法則告訴我們要降低耦合度;合成複用原則告訴我們要優先使用組合或者聚合關係複用,少用繼承關係複用。在實際開發中我們可以根據業務來進行設計模式的使用,但是很重要的一點千萬不要被這些條條框框束縛了你的手腳。