S.O.L.I.D 面向對象設計和編程(OOD&OOP)筆記

SOLID是 面向對象編程面試對象設計 的五個基本原則,應用這五個原則能建立一個易於維護和擴展的軟件系統。SOLID能夠指導代碼重構和在迭代的過程當中進行代碼清掃,以使得軟件源代碼清晰可讀和具備良好的擴展性。在測試驅動開發中是典型應用場景,而且也是敏捷開發和自適應軟件開發基本原則的重要組成部分。javascript

首字母-簡寫(全稱) 指代 概念
S-SRP(Single Responsibility Principle) 單一功能原則 對象應該僅具備一種單一功能
O-OCP(Opened Closed Principle) 開閉原則 軟件應該是對於擴展開放的,但對於修改封閉的
L-LSP(Liscov Substitution Principle) 里氏替換原則 程序中的對象應該是能夠在不改變程序正確性的前提下被他的子類所替換
I-ISP(Interface Segregation Principle) 接口隔離原則 多個特定客戶端接口要好於一個寬泛用途的接口
D-DIP(Dependency Inversion Principle) 依賴反轉原則 一個方法應該聽從「依賴於抽象而不是一個實例」

單一功能原則(S)

翻譯:你能夠這樣幹,並非說你應該這樣幹php

引發類變化的因素永遠不要多於一個,也就是說一個類有且只有一個職責。java

若是一個類包含多個職責,代碼會變得耦合,難以維護和集中管理。好比咱們利用 PHP 的 composer 去定義一個自動加載文件:面試

"autoload": {
        "files": [
            "app/Helpers.php"
        ]
    },複製代碼

你們喜歡把一些全局函數定義在這個文件中,若是項目較小或維護者只有幾個的時候,這個文件維護仍是較爲方便的,但項目一旦變大,有大量的全局函數寫到這個文件中就會變得臃腫難以維護。編程

又好比,在 Laravel 框架的模型中,咱們既定義了數表關聯關係,又定義了服務器,修改器,還將部分數據訪問邏輯寫在模型中,模型到最後就會很臃腫並且難以維護。設計模式

經過分拆,把相同功能的函數及功能放在一塊兒,使得同一類進行高內聚,能夠更好的進行這些代碼的集中管理和維護。全局函數分拆能夠經過將不一樣功能的函數放入不一樣的文件中,而後在 Helpers.php 引入解決,而模型分拆,能夠將修改器,訪問器定義在 trait 中而後在模型中 use 分拆(PHP 5.4以上版本),數據訪問邏輯能夠經過 Repository 設計模式進行與模型分離。服務器

若是你不想作出下面這把錘子,那就重視這個問題吧。微信

開閉原則(O)

翻譯:開胸手術時不須要穿上一件外套app

軟件實體(類,模塊,函數等等)應當對擴展開放,對修改閉合。composer

這個是面向對象編程原則中最爲抽象、最難理解的。「對擴展開放」指的是設計類時要考慮到新需求提出是類能夠增長新功能,「對修改關閉」指的是一旦一個類開發完成,除了修正 BUG 就不要再去修改它。

這個原則先後兩部彷佛是衝突的,可是若是正確地社及類和它們的依賴關係,就能夠增長功能而不修改已有的源代碼。

一般能夠經過依賴關係抽象實現開閉原則,好比 interface(接口) 或 abstract(抽象類)而不是具體類,經過建立新的類實現它們來增長功能。

這個原則能減小改出來的 BUG 出現,並且增長軟件自己的靈活性。

好比支付功能,不遵照這個原則的話你可能會寫出這樣的代碼:

public function payInit($payType){
    $payment = null;
    if(true == $payType){
        // 微信支付
        $payment = acceptWechat($total);
    }else{
        // 支付寶支付
        $payment = acceptAlipay($total);
    }
    return $payment;
}複製代碼

以上的代碼中初始化了兩種支付方式,當業務增加時要增長信用卡支付只能去修改這個方法增長 elseif 或者改成 switch 並且由於忽視了業務的增加狀況,傳入參數是一個 Bool 值,不能適用這種變化,所以須要更改調用的傳值,這種修改只要一不當心就會改出 BUG 來。

更好的解決方案是:

interface PaymentMethod{ public function accept($total) } public function checkOut(PaymentMethod $pm, $total){
    return $pm->accept($total);
}複製代碼

這樣在實現一個新的支付渠道時,只要實現 PaymentMethod 接口就能夠建立一個新的支付方式,在調用時將實現接口具體類的實例傳入到 checkOut 中就能夠獲得不一樣支付渠道付款的實例,而不用每新增一個支付渠道就去修改原來的代碼。

里氏替換原則(L)

翻譯:若是它看上去像一隻鴨子,而且像鴨子同樣嘎嘎叫,可是須要電池-你可能錯誤的抽象了

當一個子類的實例應該可以替換任何其父類的實例時,它們之間才具備IS-A關係

里氏替換原則適用於繼承層次結構,指設計類時客戶端依賴的父類能夠被子類替換,而客戶端無須瞭解這個變化。

