單一職責原則 | Single Responsibility Principle

Posted by Ian Tsai on Thursday, September 10, 2020

本系列文章同步分享於IT邦幫忙第12屆鐵人賽


Design Pattern 系列文章導讀


Design Pattern可以說是開發上大家都會遇到的一個課題, 這系列文會從Design Principles、各種design pattern到最後的Anti-Patterns & Code Smells介紹下去,讓我們可以更了解各種pattern的使用時機與場合。 預計目標主題如下,若有哪部分不熟的章節可以直接點進去看
註:可以利用Online Java Compiler IDE

  1. 設計模式 - 入門篇
  2. 設計模式 - 原則篇 | Design Principles
  3. 設計模式 - 創建型模型篇 | Creational Patterns
  4. 設計模式 - 結構型模型篇 | Structural Patterns
  5. 設計模式 - 行為型模型篇 | Behavioural Patterns
  6. 設計模式 - 番外篇
  • 定義


There should never be more than one reason for a class to change.

翻譯年糕:一個類別只能有一個改變的原因。

什麼叫一個原因?是指一個class只能做一件事嗎? 我們舉一個生活化的例子:購物車!!

當我們在購物,決定要將購物車內容結帳時會經由以下步驟:確認訂單、確認聯絡資料、確認收貨地點、付費。用程式呈現方式如下:

interface IShoppingCart{
     void checkOrder();
     void checkContactInfo();
     void checkPlace();
     void paid();
}
class ShoppingCart implements IShoppingCart{
    @Override
    public void checkOrder(){
        System.out.println("請確認訂單");
    };
    @Override
    public void checkContactInfo(){
        System.out.println("請聯絡資料");
    };
    @Override
    public void checkPlace(){
        System.out.println("請收貨地點");
    };
    @Override
    public void paid(){
        System.out.println("請付款");
    };
}
public class MyCart {
    public static void main(String args[]) {
        ShoppingCart cart = new ShoppingCart();
        cart.checkOrder();
        cart.checkContactInfo();
        cart.checkPlace();
        cart.paid();
    }
}

output

請確認訂單
請聯絡資料
請收貨地點
請付款

這是我們在購物會經過的流程。可以發現,shoppingCart內包含了兩個職責,確認資料以及付費。確認訂單、確認聯絡資料與確認收貨地點都是在檢查這筆訂單的資訊是否正確。而付費則是繳清這筆訂單所需的金額,很明顯地與其他三者的職責不同。若購物車需要有更多元的付費方法,比方說加入電子支付,又或是確認資料的功能要做更改,都會需要更動到整個interface,這樣可能會影響到其他不需要更動的功能。

  • 使用 Single Responsibility Principle


一開始我們提到 一個類別只能有一個改變的原因。,也就是說我們只希望這個interface只因為一個原因(例如:異動付款相關功能)才做異動,所以我們就將這兩個職責劃分成兩類:

public interface CheckData{
    void checkOrder();
    void checkContactInfo();
    void checkPlace();
}

public interface Payment{
    void paid();
}

public class ShoppingCart implements CheckData, Payment{
    @Override
    public void checkOrder(){
        System.out.println("請確認訂單");
    };
    @Override
    public void checkContactInfo(){
        System.out.println("請聯絡資料");
    };
    @Override
    public void checkPlace(){
        System.out.println("請收貨地點");
    };
    @Override
    public void paid(){
        System.out.println("請付款");
    };
}
public class MyCart {
    public static void main(String args[]) {
        ShoppingCart cart = new ShoppingCart();
        cart.checkOrder();
        cart.checkContactInfo();
        cart.checkPlace();
        cart.paid();
    }
}

這樣的設計就達到了一個interface只有一個職責的原則。需要增加付款方式就改動Payment,需要更改確認資料內容就更改CheckData。如此一來就符合SRP的目標一個類別只能有一個改變的原因。

SRP與ISP的差異

看完這SRP與IRP兩篇的人可能會覺得,SRP跟IRP不都是拆分interface嗎?那有什麼不同?

SRP的目的在於一個模組只能有承擔一個責任。
ISP的目的在於不強迫實作的類別實作不需要的function。

我們可以用不同的角度去看這件事。

SRP注重的是設計層面,如何劃分功能的歸類,讓程式方便維護及擴充。
ISP則是注重在客戶端層面,認為只需要呈現客戶端所需要的功能即可。
  • 小結


  1. SRP的目的:

    實現高內聚,將不相關的程式碼移除,使得整體程式碼中的每個部分都與自己實作的功能相關。

  2. SRP的優缺點比較

優點 缺點
- 降低類別複雜度。- 提高可讀性及維護性。- 變更引起的風險降低。 - 功能類別的歸類困難,依照不同情境有不同的劃分方法。
  • 範例程式碼


範例1:未使用SRP

範例2:使用SRP

  • References


  • 設計模式五大基本原則 SOLID
  • [Design Pattern] 單一職責原則 (Single Responsibility Principle)