一、單一職責原則
不要存在多於一個導致類變更的原因。簡單來說,就是一個Class/Interface/Method只負責一項職責。
這句話最為重要的就是這一段:一個Class/Interface/Method只負責一項職責。
我們先來舉一個例子,我們在日常生活中都或多或少的聽過LOL(英雄聯盟)這個遊戲,而這個遊戲在各個直播平臺都很火爆,那我們就以此為例:
某個遊戲直播平臺會將主播直播時的視訊錄製下來,等到主播下播後再上傳到平臺,這樣就形成了錄播。對於這兩種視訊觀看模式,平臺有著這樣的規定:觀看直播不允許快進、回放,而錄播則可以,那我們首先想到的應該是這樣方式:
/**
* 平臺
*/
public class UuSee {
private final String LiveName = "直播";
private final String ReplayName = "錄播";
// 觀看
public void watch(String name){
if (LiveName.equals(name)){
System.out.println("觀看LOL "+name+",不能快進、回放!");
} else if(ReplayName.equals(name)) {
System.out.println("觀看LOL "+name+",可以快進、回放!");
}
}
}
我們來寫一個測試的程式碼看看:
public static void main(String[] args) {
UuSee uuSee = new UuSee();
uuSee.watch("直播");
uuSee.watch("錄播");
}
從測試的程式碼來看的話,
UuSee
類承擔了兩種不同的處理邏輯。那麼現在來增加一個需求:對直播和錄播的視訊進行加密,而直播和錄播視訊的加密方式不同。那麼我們必須要修改原始碼,而這可能影響其他地方的呼叫。所以現在我們來將兩種觀看方式分開,讓它們互不干擾:
直播(LiveVideo
類):
/**
* 直播
*/
public class LiveVideo {
public void watch(String name){
System.out.println("直播視訊加密......");
System.out.println("觀看LOL "+name+",不能快進....");
}
}
錄播(RePlayVideo
類):
/**
* 錄播
*/
public class RePlayVideo {
public void watch(String name){
System.out.println("錄播視訊加密......");
System.out.println("觀看LOL "+name+",可以快進.....");
}
}
呼叫程式碼:
public static void main(String[] args) {
RePlayVideo rePlayVideo = new RePlayVideo();
rePlayVideo.watch("錄播");
LiveVideo liveVideo = new LiveVideo();
liveVideo.watch("直播");
}
這樣看的話,直播類
LiveVideo
和錄播類RePlayVideo
都呼叫自己的處理邏輯,兩者互不干擾。那麼如果業務繼續發展:增加VIP使用者,並且只有VIP使用者才能觀看錄播(獲得視訊流);而普通使用者雖然不能觀看錄播,但可以獲取錄播視訊的基本資訊。這樣就會增加兩個方法:getReplayVideoInfo()
和getReplayVideo()
,這時候發現錄播類RePlayVideo
和直播類LiveVideo
都會擁有其中的兩個方法,那不如設計一個頂級介面,將他們一起納入管理:UuSeeInterface
public interface UuSeeInterface {
// 獲得 錄播視訊資訊
public String getReplayVideoInfo();
// 獲得 錄播視訊 視訊流
public byte[] getReplayVideo();
// 觀看視訊
public void watch(String name);
}
寫完這個介面,我們發現從控制視訊播放許可權的層面來看的話,可以分為兩個職責:管理職責和展示職責;
getReplayVideoInfo()
和getReplayVideo()
方法都屬於展示職責,watch()
方法屬於管理職責,這樣的話我們就可以將上面的介面拆分一下:
管理職責介面(UuSeeManagement
)
/**
* 管理職責
*/
public interface UuSeeManagement {
// 觀看視訊
public void watch(String name);
}
展示職責介面(UuSeeInfo
)
/**
* 展示職責
*/
public interface UuSeeInfo {
// 獲得 錄播視訊資訊
public String getReplayVideoInfo();
// 獲得 錄播視訊 視訊流
public byte[] getReplayVideo();
}
說完了一個Class/Interface只負責一項職責的事情後,我們再來看一看一個Method只負責一項職責的問題。
假如有這麼一個方法:更改一個使用者的使用者名稱和地址,我們可能會偷懶寫成這樣。
// 修改使用者名稱稱和地址
public void modifyUserInfo(String userName,String address){
System.out.println("使用者名稱改為:"+userName);
System.out.println("使用者地址改為:"+address);
}
那麼當我們又增加一個需求:只更改使用者名稱稱,不更改使用者地址。那麼我們在呼叫
modifyUserInfo()
方法時,還要去千方百計的獲得使用者的地址,非常的麻煩,倒不如將上面的方法拆分為兩個方法:
// 修改使用者名稱稱
public void modifyUserName(String userName){
System.out.println("使用者名稱改為:"+userName);
}
// 修改使用者地址
public void modifyUserAddress(String address){
System.out.println("使用者地址改為:"+address);
}
這樣來看,我們需要修改使用者名稱稱的時候就呼叫
modifyUserName()
方法,需要修改使用者地址的時候就呼叫modifyUserAddress()
方法,兩個方法獨立分開,不相互干擾,後期也好維護。當然,在日常工作中,我們不可能面面俱到,也不要為了某種原則而將自己陷入某個陷阱中,還是要根據實際情況做出不同的選擇。但是,編寫程式碼時,要儘量的滿足單一原則,這樣也方便我們後期的程式碼維護。
二、依賴倒置原則
這個原則是開閉原則的基礎,是指設計結構程式碼時,高層模組不應該依賴於底層模組,二者應該依賴於抽象。抽象不應該依賴於細節,細節應該依賴於抽象。即:針對介面程式設計,依賴於抽象而不依賴於具體。
我們來先看一段程式碼:
/**
* 動物園
*/
public class Zoom {
// 老虎
public void tiger(){
System.out.println("老虎在吃雞肉.....");
}
// 猴子
public void monkey(){
System.out.println("猴子在吃香蕉.....");
}
}
// 測試
public static void main(String[] args) {
Zoom zoom = new Zoom();
zoom.tiger();
zoom.monkey();
}
小明週末去動物園遊玩,正值晌午,動物飼養員在給動物餵食,覺得有趣就想記錄一下。在記錄完tiger和monkey之後,小明又發現了熊貓(panda)在吃竹子,於是興沖沖的準備記錄下來,可動筆時卻發現一個問題:
tiger()
、monkey()
方法已經寫好了,呼叫沒有任何問題,但在增加一個panda()
方法的話,不就修改了Zoom
類的原始碼,這樣會不會有額外的風險呢?似乎也不符合設計模式中的開閉原則。思考良久,小明將上述程式碼改成了下面這樣:
/**
* 動物園
*/
public interface Zoom {
// 吃午飯
public void eat();
}
// 老虎
public class Tiger implements Zoom {
@Override
public void eat() {
System.out.println("老虎在吃雞肉.....");
}
}
// 猴子
public class Monkey implements Zoom{
@Override
public void eat() {
System.out.println("猴子在吃香蕉......");
}
}
// 呼叫類
public class Xming {
public void eat(Zoom zoom){
zoom.eat();
}
}
// 測試
public static void main(String[] args) {
Xming xming = new Xming();
xming.eat(new Tiger()); // 老虎
xming.eat(new Monkey()); // 猴子
}
這樣一看,
Tiger
和Monkey
互不干擾,當需要記錄熊貓吃竹子時,只需要在建立一個Panda
類就可以了,不用去修改現有的原始碼:
// 熊貓
public class Panda implements Zoom {
@Override
public void eat() {
System.out.println("熊貓正在吃竹子......");
}
}
// 測試
public static void main(String[] args) {
Xming xming = new Xming();
xming.eat(new Tiger()); // 老虎
xming.eat(new Monkey()); // 猴子
xming.eat(new Panda());// 熊貓
}
這個時候我們發現
Xming
類呼叫eat()
方法時注入方式很熟悉,想了想是依賴注入,而依賴注入的方式還有構造器注入和setter注入。我們先來看看構造器注入的方式:
// 呼叫類
public class Xming {
private Zoom zoom;
public Xming(Zoom zoom) {
this.zoom = zoom;
}
public void eat(){
zoom.eat();
}
}
呼叫程式碼:
public static void main(String[] args) {
Xming tiger = new Xming(new Tiger());
tiger.eat();
Xming panda = new Xming(new Panda());
panda.eat();
}
構造器注入的方式在每次呼叫時都要建立例項。那麼,如果
Xming
是全域性單例的話,我們就只能選擇setter注入的方式了:
// 呼叫類
public class Xming {
private Zoom zoom;
public void setZoom(Zoom zoom) {
this.zoom = zoom;
}
public void eat(){
zoom.eat();
}
}
// 呼叫
public static void main(String[] args) {
Xming tiger = new Xming();
tiger.setZoom(new Tiger());
tiger.eat();
Xming panda = new Xming();
panda.setZoom(new Panda());
panda.eat();
}
結語:依賴倒置原則的本質還是面向介面程式設計,而事實就是以抽象為基準比以細節為基準搭建起來的框架要穩定的多,後期維護與檢視都相對的容易與清晰一些。所以,我們在日常的開發中,要根據實際的業務需求來分析,儘量的使用面向介面程式設計,先頂層再細節的步驟來設計程式碼架構。