介紹
SOLID是什么,它是如何幫助我們寫更好的代碼的?
SOLID原則由以下5個概念組成:
- Single Responsibility(單一職責)
- Open/Closed(開閉)
- Liskov Substitution(里氏替換)
- Interface Segregation(接口隔離)
- Dependency Inversion(依賴反轉)
簡單來說,這些原則幫助我們創造更易維護,易于理解和靈活的軟件。因此,隨著我們應用的擴大,我們可以降低其復雜性,并在以后為我們省去很多麻煩。
深入說明
單一職責原則
像我們從字面意思看到的一樣,該原則指出,一個類僅應承擔一種責任。此外,它應該只有一個改變的理由(不能同時有很多理由來修改它)。
那么,這個原則是怎么幫助我們寫更好的軟件的?讓我們先看一下它的優點:
- 易測試 - 因為職責單一,所以擁有更少的測試用例
- 低耦合 - 單個類中較少的功能將具有較少的依賴關系
- 易于組織 - 更容易編寫代碼邏輯
下面我們以代碼示例說明:
我們用Book類代表一本書,其屬性包括:書名(name),作者(author)和內容(text)
/**
* 實體:書
*/
public class Book {
private String name;
private String author;
private String text;
/**
* 文本替換
* @param word
* @return
*/
public String replaceWordInText(String word){
return text.replaceAll(word, text);
}
/**
* 是否包含指定的文本
* @param word
* @return
*/
public boolean isWordInText(String word){
return text.contains(word);
}
}
現在我們的程序運行的很好,我們能存儲任意的書的內容。但是我們無法將書的內容打印出來,無法閱讀該怎么辦?那么,我們就在新增一個打印內容的方法,如:
void printTextToConsole(){
// 輸出文本
}
好的,現在已經可以實現打印內容了。但是,我們這樣就違背了“單一職責”(書本身不與打印有什么關系)。為了解決我們的問題,需要實現一個單獨的類,該類僅與打印書籍有關:
/**
* 書籍打印
*/
public class BookPrinter {
/**
* 打印到控制臺
* @param text
*/
void printTextToConsole(String text){
// printing the text
}
/**
* 打印到其他媒介
* @param text
*/
void printTextToAnotherMedium(String text){
// do something
}
}
上面的類不僅與書籍本身解耦,而且能夠實現通過各種媒介進行打印內容,本身又是一個職責單一的案例(打印)。
開閉原則
簡單來說,一個類應對擴展是打開的,對修改是關閉的(open for extension, closed for modification)。這樣一來,我們就可以避免修改現有代碼,從而引入新的潛在的問題。當然有個特例,就是如果現有代碼中存在已有bug,我們還是應該去解決它的。
比如現在有一個吉他,可以實現基本功能的彈奏:
/**
* 吉他
*/
public class Guitar {
private String make;
private String model;
private int volume;
}
但是用了一段時間后,覺得有點無聊想增加一些音節,讓它用起來更加的搖滾。
我們如果直接在原有的Guitar類上修改,可能會把原有的功能破壞掉從而引入新的問題,所以根據“開閉原則”,我們應該在原有基礎上進行擴展而不是修改:
/**
* 更酷炫的音節
*/
public class SuperCoolGuitarWithFlames extends Guitar {
private String flameColor;
}
通過擴展實現,我們可以保證現有的功能不會受到破壞。
里氏替換原則
這個原則字面意思較難理解,簡單來說就是,如果一個類A是類B的子類,那么我們在不中斷程序行為的情況下可以把B替換成A,而不影響程序原有的功能。
我們用代碼示例說明:
/**
* 車
*/
public interface Car {
/**
* 打開引擎
*/
void turnOnEngine();
/**
* 加速
*/
void accelerate();
}
我們定義了一個接口,里面有兩個方法,可以實現引擎打開和車加速功能。
下面來看下具體的實現類:
/**
* 摩托車
*/
public class MotorCar {
private Engine engine;
public void turnOnEngine() {
//turn on the engine!
engine.on();
}
public void accelerate() {
//move forward!
engine.powerOn(1000);
}
}
可以看出摩托車屬于車的一種,實現了打開引擎和加速能力,我們繼續看其他實現:
/**
* 電車
*/
public class ElectricCar {
public void turnOnEngine() {
throw new AssertionError("I don't have an engine!");
}
public void accelerate() {
//this acceleration is crazy!
}
}
可以看出,上面的電車雖然實現了Car,但是它沒有引擎,所以不具有打開引擎的功能,那么這個就改變了程序的行為,違背了我們說的“里氏替換原則”。
接口隔離原則
簡單來說,就是將大的接口切分為更小的接口,這樣我們就可以確保實現類只需要關心它們感興趣的方法。
舉個例子,假如我們在動物園工作,具體是熊的“看護人”,那么可以這樣定義:
public interface BearKeeper {
/**
* 給熊洗澡
*/
void washTheBear();
/**
* 給熊喂食
*/
void feedTheBear();
/**
* 撫摸熊
*/
void petTheBear();
}
我們可以開心的喂養熊,但是撫摸熊可能有危險,但是我們的接口定義的相當大,我們別無選擇。那么,現在我們將接口拆分一下:
public interface BearCleaner {
void washTheBear();
}
public interface BearFeeder {
void feedTheBear();
}
public interface BearPetter {
void petTheBear();
}
通過拆分,我們就可以自由實現我們感興趣的方法,
public class BearCarer implements BearCleaner, BearFeeder {
public void washTheBear() {
}
public void feedTheBear() {
}
}
我們可以將撫摸熊的“福利”給那么膽大或瘋狂的人:
public class CrazyPerson implements BearPetter {
public void petTheBear() {
//Good luck with that!
}
}
依賴反轉
這個原則主要是為了解耦。代替低級模塊依賴高級模塊,二者都應該依賴抽象。
舉個例子,假如我們現有一臺windows98電腦:
public class Windows98machine {}
但是沒有顯示器和鍵盤對我們來說有什么用呢?于是我們給這臺電腦新增顯示器和鍵盤:
public class Windows98Machine {
private final StandardKeyboard keyboard;
private final Monitor monitor;
public Windows98Machine() {
monitor = new Monitor();
keyboard = new StandardKeyboard();
}
}
上面代碼運行的很好,我們的電腦已經實現了顯示器和鍵盤,那么問題解決了嗎?沒有,我們又把這三個類緊密結合在一起了。這不僅使Windows98Machine難以測試,而且還失去了將StandardKeyboard替換為其他類的能力。
讓我們把Keyboard抽象出來:
public interface Keyboard { }
public class Windows98Machine{
private final Keyboard keyboard;
private final Monitor monitor;
public Windows98Machine(Keyboard keyboard, Monitor monitor) {
this.keyboard = keyboard;
this.monitor = monitor;
}
}
public class StandardKeyboard implements Keyboard { }
現在Windows98Machine就與StandardKeyboard解耦了,通過依賴反轉(依賴抽象而不是具體類)完成了解耦。
總結
在本文中我們深入研究了面向對象的SOLID原則,并通過代碼示例說明其原理和實現。深入設計原則-SOLID