關於上帝類
神說:“要有光”,就有了光。——《聖經》。上帝要是會寫程序,他寫的類一定是“上帝類”。程序員不是上帝,不要妄想成為上帝,但程序員可以寫出“上帝類”。上帝是唯一的,上帝的光芒照耀人間,上帝是很愛面子的,他知道程序員寫了“上帝類”,搶了他的風頭,於是他降下神罰要懲戒程序員。——既然你寫了“上帝類”,那么就將你流放到艱難地修改和痛苦的維護的煉獄中,在地獄之火中永久地熬煉。
你看,上帝也是有脾氣的,你做了什么他都知道,你不能搶他的風頭,否則你就要付出代價,受到相應的懲罰。為息帝怒,咱們還是老老實實地編寫一些“小類”吧。
有些開發者為了貪圖簡便,看到一個現成的類,也不管這個類是做什么的,需要追加功能時,就向這個類里面添加功能代碼。久而久之,使得一些類變成了“上帝類”。什么是“上帝類”?上帝類也叫萬能類,意指做了太多“事情”的類。在開發基於WebForms的應用程序時,Page頁面的后置代碼中包含了訪問數據庫、處理業務邏輯、綁定頁面數據、頁面事件處理等這些事情,這就是上帝類的一個舉證(可能很多人都這么干過)。
上帝類的優缺點
優點
“存在即合理”——上帝類比較適用於一些較小的、穩定的應用開發場景,即那些業務邏輯不復雜、也不需要太多維護的應用程序。
比如:一些小工具的開發,不需要過多地考慮類的粒度和職責划分,樓主博客中用的Windows Live Writer代碼高亮插件就是這么做的。
缺點
上帝類的缺點是顯而易見的,上帝類的顆粒度較大,它缺乏可讀性、可擴展性和可維護性。
上帝類違反了“SRP原則”,上帝類擔任的職責太多了,該做的和不該做的它都做了。
同時也違反了“OCP原則”,上帝類功能之間的耦合性太高了,因此不具備可擴展性,當需求變化時,可能會涉及到大量代碼的修改。
“SRP原則”和“OCP原則”的我就不再贅述了,想了解這兩個原則,請參考該系列的另外兩篇文章:分離職責和提取接口。
示例
重構前
下面這個CustomerService類,定義了5個方法:
- CalculateOrderDiscount()方法:結合客戶信息,計算訂單的折扣
- CustomerIsValid()方法:結合訂單信息,判斷客戶是否有效
- GatherOrderErrors()方法:結合訂單的商品信息和客戶信息,收集訂單錯誤信息
- Register()方法:注冊客戶信息
- ForgotPassword()方法:處理客戶忘記密碼
public class CustomerService { public decimal CalculateOrderDiscount(IEnumerable<Product> products, Customer customer) { // do work } public bool CustomerIsValid(Customer customer, Order order) { // do work } public IEnumerable<string> GatherOrderErrors(IEnumerable<Product> products, Customer customer) { // do work } public void Register(Customer customer) { // do work } public void ForgotPassword(Customer customer) { // do work } }
在業務上,這些方法多少和Customer是有一些關聯的。但這不意味着,只要是和Customer相關的方法都要放到CustomerService中。
這個類還可以在職責上做一些划分,粒度可以控制的在細一些。
重構后
重構后,我們按職責將CustomerService
拆分為了CustomerOrderService
和CustomerRegistrationService
。
public class CustomerOrderService { public decimal CalculateOrderDiscount(IEnumerable<Product> products, Customer customer) { // do work } public bool CustomerIsValid(Customer customer, Order order) { // do work } public IEnumerable<string> GatherOrderErrors(IEnumerable<Product> products, Customer customer) { // do work } } public class CustomerRegistrationService { public void Register(Customer customer) { // do work } public void ForgotPassword(Customer customer) { // do work } }
拆分后,我們可以看到:
- 這兩個類的語義和它們的命名以及定義在其中的方法都是契合的。
- 類的粒度變小了,代碼的可讀性增強了,並且有利於將來的擴展、維護、修改。
在開發過程中,我們應該保持一個良好的習慣,為類中追加功能時,盡量確認好類的職責,並控制好類的粒度,這有益於代碼的可讀性、擴展性、維護和修改,這樣就不會被上帝發現了。