即便你是一個初級開發人員,你也可能據說過 SOLID 原則。它們無處不在。工做面試時,你也許聽過這樣的問題:「你是如何評估代碼質量的呢?又是如何區分代碼的好壞呢?」通常的答案相似這樣:「我儘可能保持文件足夠小。當文件變得很大時,我將移動部分代碼到其餘文件中。」。最糟糕的答案之一是:「我看到了就知道了。」當你用此類的描述回答以後,面試官一般會問:「你據說過 SOLID 原則嗎?」。數據庫
那麼,什麼是 SOLID 原則呢?它爲何會被常常提到呢?維基百科 - SOLID 是這樣描述的:「在面向對象的計算機編程中,術語 SOLID 是對五大軟件設計原則的助記縮寫,這五個原則旨在讓軟件設計更易理解、更靈活、更易於維護。它與 GRASP 軟件設計原則並沒有關係。這些軟件設計原則是 Robert C. Martin 提出的衆多原則中的一個子集。雖然它們適用於任何面向對象的設計,可是 SOLID 原則也能夠造成諸如敏捷開發或者自適應軟件開發等方法論的核心理念。Martin 在 2000年 的論文《設計原則與設計模式》中介紹了 SOLID 原則的理論。」。編程
若是正確地遵循了 SOLID 原則,那麼它將引導你寫出維護性和可測試性更好的高質量代碼。若是需求變動,你也能夠輕鬆地修改代碼(與不遵照 SOLID 的代碼相比)。我認爲向你說起這些內容是很是重要的:這些原則關注的是可維護性、可測試性以及和人類思惟相關的更高質代碼,而不是你爲之編碼的計算機。工做在以性能爲主的領域的開發者各自有着不一樣的編程方法。因爲在咱們生活的世界裏,人工時間成本比機器時間成本更昂貴,因此大多數開發者都不在高性能需求的領域裏工做,並且他們經常被鼓勵去使用面向對象的編程方法。在這種狀況下,大部分開發者都能很好地應用 SOLID 原則。設計模式
從本文開始,我將按照單詞首字母縮寫的順序盡力向你解釋每個原則。爲了簡潔起見,我會把這個主題分爲 3 個部分。本文是我將向你介紹的前兩個原則的第 1 部分。好了,讓咱們從字母 S 開始吧。服務器
SOLID 中的 「S」 表示的是單一職責原則(Single Responsibility Principle,簡稱 SRP),它是最容易理解也多是最容易讓人忽視的一個原則。此原則意味着一個類應該只作且只能作一件事情。若是一個類未能達到它的目的,那麼你就能夠看着它並說「你作了一件事……」。網絡
舉例來講,假如咱們須要從網絡上獲取一些 JSON 數據,而後解析它,並把結果保存在本地數據庫中。根據咱們正在編碼的平臺,這種工做可使用爲數很少的代碼來實現。因爲代碼量很少,咱們可能會想把全部的邏輯所有扔到一個類中。可是,根據單一職責原則,這將會是一個糟糕的作法。咱們能夠清楚地區分 3 個不一樣的職責:從網絡上獲取 JSON 數據,解析數據,保存解析的結果到數據庫中。基於此,咱們應該有 3 個類。模塊化
第 1 個類應該只處理網絡。咱們給它提供一個 URL,而後接收 JSON 數據或者在出現問題時,收到一個錯誤信息。函數
第 2 個類應該只解析它接收到的 JSON 數據並以相應的格式返回結果。post
第 3 個類應該以相應的格式接收 JSON 數據,並把它保存在本地數據庫中。性能
爲何非要這麼麻煩呢?經過這樣分離代碼,咱們能得到什麼好處呢?其中一個好處就是可測試性。對於網絡請求類,咱們可使用一個測試的 URL 分別在請求成功和發生錯誤的測試用例下來觀察它的正確行爲。爲了測試 JSON 模塊,咱們能夠提供一個模擬的 JSON 數據,而後查看它生成的正確數據。一樣的測試原則也適用於數據庫類(提供模擬數據,在模擬的數據庫上測試結果)。
有了這些測試,若是咱們的程序出了問題,咱們能夠運行測試並查看問題發生在哪一個地方。多是服務器上的某些部分發生了改變,致使咱們接收了有損數據。或者數據是正常的,可是咱們在 JSON 解析模塊中遺漏了什麼,導致咱們不能正確地解析數據。又或者可能咱們正嘗試插入數據的數據庫中不存在某個列。經過這些測試,咱們沒必要猜想問題出在哪一個地方。看到了問題所在,咱們就努力地去解決它。
除了可測試性,咱們還擁抱了模塊化。若是項目需求變動,服務器返回數據的格式是 XML 或者其餘的自定義格式而非 JSON,那麼咱們所要作的就是編寫一個處理數據解析的新模塊,而後用這個新的代替 JSON 模塊。或者可能由於一些奇葩的理由,上述二者咱們都須要,而網絡模塊再根據一些規則調用正確的模塊。
若是咱們有一些本地的 JSON 文件須要解析並將解析的數據發給其餘模塊時該怎麼辦呢?那麼,咱們能夠把本地的 JSON 發送給咱們的解析模塊,而後獲取結果並把它用在須要的地方。若是咱們須要以 Flat 的格式(譯者注:Flat File)而不是數據庫的形式本地保存數據呢?一樣沒問題,咱們能夠用一個新的模塊來替換數據庫模塊。
如你所見,這個看似簡單的原則有不少優點。經過遵照這個原則,咱們已經可以想象的到咱們的代碼庫在可維護性方面會有重大改進。
字母「O」表示的是開閉原則( Open-Closed Principle,簡稱 OCP)。常言道,咱們的類應該對擴展開放,對修改關閉。什麼意思呢?個人理解是,咱們應該以插件式的方式來編寫類和模塊。若是咱們須要額外的功能,咱們不該該修改類,而是可以嵌入一個提供這個額外功能的不一樣類。爲了解釋個人理解,我將使用一個經典的計算器示例。這個計算器在最開始只能執行兩種運算:加法和減法。計算器類看起來像下面這樣(本文中的代碼不是用特定語言編寫的):
class Calculator {
public float add(float a, float b) {
return a + b
}
public float subtract(float a, float b) {
return a — b
}
}
複製代碼
咱們像下面這樣使用這個類:
Calculator calculator = new Calculator()
float sum = calculator.add(10, 2) //the value of sum is 12
float diff = calculator.subtract(10, 2) //the value of diff is 8
複製代碼
如今,咱們假設客戶但願爲這個計算器添加乘法功能。爲了添加這個額外的功能,咱們必須編輯計算器類並添加乘法方法:
public float multiply(float a, float b) {
return a * b
}
複製代碼
若是需求又一次改變,客戶又須要除法,sin
,cos
,pow
以及衆多的其餘數學函數,咱們不得不一次又一次編輯這個類來添加這些需求。根據開閉原則,這並非一個明智的作法。由於這意味着咱們的類能夠修改。咱們須要讓它屏蔽修改,而對擴展開放,那麼咱們該怎麼作呢?
首先,咱們定義一個名爲 Operation
的接口,這個接口只有一個名爲 compute
的方法:
interface Operation {
float compute(float a, float b)
}
複製代碼
以後,咱們能夠經過實現 Operation
接口來建立操做類(本文中提供的大多數示例也能夠經過繼承和抽象類來完成,但我更喜歡使用接口)。爲了重建簡單的計算器示例,咱們將編寫加法和減法類:
class Addition implements Operation {
public float compute(float a, float b) {
return a + b
}
}
class Subtraction implements Operation {
public float compute(float a, float b) {
return a — b
}
}
複製代碼
咱們的新計算器類只有一個名叫 calculate
的方法,在這個方法中,咱們能夠傳遞操做數與操做類:
class Calculator {
public float calculate(float a, float b, Operation operation) {
return operation.compute(a, b)
}
}
複製代碼
咱們將像下面這樣使用咱們的新類:
Calculator calculator = new Calculator()
Addition addition = new Addition()
Subtraction subtraction = new Subtraction()
float sum = calculator.calculate(10, 2, addition) //the value of sum is 12
float diff = calculator.calculate(10, 2, subtraction) //the value of diff is 8
複製代碼
如今若是咱們須要添加乘法,咱們將建立這樣的一個乘法運算類:
class Multiplication implements Operation {
public float compute(float a, float b) {
return a * b
}
}
複製代碼
而後經過添加如下內容在上面的示例中使用它:
Multiplication multiplication = new Multiplication()
float prod = calculator.calculate(10, 2, multiplication) // the value of prod is 20
複製代碼
咱們終於能夠說咱們的計算器類對修改關閉,對擴展開放了。看一下這個簡單的例子,你可能會說將這些額外的方法添加到原始的計算器類中也沒什麼大問題,還有就是可能更好的實現也就意味着編寫更多的代碼。誠然,在這個簡單的情景中,我更贊同你的說法。可是,在現實生活裏的複雜情景下,遵照開閉原則編碼將大有裨益。也許你須要爲每一個新功能添加遠不止那三個方法,也許這些方法很是複雜。然而經過遵循開閉原則,咱們能夠用不一樣的類外化新的功能。它將有助於咱們以及他人更好地理解咱們的代碼,而這主要是由於咱們必須專一於較小的代碼塊而不是滾動無休止的文件。
爲了更好地可視化這個概念,咱們能夠把計算器類視爲第三方庫的一部分,而且沒法訪問其源碼。好的實現就是編寫它的善良的人們遵照了開閉原則,使它對擴展開放。所以,咱們可使用本身的代碼擴展其功能,並輕鬆地在咱們的項目中使用它。
若是這聽起來仍讓人犯傻,那就這樣想吧:你剛剛爲客戶編寫了一個很棒的軟件,它完成了客戶想要的一切。你盡最大能力編寫了全部的內容,而且代碼質量使人驚歎。數週後,客戶想要新的功能。爲了實現它們,你必須潛心投入到你的漂亮代碼中,修改各類文件。這樣作,有可能代碼質量會受到影響,特別是在截止日期緊張時。若是你已經爲你的代碼編寫了測試(這也是你應該作的),那麼這些修改可能會破壞一些測試,你還必須修改這些測試。
這與遵照了開閉原則編寫的代碼造成了鮮明的對比。要實現新功能,你只需編寫新代碼便可。舊代碼保持不變。你全部的舊測試仍然有效。由於咱們不是生活在一個完美的世界中,因此在某些跑偏的狀況下,你可能仍然會對舊代碼的某些部分進行細微的更改,但這與非開閉原則帶來的修改相比則能夠忽略不計。
除此以外,遵循開閉原則的編碼方式還能讓你在心理上得到極大的愉悅體驗。其中一個就是,你只須要編寫新代碼,而無須爲了實現新功能對你引覺得傲的代碼痛下殺手。經過爲新功能編寫新代碼,而不是修改舊代碼,高漲的團隊士氣將隨之而來。這能夠提升生產效率,從而減小工做壓力,改善生活質量。
我但願你能看到這個原則的重要性。不過使人沮喪的是,在一個真實的項目中,主要是因爲缺少魔法水晶球的能力,咱們很難預見將來以及應該如何、在哪裏應用這個原則。可是,知道了開閉原則的確有助於在需求來臨時識別出可能的用例。在一開始的實現中,當客戶想要給這個計算器添加乘法和除法功能時,咱們隨手將這兩個方法添加到了 Calculator
類中。接下來,當他還要 sin
、cos
時,咱們也許會對本身說:「等會兒……」。等待事後,咱們開始重構代碼以適配開閉原則來避免未來可能遇到的麻煩。如今,當客戶還想要 tan
、pow
以及其餘功能時,咱們早就搞定了。
……
你能夠在 什麼是SOLID原則(第2部分) 閱讀下兩個 SOLID 原則。若是你喜歡這篇文章,你能夠在咱們的 官方站點 上找到更多信息。