SRP:TheSingle Responsibility Principle
單職原則
None but Buddha himself must take theresponsibility of giving out occult secrets...
— E. Cobham Brewer1810–1897.
Dictionaryof Phrase and Fable. 1898.
(譯者註:一開始翻譯就遇到這句很難翻譯的了,本人的理解為“就連佛祖都有他的職責”)
SRP原則是TomDeMarco和
Meilir Page-Jones在工作的時候提出的。他們稱這個原則為“內聚”。在21章,我們說給出更多在的Package層次的定義說明。這個SRP原則是針對Class層次的。
SRP:單職原則
修改一個類的理由不應該超過一個。
以第六章的保齡球遊戲為例子。Game這個類擁有2個完全獨立的職責。它不停地追蹤每一幀,並進行分數計算。最後它被分割為RCM和RSK類。Game類保持其原有追蹤幀與計算得分的職責。
為什麼將這2個職能分割到2個類中呢?這是因為這2個職能都是以其自身為修改點的。我們只需要修改對應類就可以修改對應的職能。一個類超過一個職能,那麼我們將會有多個原因去修改它(譯者註:一個職能的修改歸結為一個原因)。
例9-1的設計。Rectangle類有2個方法。一個是渲染矩形到螢幕,另一個是計算矩形的面積。
超過一個職能
2個不同的程式去使用Rectangle類。一個程式是幾何運算程式,需要使用Rectangle類來進行對幾何圖形進行數學運算,它並不需要將矩形輸出到螢幕。另一個程式是映像顯示程式,它在需要幾何運算的同時也需要將矩形輸出到螢幕上。
這個設計違背了SRP。矩形有2個職責,一是計算面積,二是GUI相關的渲染。
違背SRP會導致一些問題。
首先,這樣的Rectangle類,需要包含GUI相關代碼到幾何運算程式中。如果它是一個C++程式,那麼就需要link相關的GUI庫了,增加了complie、link記憶體的開銷。如果是一個JAVA程式,那麼它也需要依賴相關平台的GUI庫。
其次,如果映像顯示程式,需要修改Rectangle類,那麼幾何運算程式,如果你忘記了做這一步,那麼幾何運算程式將會因為函數指標的位移而導致程式隨時崩潰。
一個比較好的設計就是將職能劃分,9-2。將渲染和面積計算劃分到不同的類,這樣2個程式就獨立開來了。
什麼是職能?
上文我們將SRP定義為一個職能職能因為“一個理由而去改變”。如果你有多於一個動機去改變一個類,那麼這個類其實就是有多個職能了。很多時候,這個情況是很困難發現的。我們習慣將一個邏輯歸到一個職能中。例如下面表格9-1中Modem這個interface。大多數人都認為它是比較合理的。Modem的4個功能分別由4個函數去聲明。
然而,其實它隱含了2個職能。dial和hangup函數屬於通訊管理,send和recv屬於資料轉送。
這2個職能是否應該分隔呢?在大多數的情況下,他們都應該被分隔。這2組函數幾乎沒有共同之處。修改他們肯定是出於不同的理由。此外,這2組函數也會被不同的程式模組去調用。這些差異也導致了修改原因的差異。
因此圖9-3的設計就比較合理。將其只能分隔到2個interface中。最後用戶端程式將2個介面進行串連。
然而,你會發現我又將2個職能歸結到ModemImplementation類中了。雖然讓人不爽,但這是必須的。我們經常會因為一些原因,如硬體或者作業系統,導致我們將一些東西耦合起來。但是,將他們分隔到不同的interface的話,我們又需要將剩餘的部分進行解耦。
我們可以將ModemImplementation類看作是一個組裝機。需要注意的是所有的依賴都會從它消失。因為其他所有類都不依賴與它。除了main函數,其他類都沒不需要知道它的存在。因此,我們將這些醜陋的實現都放到ModemImplementation裡面,而且醜陋的實現又不會汙染程式的其他部分。
結論
SRP是一個最簡單的原則,但也是最難做得好的。我們很自然地將職責串連起來。所謂軟體設計,其實就是尋找職責並將其分隔。剩下我們將要討論的原則都用這樣或那樣的方式回到SRP這個點上。