1、定義
首先呢,我們來看一下單一職責原則的定義。
就一個類而言,應該只有一個引起它變化的原因
這個說法不是很好懂,有一些抽象,不過呢,我們依舊可以嘗試著理解一下。
就一個類而言,只有一個引起它變化的原因,也就是說,除此之外,不能有其它引起變化的原因。
這樣就需要一個前提,這個類只能負責一項職責,而不能負責其他的職責,不然,其他的職責就會存在其他變化的原因了。
通俗的說,即一個類只負責一項職責。
懶人就比較喜歡這種通俗地定義,一目瞭然。
懶人曾經總結過:通俗的定義,淺顯易懂;理論的定義,大腦一懵。
有同感的小夥伴請雙擊666。
2、場景
餐館聚餐,通過服務員點餐
這是一個比較常見的場景,比如懶人擼了五天的程式碼,身心疲憊,週末的時候呢,就約上三五個好友,去餐館(番茄餐廳)happy一下(非常單純的吃飯)。我們剛剛坐下,就來了一位很漂亮的服務員為我們點餐。
這樣一個服務員為我們點餐的場景,一般都是什麼樣的流程?
第一步:客人點餐
懶人:我們呢,不但要吃飽,還要吃好!服務員,先來一份蕃茄炒雞蛋,再來一份酸辣土豆絲!!!
好友:臉呢。。。說好的臉呢。。。
服務員:你是顧客,你是上帝,你說啥就是啥,不過,你剛才說的是啥。。。
第二步:烹飪美食
蕃茄炒雞蛋,先炒雞蛋,再炒蕃茄。。。ok,出鍋。
第三步:上餐
服務員:這是您點的蕃茄炒雞蛋,請您慢用。
3、實現
不廢話,擼程式碼。
package com.fanqiekt.principle.single;
/**
* 服務員
* @Author: 番茄課堂-懶人
*/
public class Waiter {
/**
* 下單
* @param dishName 菜名
*/
public void order(String dishName){
System.out.println("客人點餐:" + dishName);
System.out.println("開始烹飪:" + dishName);
//菜品不同,做法不同。
switch (dishName){
case "蕃茄炒雞蛋":
System.out.println("先炒雞蛋");
System.out.println("再炒蕃茄");
System.out.println("...");
break;
case "酸辣土豆絲":
System.out.println("先放蔥薑蒜");
System.out.println("再放土豆絲");
System.out.println("...");
break;
}
System.out.println(dishName + "出鍋");
System.out.println(dishName + "上桌啦,請您品嚐");
}
}
複製程式碼
服務員這個類比較簡單,就一個下單的方法。
為了更好的理解,懶人進行了細節的優化(主要是很多細節懶人壓根不瞭解)。
package com.fanqiekt.principle.single;
/**
* 客戶端
* @Author: 番茄課堂-懶人
*/
public class Client {
public static void main(String[] args){
Waiter waiter = new Waiter();
waiter.order("蕃茄炒雞蛋");
System.out.println("-------");
waiter.order("酸辣土豆絲");
}
}
複製程式碼
客戶端這個類就相當於客人,客人負責通過服務員點餐。
客人一共點了兩道大餐,蕃茄炒雞蛋、酸辣土豆絲,我們來執行一下,看看結果。
客人點餐:蕃茄炒雞蛋
開始烹飪:蕃茄炒雞蛋
先炒雞蛋
再炒蕃茄
...
蕃茄炒雞蛋出鍋
蕃茄炒雞蛋上桌啦,請您品嚐
-------
客人點餐:酸辣土豆絲
開始烹飪:酸辣土豆絲
先放蔥薑蒜
再放土豆絲
...
酸辣土豆絲出鍋
酸辣土豆絲上桌啦,請您品嚐
複製程式碼
OK,兩個熱氣騰騰的飯菜就做好了。
我們回過頭來看一下waiter類,大家覺得這個類好不好?
肯定是不好了,那...不好在哪裡?
這就好比一個小作坊,老闆既負責點餐又負責下單,就跟waiter類一樣。
我們一般在小作坊吃飯,感受會怎麼樣?
亂,非同一般的雜亂。上菜需要等半天,點餐的時候找不到人。
還有一個弊端,我修改了做飯的流程,會影響下單的業務,增加修改的風險,為什麼這麼說呢?
客人A:老闆,給我來一份酸辣土豆絲。
老闆:好嘞,您稍等。
懶人:老闆,我剛才點的蕃茄雞蛋要少放鹽啊。
老闆:好的,我放鹽的時候用小點的勺子。
客人A:老闆,我的菜做了嗎?我的同伴都吃完了,沒做我就不要了!
老闆:您的菜已經做了,馬上就要出鍋了。(內心:我勒個去,剛才用小勺放鹽的時候把這哥們點的單給忘了,這就尷尬了。。。)
不難看出,當功能冗雜到一個物件中,這樣修改就會增加風險。那我們該如何避免呢?
一般比較完善的餐館,還至少會有一名廚師。
廚師做飯,服務員點餐,這樣做,有什麼好處呢?
一來,結構清晰了,各司其職,一目瞭然。二來,風險降低了,我修改做飯的流程,不會影響下單的業務。
只負責一項職責,這就是單一職責原則。
那我們嘗試著增加一個廚師類。
package com.fanqiekt.principle.single;
/**
* 廚師
*
* @author 番茄課堂-懶人
*/
public class Chef {
/**
* 做飯
* @param dishName 下單的菜名
*/
public void cooking(String dishName) {
System.out.println("開始烹飪:"+dishName);
switch (dishName){
case "蕃茄炒雞蛋":
System.out.println("先炒雞蛋");
System.out.println("再炒蕃茄");
System.out.println("...");
break;
case "酸辣土豆絲":
System.out.println("先放蔥薑蒜");
System.out.println("再放土豆絲");
System.out.println("...");
break;
}
System.out.println(dishName + "出鍋");
}
}
複製程式碼
廚師類,只負責了一項職責:做飯。
這就是類的單一職責原則。
Chef類只有一個cooking方法,cooking方法是根據下單的菜品名稱去烹飪不同的菜,以及炒蕃茄雞蛋以及酸辣土豆絲的具體烹飪過程。這樣做合適嗎?
不合適的,cooking方法應該只有菜品分發這一項職責,而炒蕃茄雞蛋以及酸辣土豆絲這兩件事顯然易見與分發沒有任何關係,所以拆分出來效果會更好。
我們將廚師類再優化下。
package com.fanqiekt.principle.single;
/**
* 廚師
*
* @author 番茄課堂-懶人
*/
public class Chef {
/**
* 做飯
* 方法的單一職責原則
* @param dishName 下單的菜名
*/
public void cooking(String dishName) {
System.out.println("開始烹飪:"+dishName);
switch (dishName){
case "蕃茄炒雞蛋":
cookingTomato();
break;
case "酸辣土豆絲":
cookingPotato();
break;
}
System.out.println(dishName + "出鍋");
}
/**
* 炒蕃茄雞蛋
*/
private void cookingTomato() {
System.out.println("先炒雞蛋");
System.out.println("再炒蕃茄");
System.out.println("...");
}
/**
* 炒酸辣土豆絲
*/
private void cookingPotato() {
System.out.println("先放蔥薑蒜");
System.out.println("再放土豆絲");
System.out.println("...");
}
}
複製程式碼
優化後Chef類有三個方法。
cooking方法是根據下單的菜品名稱去烹飪不同的菜。
cookingTomato方法是炒蕃茄雞蛋。
cookingPotato方法是炒酸辣土豆絲。
每個方法只負責一項職責,這就是方法的單一職責原則。
遵守方法單一職責原則的類,是不是更加的直觀?修改各自的方法是不是也沒有影響到其他的方法?
接下來,我們再優化下Waiter類,讓他遵循類的單一職責原則。
package com.fanqiekt.principle.single;
/**
* 單一職責原則的服務員
*
* @author 番茄課堂-懶人
*/
public class Waiter {
private Chef chef = new Chef();
/**
* 點餐
* @param dishName 餐名
*/
public void order(String dishName) {
System.out.println("客人點餐:"+dishName);
chef.cooking(dishName);
System.out.println(dishName+"上桌啦,請您品嚐!");
}
}
複製程式碼
優化後SingleWaiter類有隻負責點餐、上餐這些與服務員相關的職責,而做飯的這些無關的職責則交給了Chef。
遵守類單一職責原則的專案,是不是更加的直觀?修改各自的類是不是也沒有影響到其他的類?
接下來,我們把Client執行一下。
客人點餐:蕃茄炒雞蛋
開始烹飪:蕃茄炒雞蛋
先炒雞蛋
再炒蕃茄
...
蕃茄炒雞蛋出鍋
蕃茄炒雞蛋上桌啦,請您品嚐
-------
客人點餐:酸辣土豆絲
開始烹飪:酸辣土豆絲
先放蔥薑蒜
再放土豆絲
...
酸辣土豆絲出鍋
酸辣土豆絲上桌啦,請您品嚐
複製程式碼
結果與原來一致。
4、優點
擼過程式碼後,我們發現單一職責原則的幾個優點。
提高類的可讀性
符合單一職責原則的方法、類,結構會更加的清晰,類的可讀性也就提高了。
降低類的複雜性 一個類只負責一項職責,一個方法也只負責一項職責。肯定要比功能冗雜到一個方法,一個類中要簡單得多。
降低風險
修改其中的一個業務,不會影響到業務。
5、總結
我們必須要意識到,一味的遵守單一職責原則,不停的分拆類所付出的開銷是很大的。
這時候就涉及到平衡的問題,平衡單一職責原則與修改造成的開銷。
懶人的觀點是如果一個方法邏輯不復雜的情況下,可以修改方法實現,否則要拆分為兩個方法,遵循方法級別的單一職責原則。
如果一個類方法不多的情況下,可以只增加方法,而不用分拆為多個類,否則要拆分為多個類,遵循類級別的單一職責原則。
6、嘻哈說
接下來,請您欣賞單一職責原則的原創歌曲。
嘻哈說:單一職責原則
作曲:懶人
作詞:懶人
Rapper:懶人
週末約上了好友去熟悉的餐館聚餐
只負責點餐的漂亮服務員保持笑容已經成為習慣
只負責做飯的帥氣廚師一直待在了煙霧瀰漫了幾遍的廚房裡面
每個人有自己負責的地盤
就像單一職責
一個類只有一個職責 好體面
它降低了類的複雜性
它提高了類的可讀性
那風險被降低代表著單一職責沒毛病
複製程式碼
閒來無事聽聽曲,知識已填腦中去;
學習複習新方式,頭戴耳機不小覷。
番茄課堂,學習也要酷。