「在函式裡,我們計算程式行數,來衡量函式的大小;在類別裡,我們使用不同的量測方式,我們計算職責的數量」
取自: Clean Code (p.152)
public class EmployeeUtils {
// 取資料
public void FetchEmployeeDetails(string employeeId)
// 存資料
public void SaveEmployeeDetails(EmployeeModel employeeDetails)
// 驗證資料
public void ValidateEmployeeDetails(EmployeeModel employeeDetails)
// 輸出資料
public void ExportEmpDetailsToCSV(EmployeeModel employeDetails)
// 引入資料
public void ImportEmpDetailsForDb(EmployeeModel employeeDetails)
// 員工資料細節
private class EmployeeModel {
public string EmployeeId;
public string EmployeeName;
public string EmpplyeeAddress;
public string EmployeeDesignation;
public double EmployeeSalary;
}
}
上述的類別有 5 個職責,有可能導致日後的維護性下降 (最理想的狀況是 1 個)注意: 不要把它跟「函式只做一件事」搞混!
P.S. 關於 SOLID 設計原則在之後介紹 Clean Architecture 時,筆者會再次介紹
public class Stack
{
private int topOfStack = 0;
List<Integer> elements = new LinkedList<Integer>();
public int size() {
return topOfStack;
}
public void push(int element) {
topOfStack++;
elements.add(element);
}
public int pop() throws PoppedWhenEmpty {
if (topOfStack == 0)
throw new PoppedWhenEmpty();
int element = elements.get(--topOfStack);
elements.remove(topOfStack);
return element;
}
}
上例中只有 Size() 沒有同時使用到類別的 2 個變數,這是一個非常有凝聚力的類別
違反 SRP 的 SQL 類別
public class Sql {
public Sql(String table, Column[] columns)
public String create()
public String insert(Object[] fields)
public String selectAll()
public String findByKey(String keyColumn, String keyValue)
public String select(Column column, String pattern)
public String select(Criteria criteria)
public String preparedInsert()
private String columnList(Column[] columns)
private String valuesList(Object[] fields, final Column[] columns)
private String selectWithCriteria(String criteria)
private String placeholderList(Column[] columns)
}
以上例來說,當我們想要新增指令 (e.g., Delete)、或者修改某指令的細節,都需要更動到此類別。很明顯地這個類別有超過 1 個以上的修改理由
那麼,何時該做職責拆解?
想讓系統的每一個類別都符合 SRP 原則並不是一件輕鬆的事,且可能會流於過度設計 (Over-Design)。所以關鍵在於,未來更動或新增 SQL 類別的機會多不多? 若未來須新增 Update 功能,就是一個修補設計的好機會
重構後的 SQL 符合「單一職責原則 (SRP)」 和 「開放封閉原則 (OCP)」
abstract public class Sql {
public Sql(String table, Column[] columns)
abstract public String generate();
}
public class CreateSql extends Sql {
public CreateSql(String table, Column[] columns)
@Override public String generate()
}
public class SelectSql extends Sql {
public SelectSql(String table, Column[] columns)
@Override public String generate()
}
public class InsertSql extends Sql {
public InsertSql(String table, Column[] columns, Object[] fields)
@Override public String generate()
private String valuesList(Object[] fields, final Column[] columns)
}
public class SelectWithCriteriaSql extends Sql {
public SelectWithCriteriaSql(
String table, Column[] columns, Criteria criteria)
@Override public String generate()
}
public class SelectWithMatchSql extends Sql {
public SelectWithMatchSql(
String table, Column[] columns, Column column, String pattern)
@Override public String generate()
}
public class FindByKeySql extends Sql
public FindByKeySql(
String table, Column[] columns, String keyColumn, String keyValue)
@Override public String generate()
}
public class PreparedInsertSql extends Sql {
public PreparedInsertSql(String table, Column[] columns)
@Override public String generate() {
private String placeholderList(Column[] columns)
}
public class Where {
public Where(String criteria)
public String generate()
}
public class ColumnList {
public ColumnList(Column[] columns)
public String generate()
}
重構後雖然多了許多程式碼,但我們可以發現每一個小功能的可讀性都大大地上昇了,且函式之間幾乎沒有任何耦合,這也使得測試程式變得更容易撰寫。而當我們想新增指令時,只要新增一個子類別即可,沒有任何既有的程式碼會被更動
「整潔的程式碼幫助我們在較低抽象層次上,達成這個目標。在本章中,讓我們來思考該如何在較高的抽象層次,達成整潔的目標」
取自: Clean Code (p.170)
public Service getService() {
if (service == null)
service = new MyServiceImpl(...);
return service;
}
上例是一個很經典的初始化方式[3],當物件要被使用的前一刻才進行實例化 (Instantiation)。這麼做的好處除了撰寫方便外,也能增進系統效能Kent Beck's 簡單設計四守則 (Four Rules of Software Design)
遵守以下四個守則就能更容易使軟體善用「單一職責原則 (SRP)」及「相依性反向原則 (DIP)」
- 執行完所有的測試 => Passes the tests
- 表達程式設計師的本意 => Reveals intention (should be easy to understand)
- 沒有重複的部分 => No duplication (DRY)
- 最小化類別和方法的數量 => Fewest elements
取自: Clean Code (p.190)
註: 筆者發現中文書的翻譯與順序和原文有些許出入,附上原文: