「在函式里,我们计算程序行数,来衡量函式的大小;在类别里,我们使用不同的量测方式,我们计算职责的数量」
取自: 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)
注: 笔者发现中文书的翻译与顺序和原文有些许出入,附上原文:
>>: Day 22-state manipulation 之四:让 terraform 遗忘过去的 state rm
前言 JavaScript 的语句分成两种 陈述式、表达式,这两种语法区分并不困难,接下来会一一介绍...
昨天我们通过Unity官方提供的"UnityDownloadAssistant-20xx....
最近,我偶然发现了一个软件,"Visited",一个建立在Node.js上的开源...
(不写code,有点不懂但看起来超酷的呈现方式) 先小小悼念一下作者伊藤计划,天才英年早逝,如果仍...
前言:相信大家对於「树」都不陌生,资料结构中的树其实是模拟现实生活中的树干、树枝和叶子,相当於树状结...