單一職責原則的定義比較簡單——對於一個類,應該只有一個引起它變化的原因。
當我們在進行軟件開發的時候,經常會給已經設計的一個類新增各種各樣的功能,或許是出於需求變更,或許是因爲出於開發量的考量,一個類就在這樣或那樣的原因下變得逐漸臃腫龐大而且不方便管理。例如一個負責接受用戶發來信息的類,也被設計爲可以訪問數據庫將用戶發來的信息進行記錄等等,這意味着,當接收用戶發送信息的結構變化或數據庫持久層變化都會引起類的變化,而這就違背了類的單一職責原則。
舉個例子,我們設計一個超人SuperMan類,超人就應該能人所不能,所以設計給他儘可能多的功能。
/**
* Author:小青
* Time:2017-8-26
* Function:無所不能的超人類
*
*/
public class SuperMan{
public void worldPeace(){
System.out.println("超人可以維護世界和平");
}
public void writeCode(){
System.out.println("超人能寫代碼");
}
public void writeBook(){
System.out.println("超人能寫小說");
}
public void playGanme(){
System.out.println("超人打LOL上王者");
}
public void other(){
System.out.println("超人還有其他數不清的功能");
}
}
這樣設計出來的超人可以說是非常強悍的,什麼都會,但與此同時,我們發現,由於LOL版本更新,超人需要重新修改下玩LOL 的代碼,在這個修改期間,很多的罪犯出來搗亂了,而超人卻還在維修,於是我們需要重新考慮設計超人的問題,只有超人可以維護世界和平的情況下,因爲LOL而拉回去改造是不是很不合適呢?所以我們減少超人的職責負擔。/**
* Author:小青
* Time:2017-8-26
* Function:維護世界和平的超人類
*
*/
public class SuperMan{
public void worldPeace(){
System.out.println("超人可以維護世界和平");
}
}
爲什麼類應該滿足單一職責原則呢?首先,易於維護的高度可複用性是面向對象開發的突出優點,如果一個類職責過於龐大,維護起來必然是困那重重,我們需要儘可能多的創造複用代碼,所以當類的職責過於龐大的時候,就與面向對象的思想背道而馳了,類的合理性也就不用在提。
但即使經驗豐富的開發人員也不可避免的會違背單一職責這一原則,這就是因爲職責擴散。職責擴散是因爲某些原因,職責A被分化爲粒度更細的職業A1和A2。舉個例子
/**
* Author:小青
* Time:2017-8-26
* Function:動物的呼吸功能
*
*/
public class Animal{
public void breathe(String animal){
system.out.println(animal + "呼吸空氣");
}
}
public class Client{
public sttaic void main(String[] args){
Animal animal = new Animal();
animal.breathe("貓");
animal.breathe("狗");
animal.breathe("羊");
}
}
程序在運行時候發現,並不是所有的動物都呼吸空氣的,比如魚。修改的時候如果遵循單一職責原則,就需要將動物細分爲陸生動物類和水生動物類
/**
* Author:小青
* Time:2017-8-26
* Function:陸生動物呼吸空氣
*
*/
public class Terrestrial{
public void breathe(String animal){
system.out.println(animal + "呼吸空氣");
}
}
/**
* Author:小青
* Time:2017-8-26
* Function:水生動物在水中呼吸
*
*/
public class Aquatic{
public void breathe(String animal){
system.out.println(animal + "在水中呼吸");
}
}
public class Client{
public sttaic void main(String[] args){
Terrestrial animal = new Terrestrial();
animal.breathe("貓");
animal.breathe("狗");
animal.breathe("羊");
Aquatic aquatic = new Aquatic();
aquatic.breathe("魚");
}
}
代碼改動後結構如下,除了將Animal修改爲Terrestrial和Aquaticlian兩個類意外,還包括對客戶端代碼的修改,而直接修改Animal類雖然違背了單一職責原則,對資源的開
銷反而更小。
/**
* Author:小青
* Time:2017-8-26
* Function:動物的呼吸功能
*
*/
public class Animal{
public void breathe(String animal){
if("魚".equals(animal)){
system.out.println(animal + "在水中呼吸");
}else{
system.out.println(animal + "呼吸空氣");
}
}
}
public class Client{
public sttaic void main(String[] args){
Animal animal = new Animal();
animal.breathe("貓");
animal.breathe("狗");
animal.breathe("羊");
animal.breathe("魚");
}
}
這樣修改相對更加簡單,但是如果不僅僅只有許需要在水中呼吸,還有蝦,螃蟹等等其他的水生物呢?所以這樣的方式是存在隱患的,這種修改方式在代碼級別上違背了單
一職責原則,雖然看起來操作最簡單,但是隱患也是最大的。
/**
* Author:小青
* Time:2017-8-26
* Function:動物的呼吸功能
*
*/
public class Animal{
public void breathe(String animal){
system.out.println(animal + "呼吸空氣");
}
public void breathe2(String animal){
system.out.println(animal + "在水中呼吸");
}
}
public class Client{
public sttaic void main(String[] args){
Animal animal = new Animal();
animal.breathe("貓");
animal.breathe("狗");
animal.breathe("羊");
animal.breathe2("魚");
}
}
這種方式在不改動原有方法的基礎上,新增了一個方法,這樣在方法級別上是滿足單一職責原則的。以上三種方式各有優缺點,在實際開發中要有選擇的使用,有一種原則
:需要足夠簡單的邏輯才能在代碼級別上違背單一職責原則,類中方法足夠少才能在方法級別上違背單一職責原則。
單一職責原則可以降低類的複雜度,一個類只負責一個職責,其邏輯肯定要比負責多項職責簡單,提高類的可讀性,提高系統的可維護性。
面向對象的編程幾乎就是一個發現職責並將其互相分離的過程,至於如何做到,如果能想到多餘一個動機去改變一個類,那麼這個類就多餘一個職責,需要我們將其分離開
來。
以上內容,整理自劉徑舟,張玉華編著的《設計模式其實很簡單》讀書筆記,歡迎轉載.