一個違反LSP的典型例子是 Square(正方形) 類派生於 Rectangle(長方形) 類。Square 類老是假定寬度與高度相等。若是一個正方形對象用於指望一個長方形的上下文中,可能會出現意外行爲,由於一個正方形的寬高不能(或者說不該該)被獨立修改。

  • 若是沒有 LSP,類繼承就會混亂;
  • 若是子類做爲一個參數傳遞給方法,將會出現未知行爲;
  • 若是沒有 LSP,適用與基類的單元測試將不能成功用於測試子類;

若違反 LSP 進行設計,將致使不明確的行爲產生,不明確也意味着它在開發過程當中運行良好,但生產環境下會出現偶發 BUG,咱們不得不去查閱上百兆的日誌找出錯誤發生在什麼地方。

接口隔離原則(I)

翻譯:我須要食物,我想吃(食物,食物),不要去點亮枝狀大燭臺或者佈置餐桌。

不要強迫客戶端(泛指調用者)去依賴那些他們不使用的接口

當咱們使用非內聚的接口時,ISP 原則指導咱們建立多個較小的高內聚接口。

好比咱們建立一個鳥類接口 Bird,這個接口中包括了鳥類的不少行爲,其中有一個行爲是飛行方法 Fly(),可是此時咱們要建立一個 Ostrich(鴕鳥)類,那麼仍是須要實現沒必要要的 Fly() 方法,此時這個臃腫的接口就應該拆成 Bird 接口和 FlyingBird 接口,Ostrich 類只需去實現 Bird 接口就能夠了,在這個接口裏沒有 Fly() 這個方法,而要建立一個 KingFisher(翠鳥) 類就去實現 FlyingBird,那麼當你要建立一個企鵝類,你以爲你應該去實現那個接口呢?

上面這個例子是單一接口實現,也許你以爲太簡單了,並且實際業務中沒法用的,咱們爲何要沒事幹去建立上面鳥類接口而且去實現它呢,那麼咱們來接下來看一個更貼近實際業務的例子吧。

想象一個 ATM 取款機,經過屏幕顯示咱們想要的不一樣信息,咱們如今要爲取款機添加一個僅在取款功能界面纔出現的信息,好比「ATM機將在您取款時收取一些費用,你贊成嗎?」。這時你有一個 Messenger 接口,你也許會直接給 Messenger 接口添加一個方法,而後去實現它。這時你就違反了 OCP 原則,代碼開始腐敗!由於全部實現 Messenger 接口的實現類都須要進行修改實現這個新添加的方法。但咱們僅僅須要在取款界面才具備這個方法。

interface Messenger {
    askForCard();
    tellInvalidCard();
    askForPin();
    tellInvalidPin();
    tellCardWasSiezed();
    askForAccount();
    tellNotEnoughMoneyInAccount();
    tellAmountDeposited();
    tellBalance();
}複製代碼

根據 ISP 原則,咱們須要將 Messenger 接口進行分切,不一樣的 ATM 功能依賴於分離後的 Messenger

interface LoginMessenger {
  askForCard();
  tellInvalidCard();
  askForPin();
  tellInvalidPin(); 
}

interface WithdrawalMessenger {
  tellNotEnoughMoneyInAccount();
  askForFeeConfirmation();
}

publc class EnglishMessenger implements LoginMessenger, WithdrawalMessenger {
  ...   
}複製代碼

依賴反轉原則(D)

翻譯:你會將一個燈直接焊接到牆上的電路嗎?

1.高層模塊不該該依賴底層模塊,二者都應該依賴其抽象
2.抽象不該該依賴於細節,細節應該依賴於抽象

interface Reader { getchar(); }
interface Writer { putchar($c)}

class CharCopier {

  public function copy(Reader reader, Writer writer) {
    $c;
    while ((c = reader.getchar()) != EOF) {
      writer.putchar();
    }
  }
}

public Keyboard implements Reader {...}
public Printer implements Writer {...}複製代碼

以上代碼片斷是一個例子,一個程序依賴於 Reader 和 Writer 接口,Keyboard 和 Printer 做爲依賴於這些抽象的細節實現了這些接口,CharCopier 是依賴於 Reader 和 Writer 實現類的底層細節,能夠傳入任何 Reader 和 Writer 的實現進行正常工做。

總結

S.O.L.I.D 原則應該是你工具箱裏頗有價值的工具,在設計下一個功能或者應用時它們就會出如今你的腦海中,下面引用 Bob 大叔的總結:

- - -
SRP 單一職責原則 一個類有且只有一個更改的緣由
OCP 開閉原則 可以不更改類而擴展類的行爲
LSP 里氏替換原則 派生類能夠替換基類使用
ISP 接口隔離原則 使用客戶端特定的細粒度接口
DIP 依賴反轉原則 依賴抽象而不是具體實現

將這些原則應用在你的項目中,建立一個優秀的應用,不要讓你的代碼腐敗。

相關文章
相關標籤/搜索