I walk very slowly, but I never walk backwards
設計模式原則 - 單一職責原則
寂然
大家好,我是寂然~,本節課呢,我來給大家介紹設計模式原則之單一職責原則,帶領大家揭開設計模式原則的神秘面紗,話不多說,我們進入正題。不知道大家是否遇到過下面這樣的情況
在實際開發的過程中,有時候大家會發現自己寫的類越來越大,幾百一千行,該類的功能也越來越多,有一些開發者包括之前的我,看到自己寫的類夠大,功能夠多,可能有點小自豪對吧,看,這是朕寫下的江山!!但是當某個功能需要做一個小改動時,就會發現整個程式出現了各種大大小小的問題,然後頭發越來越少....
為什麼隻對這個類的一個功能做了小小的修改就會引起這麼大的問題?其實就是因為我們違反了單一職責原則,将多種功能內建在一個類中,就等于把這些功能耦合了起來,一個功能的變化可能會削弱或者抑制這個類完成其他職責的能力,而如果想要避免這種現象的發生,就要盡可能的遵守單一職責原則,那什麼是單一職責原則呢?
當然,我們還是首先來看一下單一職責原則的定義
官方定義
單一職責原則(Single Responsibility Principle, SRP),有且僅有一個原因引起類的變更
基本介紹
那根據上面給出的定義,我們來對單一職責原則進行一個基本介紹
即對類來說,一個類應該隻負責一項職責。如類 A 負責兩個不同職責:職責 1,職責 2,當職責 1 需求變更而改變 A 時,可能造成職責 2 執行錯誤,是以需要将類 A 的粒度分解為 A1,A2
什麼意思呢,給大家舉個栗子,如果你項目中DAO層的一個類,既操作user表,又操作了order表,也就是說這個javaBean既負責user表的增删改查,又負責order表的增删改查,那麼這個類負責了兩個不同的職責,就違反了單一職責原則,是以根據原則需要将這個類的粒度分解為一個userDao操作user表,orderDao來操作order表
案例:動物世界
大家聽了單一職責原則的介紹,那我們來看如下一個案例
有一個動物類,裡面定義一個在森林奔跑的方法,然後我們建立動物的執行個體,調用方法,方法内執行列印操作
public class SingleDemo {
public static void main(String[] args) {
Animal animal = new Animal();
animal.run("老虎");
animal.run("獅子");
animal.run("老鷹");
}
}
//定義動物類
class Animal{
//森林奔跑方法
public void run(String animal){
System.out.println(animal + "正在森林裡愉快的奔跑");
}
}
首先這段代碼沒有文法上的問題,但是執行的時候,大家就可以看到,出現了明顯的邏輯錯誤,老鷹是沒辦法在森林裡奔跑的,換句話說,在run方法中,出現了即有森林裡的動物,又有天空上的動物,違反了單一職責原則

解決方案一:拆分類為更小粒度
我們可以按單一職責原則,将原來的類Animal,分成多個類,在目前業務邏輯下,每個類負責不同的職責,是以根據上面的案例,我們将Animal類根據奔跑的位置進行拆分,分解成不同的類即可,代碼示例如下
public class SingleDemo {
public static void main(String[] args) {
ForestAnimal forestAnimal = new ForestAnimal();
forestAnimal.run("老虎");
forestAnimal.run("獅子");
SkyAnimal skyAnimal = new SkyAnimal();
skyAnimal.fly("老鷹");
}
}
class ForestAnimal{
//森林奔跑方法
public void run(String animal){
System.out.println(animal + "正在森林裡愉快的奔跑");
}
}
class SkyAnimal{
//森林奔跑方法
public void fly(String animal){
System.out.println(animal + "正在天空上愉快的飛翔");
}
}
OK,那這樣首先确實遵循了單一職責原則,同時也保證了業務邏輯的正确,但是大家同時考慮,這樣做的改動很大,我們不僅要拆分類,同時還要大範圍修改用戶端(即main方法裡的代碼也要改動)
那我們還可以怎樣做呢?
解決方案二:原有類進行修改
那上面提到,使用方案一,不僅要拆分類,同時還要修改用戶端裡的代碼,那可能有人想到了,那我直接在原有類的基礎上進行改動呢?下面我們來看代碼示例
public class SingleDemo {
public static void main(String[] args) {
Animal animal = new Animal();
animal.runForest("老虎");
animal.runForest("獅子");
animal.runSky("老鷹");
}
}
class Animal {
//森林奔跑方法
public void runForest(String animal) {
System.out.println(animal + "正在森林裡愉快的奔跑");
}
//天空飛翔方法
public void runSky(String animal) {
System.out.println(animal + "正在天空上愉快的飛翔");
}
}
那下面我們針對方案二進行一下分析
- 這種修改方法沒有對原來的類做大的修改,隻是增加了方法
- 用戶端改動範圍很小的同時保證了業務邏輯的正确
那這時可能有人要問了,那這樣的寫法,同樣将森林和天空的動物耦合在一個類裡了啊?
這裡大家要注意哈,确是如此,但是,方案二雖然沒有在類這個級别上遵守單一職責原則,但是在方法級别上,仍然遵守單一職責原則,即一個方法隻負責一項職責
通過上面兩種方案,大家可以看到,方案一,類級别遵守了單一職責原則,但是改動的代價很高,方案二方法級别遵守了單一職責原則,改動幅度較小,綜上所述,單一職責原則最核心的其實就是各司其職
注意事項&細節
-
降低類的複雜度,一個類隻負責一項職責
(一個類的職責少了,相應的複雜度就會降低)
-
提高類的可讀性以及可維護性
(相應的複雜度降低,代碼量就會減少,可讀性也就會提高,可維護性自然就提高了)
-
降低變更引起的風險
(一個類的職責越多,變更的可能性就更大,變更帶來的風險也就越大)
-
通常情況下,我們應當遵守單一職責原則
(隻有邏輯足夠簡單,才可以在代碼級違反單一職責原則,隻有類中方法數量足夠少,才可以在方法級别保持單一職責原則,參考方案二)
如何遵守單一職責原則?
上面的注意事項中也提到了,身為設計模式七大原則之一,通常情況下,我們的代碼應當遵守單一職責原則,那大家肯定或多或少都有這樣的疑問,如何遵守呢?
其實就是合理的職責分解,相同的職責放到一起,不同的職責分解到不同的接口和實作中去,這個是最容易也是最難運用的原則,關鍵還是要從業務出發,從需求出發,識别出同一種類型的職責
需要說明的一點是:單一職責原則不隻是面向對象程式設計思想所特有的,隻要是子產品化的程式設計,都适用單一職責
下節預告
下一節,我們正式進入設計模式原則之接口隔離原則的學習,我會為大家用多個案例分析,來解讀設計模式原則之接口隔離原則,以及它的注意事項和細節,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~