原則 or 不原則?
我們都知道設計模式的學習是進階Java架構師的必經之路,而設計模式的設計都或多或少遵循了一些設計原則,所以設計原則的學習能夠加深對於設計模式的理解,也是很有必要的。當然,設計原則是死的,甚至於有時候我們在實際場景中對於設計模式的使用是反設計原則的。
那麼,我們到底還有沒有必要學習設計原則呢?
當然是,有!如果我們把軟件設計比作一門武功,那麼設計原則乃至於設計模式都是武學中的一招一式。在金庸武俠中,武俠的最高境界是出手無招,那麼怎麼做到出手無招呢?就像風清揚對令狐沖講的:先將每一招融會貫通,然後儘量忘得一乾二淨。
所以要想做到出手無招,必須還得先有招!
同理,在軟件設計中,我們先得對於設計原則以及設計模式融會貫通,然後再忘掉它們,而後做到不拘泥與招式,能夠設計出最符合當前實際情況的軟件。
單一職責原則
我今天給大家帶來的就是第一個設計原則:單一職責原則(Single responsibility principle),簡稱SRP原則
。
顧名思義,單一職責原則就是一個類或者模塊只負責完成一個職責(
A class or module should have a single reponsibility
)
還記得之前在學習Spring框架的相關知識時,總是會聽到六字真言:高內聚,低耦合。 當時總是覺得不能體會其中真意,而今正好,單一職責原則的目的正是爲了設計出高內聚,低耦合
的軟件。如下圖兩種設計思路都是違反了單一職責原則:
當我們碰到耦合度高的模塊時,按照單一職責原則我們要進行必要的拆分,如圖:
而當我們碰到低內聚的模塊時,按照單一職責原則,我們則要進行一些必要的合併,如圖:
以上解釋可以會有些讓人云裏霧裏。沒關係,接下來我們來結合具體的案例來講解!!!
具體案例
如以下代碼,我們創建了一個交通工具類Vehicle
的對象vehicle
,分別傳入不同的交通工具執行了run()
方法:
/**
* @author guqueyue
* @Date 2020/6/23
**/
public class SingleResponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托車");
vehicle.run("汽車");
vehicle.run("飛機");
vehicle.run("輪船");
}
}
/**
* 交通工具類
* 方式一
* 1.違反了單一職責原則
* 2.解決方案:根據交通工具運行方法不同,分解成不同的類
*/
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + " 在公路上運行...");
}
}
運行應用程序,控制檯輸出如下:
這個時候我們會發現,什麼?飛機和輪船竟然也在公路上跑!!!這肯定不行呀,那怎麼辦呢?
方案一
這個時候我們可能一般都會想到:既然交通工具分天上飛的,地上跑的以及水裏遊的,那我來個海陸空分類不就好了嗎?再用if-else
來判斷一下。自己簡直太機智了,然後刷刷刷寫下以下代碼:
/**
* @author guqueyue
* @Date 2020/6/23
**/
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle1 vehicle = new Vehicle1();
vehicle.run("摩托車", "road");
vehicle.run("汽車", "road");
vehicle.run("飛機", "air");
vehicle.run("輪船", "water");
}
}
/**
* 交通工具類
* 用if - else 區分不同的交通工具
*/
class Vehicle1 {
/**
* 非常複雜
* @param vehicle 交通工具
* @param type 類型
*/
public void run(String vehicle, String type) {
if ("road".equals(type)){
System.out.println(vehicle + " 在公路上運行...");
}else if ("air".equals(type)) {
System.out.println(vehicle + " 在天空上飛...");
}else if ("water".equals(type)) {
System.out.println(vehicle + " 在水裏遊...");
}else {
throw new RuntimeException("小朋友,請輸入正確的類型");
}
}
}
運行應用程序,控制檯打印如下:
這樣一看似乎覺得已經非常完美了,但是我們試着畫圖來分析一下:
這樣一畫圖,我們可以很輕易的看出:Vehicle1模塊
能夠得出天上飛,公路上運行以及水裏遊三種結果,也就是說Vehicle1模塊
的耦合度非常高。 那麼這樣會有什麼缺點呢?來,幫你總結好了:
- 類的複雜度非常高。
這麼多的if-else,能不高嗎? - 類的可讀性以及可維護性較差。 試想一下,如果要增加可以遁地、潛水以及在外太空翱翔的交通工具類型,又要往裏寫if-else判斷;而之後有一天需要刪除某些不要的交通工具類型,這個if-else是不是刪的你很崩潰!
- 變更有風險。 即當我因爲職責1的需求改變時,而改變
Vehicle1模塊
時可能會造成其他職責的執行錯誤。
比如在陸地上的交通工具,需要增加一個時間參數,以便判斷是否需要限速時;那麼這個時候海和空的交通工具就會受到影響。
方案二
所以,這個時候方案二閃亮登場。根據單一職責原則:一個類或者模塊只負責完成一個職責就好啦。 所以,我們可以將之前的Vehicle1類
劃分爲三個類:
/**
* @author guqueyue
* @Date 2020/6/23
**/
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("摩托車");
roadVehicle.run("汽車");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飛機");
WaterVehicle waterVehicle = new WaterVehicle();
waterVehicle.run("輪船");
}
}
/**
* 遵守了單一職責原則
* 但是改動很大,需要分解類,修改客戶端
* 改進:直接修改Vehicle類,這樣改動比較少
*/
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上跑...");
}
}
class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在天上飛...");
}
}
class WaterVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在水裏遊...");
}
}
運行程序,結果如下:
這下看起來似乎很完美,也非常符合我們之前所說的單一職責原則:一個類只做一件事情! 但是這樣做的話,似乎對於代碼的改動非常的大,那麼有沒有避免如此改動大的方案呢?
方案三
之前我們說到方案二對於代碼的改動很大,那麼我們特此提出方案三:
/**
* @author guqueyue
* @Date 2020/6/23
* 適用於類的方法比較少
**/
public class SingleResponsibility3 {
public static void main(String[] args) {
Vehicle2 vehicle2 = new Vehicle2();
vehicle2.runRoad("摩托車");
vehicle2.runRoad("汽車");
vehicle2.runAir("飛機");
vehicle2.runWater("輪船");
}
}
/**
* 方式三
* 沒有對原來的類做大的修改,只是增加了方法
* 沒有完全遵守單一職責原則(類級別上),但是在方法級別上仍然遵守了單一職責
*/
class Vehicle2 {
public void runRoad(String vehicle) {
System.out.println(vehicle + "在公路上跑...");
}
public void runAir(String vehicle) {
System.out.println(vehicle + "在天空中飛...");
}
public void runWater(String vehicle) {
System.out.println(vehicle + "在水裏遊...");
}
}
運行程序,結果如下:
結果沒毛病!但是這個時候有人可能就會問了:博主你等等,你之前不是說單一職責原則是一個類只負責一個職責嗎?你一個類都負責三個職責了,這不是違反了單一職責原則了嗎?
話雖如此,可這種寫法雖然沒有在類的級別上遵循單一職責原則,但它在方法級別上遵循了單一職責原則呀!
最後
在上文的分析中,我們總共提出了三種方案,其中方案二最符合單一職責原則。那麼是不是我們應該嚴格按照規定,每次設計軟件的時候都按照方案二的思路來設計呢?其實不然!!偉大的馬克思主義從小就教育我們:實踐纔是檢驗真理的唯一標準。切不可生搬硬套,應當視具體情況而定!方案一和方案三也是有它們的適用場景的:
- 方案一:適用於代碼邏輯簡單。
- 方案三:適用於類中的方法的數量較少。
所以,
我們在實際軟件開發過程中,不必嚴格遵守原則,完全可以先設計一個粗粒度的類,然後再根據業務的發展情況,對代碼進行重構嘛!
就比如近期微服務
的概念一直很火熱,面試的時候面試官不問你點微服務
好像都不正常。那是不是在實際開發過程中,我們不管三七二十一不用單體架構就直接採用微服務呢?其實不然,單體架構比起微服務來說有以下兩個優點:
- 開發速度快。
- 所需資源少,也就是性價比高。
我們在業務初期,單體架構夠用的情況下,就用單體架構就好了。噹噹前架構不再適合當前業務的發展時,才考慮是否要更換架構。
最後的最後,由於本人水平有限,本篇博客內容如有不足之處,還望各位雅正。如果覺得寫的還行,幫忙點個讚唄!關於Java設計模式的博客我會一直堅持寫下去的(加關注,不迷路哦)( ̄▽ ̄)~*