一、概念
單一職責:Single Responsibility Principle,簡稱SRP。字面義很簡單,但是做起來有的時候真的蠻難的。就是小到一個接口只幹一件事,大到一個類、一個模塊只負責完成一個功能。
比如:UserService裏面既包含用戶賬號密碼登錄註冊等操作又包含用戶積分等操作。這種情況就很明顯的違背了單一職責原則,需要將其拆分爲UserService和ScoreService。
二、如何確定是否單一職責
1、描述
上面那麼明顯的demo就不多說了,但是一般都會出現摸棱兩可的情況,比如下面的UserInfo,這就需要憑藉個人經驗和實際業務需求來評估了。
public class UserInfo {
private long userId;
private String username;
private String email;
private String telephone;
private long createTime;
private long lastLoginTime;
private String avatarUrl;
private String provinceOfAddress; // 省
private String cityOfAddress; // 市
private String regionOfAddress; // 區
private String detailedAddress; // 詳細地址
// ...省略其他屬性和方法...
}
這段代碼符合單一職責嗎?有的人說符合,有的人說不符合,應該把省市區詳細地址單獨抽出來放到一個類裏。
哪種人說的對?我覺得這需要區分業務,如果你的產品業務的用戶信息字段只是負責顯示,沒其他邏輯。那這一個UserInfo.java就足夠了,如果你不光有用戶系統,還有積分兌換系統,兌換的實物需要發到你的省市區詳細地址,這種情況我認爲UserInfo.java
結合UserAddress.java
兩個類比較合適。因爲地址信息不光顯示用,物流發貨也需要用。
所以我覺得,這就是取決於產品業務的,當然特別明顯的違背了單一職責原則的代碼一眼就看得出,只有這種摸棱兩可的代碼需要取決於業務。但我覺得沒必要過度設計,如果你係統有這個需求或者近期可能會出現這個需求,那就拆分。如果近期不會這麼搞,我覺得先弄一個類,到時候重構就完了,不要過度設計。
2、總結
總結評判是否符合單一職責原則的幾個技巧
- 類的代碼行數、函數、屬性過多的時候。多少算多?個人建議類行數不過300行、函數不過50行、函數和屬性的個人不多於10個。
- 類依賴的其他類過多,不符合高內聚低耦合的思想,需要拆分。比如一個service層又依賴了其他七八個service層才能完成某個業務。
- 私有方法過多,可以考慮放到其他類里弄成public公用。提高複用性。
- 根據業務場景來適當拆分,這句話好像是廢話…這裏想說的是那個UserInfo和UserAddress的取捨。
三、切勿過度設計
千萬不要爲了單一而過度單一。比如Serialization實現了序列化和反序列化的功能。
public class Serialization {
private static final String IDENTIFIER_STRING = "CTW";
private Gson gson;
public Serialization() {
this.gson = new Gson();
}
/**
* 序列化
*/
public String serialize(Map<String, String> object) {
StringBuilder textBuilder = new StringBuilder();
textBuilder.append(IDENTIFIER_STRING);
textBuilder.append(gson.toJson(object));
return textBuilder.toString();
}
/**
* 反序列化
*/
public Map<String, String> deserialize(String text) {
if (!text.startsWith(IDENTIFIER_STRING)) {
return Collections.emptyMap();
}
String gsonStr = text.substring(IDENTIFIER_STRING.length());
return gson.fromJson(gsonStr, Map.class);
}
}
上面的設計很合理,也很完美,完全符合上面【如何確定是否單一職責】的描述。但是如下代碼就過度設計了,他將序列化和反序列化拆分成兩個Java類。
/**
* 序列化
*/
public class Serializer {
private static final String IDENTIFIER_STRING = "CTW";
private Gson gson;
public Serializer() {
this.gson = new Gson();
}
public String serialize(Map<String, String> object) {
StringBuilder textBuilder = new StringBuilder();
textBuilder.append(IDENTIFIER_STRING);
textBuilder.append(gson.toJson(object));
return textBuilder.toString();
}
}
/**
* 反序列化
*/
public class Deserializer {
private static final String IDENTIFIER_STRING = "CTW";
private Gson gson;
public Deserializer() {
this.gson = new Gson();
}
public Map<String, String> deserialize(String text) {
if (!text.startsWith(IDENTIFIER_STRING)) {
return Collections.emptyMap();
}
String gsonStr = text.substring(IDENTIFIER_STRING.length());
return gson.fromJson(gsonStr, Map.class);
}
}
乍一看好像是更加符合單一職責了,其實是典型的過度設計。首先完全沒必要,照這個思路的話,那所有的類裏面都只會包含一個方法了,其次他會引入新的問題,若我們修改了協議,數據標識從CTW變成了HELLO,或者序列化的方式從JSON換成了XML等,那Serializer和Deserializer兩個類都需要做相應的修改。顯然沒有第一種方式的內聚性高,主要是我們如果只修改了Serializer的CTW->HELLO,忘記了反序列化的類的修改,這時候會導致序列化、反序列化不一致,報錯。(我知道你能將CTW放到公用常量類裏去解決,我只想突出問題,說明這是過度設計)。