網頁

2021/1/9

Java SOLID Single Responsibility Principle 單一職責原則

Single Responsibility Principle (SRP) 單一職責原則。

SRP的定義如下:

A class should only have a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class

意思為「一個類別應該只有單一職責。一處改變只能影響一個類別」。

在『無瑕的程式碼 - 整潔的軟體設計與架構篇』一書中Robert C. Martin對SRP的描述如下。

A module/class should have one and only one reason to change

「一個模組/類別應該有一個且只有一個理由會改變」。

以前誤認為SRP是「一個方法應該只做一件事」,但應改為上述定義。

SRP代表著高內聚力(high cohesive),應該把同需求單位依賴的功能集中到同個類別(或模組),意思相當於一個類別僅對一個需求單位負責。

SRE的好處是避免程式因為一處修改而對另一處造成影響。

下面的Employee類別說明違反SRE造成的問題。Employee包含兩個方法,分別來自會計與HR單位的需求:

  • calculatePay():計算薪資 - 會計需求
  • reportHours():產生工時報表 - HR需求

Employee

class Employee {
    public void calculatePay() {
        ...
        double regularHours = regularHours();
        ...
    }

    public void reportHours() {
        ...
        double regularHours = regularHours();
        ...
    }

    private double regularHours() {
        // 計算一般工時
    }
}

兩個方法同時依賴regularHours()計算工時。若今天會計要求修改計算薪資calculatePay()時計算工時的方式而需要修改regularHours()的邏輯,但產生工時報表reportHours()也依賴regularHours()計算工時,若沒注意會造成產生報表的結果是錯誤的。

因此把不同責任的方法切分到不同類別,並將計算邏輯與資料分開。
calculatePay()對會計負責,拆到CalculatePayService

CalculatePayService

/** 計算薪資 service */
class CalculatePayService {
    public void calculatePay(EmployeeData employeeData) {
        ...
        double regularHours = regularHours(employeeData);
        ...
    }

    private double regularHours(EmployeeData employeeData) {
        // 計算會計一般工時
    }
}

reportHours()對HR負責,拆到ReportHoursService。擁有各自的計算工時邏輯。

ReportHoursService

/** 產生工時報表 service*/
class ReportHoursService {
    public void reportHours(EmployeeData employeeData) {
        ...
        double regularHours = regularHours(employeeData);
        ...
    }

    private double regularHours(EmployeeData employeeData) {
        // 計算HR一般工時
    }
}

EmployeeData則單純裝載資料不含任何邏輯。

EmployeeData

class EmployeeData {
    // attributes
}

如此各類別專職於所屬的責任,當一方修改時不會造成另一個無相關的功能受到影響。

SRE最困難的地方就在於職責的劃分及粒度的切分,是要依需求單位、功能性或開發人員職責來拆?寫出報表時的資料轉換、寫出檔案及寫出位置等是否要拆分為獨立的類別?這些問題只能依當下程式的規格來決定,程式開發者很難預測未來的需求是否會擴大或朝什麼方向變更,過早拆分或拆分太細反而令程式變得過度設計(Overengineering),設想不存在的需求而實作出的彈性設計或許未來反而難以維護。

上面的例子在會計部門修改規則前把計算工時的方法集中在一處似乎也沒麼問題。如果今天是整個公司計算工時的方法都一致且要同時修改,那就只要修改一支計算工時的邏輯即可。也就是說新需求的出現讓以前沒問題的設計反而變得有問題,所以程式必須隨著需求持續調整及重構才能滿足SRP,讓程式繼續保持容易維護的狀態。


沒有留言:

張貼留言