你一定經常見到一個兩三千行的 controller 類,類之所以發展成如此龐大,有如下原因:
- 長函數太多
- 類裏面有特別多的字段和函數
量變引起質變,可能每個函數都很短小,但數量太多
1 程序的modularity
你思考過爲什麼你不會把all code寫到一個文件?因爲你的潛意識裏明白:
- 相同的功能模塊無法複用
- 複雜度遠超出個人理解極限
一個人理解的東西是有限的,在國內互聯網敏捷開發環境下,更沒有人能熟悉所有代碼細節。
解決複雜的最有效方案就是分而治之。所以,各種程序設計語言都有自己的模塊劃分(modularity)方案:
- 從最初的按文件劃分
- 到後來使用OO按類劃分
開發者面對的不再是細節,而是模塊,模塊數量顯然遠比細節數量少,理解成本大大降低,開發效率也提高了,再也不用 996, 每天都能和妹紙多聊幾句了。
modularity,本質就是分解問題,其背後原因,就是個人理解能力有限。
說這麼多我都懂,那到底怎麼把大類拆成小類?
2 大類是怎麼來的?
2.1 職責不單一
最容易產生大類的原因。
CR一段代碼:
該類持有大類的典型特徵,包含一坨字段:這些字段都缺一不可嗎?
- userId、name、nickname等應該是一個用戶的基本信息
- email、phoneNumber 也算是和用戶相關聯
很多應用都提供使用郵箱或手機號登錄方式,所以,這些信息放在這裏,也能理解 - authorType,作者類型,表示作者是簽約作者還是普通作者,簽約作者可設置作品的付費信息,但普通作者無此權限
- authorReviewStatus,作者審覈狀態,作者成爲簽約作者,需要有一個申請審覈的過程,該狀態字段就是審覈狀態
- editorType,編輯類型,編輯可以是主編,也可以是小編,權限不同
這還不是 User 類的全部。但只看這些內容就能看出問題:
- 普通用戶既不是作者,也不是編輯
作者和編輯這些相關字段,對普通用戶無意義 - 對那些成爲作者的用戶,編輯的信息意義不大
因爲作者不能成爲編輯。編輯也不會成爲作者,作者信息對成爲編輯的用戶無意義
總有一些信息對一部分人毫無意義,但對另一部分人又必需。出現該問題的癥結在於只有“一個”用戶類。
普通用戶、作者、編輯,三種不同角色,來自不同業務方,關心的是不同內容。僅因爲它們都是同一系統的用戶,就把它們都放到一個用戶類,導致任何業務方的需求變動,都會反覆修改該類,嚴重違反單一職責原則。
所以破題的關鍵就是職責拆分。
雖然這是一個類,但它把不同角色關心的東西都放在一起,就愈發得臃腫了。
只需將不同信息拆分即可:
public class User {
private long userId;
private String name;
private String nickname;
private String email;
private String phoneNumber;
...
}
public class Author {
private long userId;
private AuthorType authorType;
private ReviewStatus authorReviewStatus;
...
}
public class Editor {
private long userId;
private EditorType editorType;
...
}
拆出 Author、Editor 兩個類,將和作者、編輯相關的字段分別移至這兩個類裏。
這倆類分別有個 userId 字段,用於關聯該角色和具體用戶。
2.2 字段未分組
有時覺得有些字段確實都屬於某個類,結果就是,這個類還是很大。
之前拆分後的新 User 類:
public class User {
private long userId;
private String name;
private String nickname;
private String email;
private String phoneNumber;
...
}
這些字段應該都算用戶信息的一部分。但依然也不算是個小類,因爲該類裏的字段並不屬於同一種類型的信息。
如,userId、name、nickname算是用戶的基本信息,而 email、phoneNumber 則屬於用戶的聯繫方式。
需求角度看,基本信息是那種一旦確定一般就不變的內容,而聯繫方式則會根據實際情況調整,如綁定各種社交賬號。把這些信息都放到一個類裏面,類穩定程度就差點。
據此,可將 User 類的字段分組:
public class User {
private long userId;
private String name;
private String nickname;
private Contact contact;
...
}
public class Contact {
private String email;
private String phoneNumber;
...
}
引入一個 Contact 類(聯繫方式),把 email 和 phoneNumber 放了進去,後面再有任何關於聯繫方式的調整就都可以放在這個類裏面。
此次調整,把不同信息重新組合,但每個類都比原來要小。
前後兩次拆分到底有何不同?
- 前面是根據職責,拆分出不同實體
- 後面是將字段做了分組,用類把不同的信息分別封裝
大類拆解成小類,本質上是個設計工作,依據單一職責設計原則。
若把大類都拆成小類,類的數量就會增多,那人們理解的成本是不是也會增加呢?
這也是很多人不拆分大類的藉口。
各種程序設計語言中,本就有如包、命名空間等機制,將各種類組合在一起。在你不需要展開細節時,面對的是一個類的集合。
再進一步,還有各種程序庫把這些打包出來的東西再進一步打包,讓我們只要面對簡單的接口,而不必關心各種細節。
軟件正這樣層層封裝構建出來的。