摘要:四依賴倒置原則依賴倒置原則簡介依賴倒置原則的英文名稱是,簡稱。依賴倒置原則的表現其實就是面向接口編程。依賴倒置原則的優點減少類間的耦合性,提高系統的穩定性。結合里氏替換原則使用接口負責定義屬性和方法,并且聲明與其他對象的依賴關系。
面向對象基本原則(2)- 里式代換原則與依賴倒置原則
面向對象基本原則(1)- 單一職責原則與接口隔離原則
面向對象基本原則(2)- 里式代換原則與依賴倒置原則
面向對象基本原則(3)- 最少知道原則與開閉原則
在面向對象的語言中,繼承是必不可少的、非常優秀的語言機制,它有如下優點:
代碼共享,減少創建類的工作量,每個子類都擁有父類的方法和屬性。
提高代碼的重用性。
子類可以形似父類,但又異于父類,“龍生龍,鳳生鳳,老鼠生來會打洞”是說子擁有父的“種”,“世界上沒有兩片完全相同的葉子”是指明子與父的不同。
提高代碼的可擴展性,實現父類的方法就可以“為所欲為”了,君不見很多開源框架的擴展接口都是通過繼承父類來完成的;
提高產品或項目的開放性。
自然界的所有事物都是優點和缺點并存的,繼承的缺點如下:
繼承是侵入性的。只要繼承,就必須擁有父類的所有屬性和方法。
降低代碼的靈活性。子類必須擁有父類的屬性和方法,讓子類自由的世界中多了些約束。
增強了耦合性。當父類的常量、變量和方法被修改時,需要考慮子類的修改,而且在缺乏規范的環境下,這種修改可能帶來非常糟糕的結果——大段的代碼需要重構。
1. 里式代換原則簡介里式代換原則的英文名稱是 Liskov Substitution Principle,簡稱LSP。
里式代換原則的英文定義是:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
意思是:所有引用基類的地方必須能透明地使用其子類的對象。
通俗點講,就是只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能適應。
2. 里氏替換原則為良好的繼承定義了規范 子類必須完全實現父類的方法/** * 槍支抽象類 * Class AbstractGun */ abstract class AbstractGun { public abstract function shoot(); } /** * 手槍 * Class Handgun */ class Handgun extends AbstractGun { public function shoot() { echo "手槍射擊 "; } } /** * 步槍 * Class Rifle */ class Rifle extends AbstractGun { public function shoot() { echo "步槍射擊 "; } }
如果子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生“畸變”,則建議斷開父子繼承關系,采用依賴、聚合、組合等關系代替繼承。
例如想要建立一個玩具手槍類,玩具手槍是不能用來射擊的,因此不能實現 AbstractGun 的 shoot() 方法。
子類可以有自己的獨有的方法和屬性里氏替換原則可以正著用,但是不能反過來用。
父類能出現的地方子類就可以出現,但是在子類出現的地方,父類未必就可以勝任。
狙擊槍類 AUG 繼承 Rifle 類
class AUG extends Rifle { public function shoot() { echo "AUG射擊 "; } /** * 狙擊槍特有的行為 */ public function zoomOut() { echo "通過望遠鏡察看敵人... "; } }
創建士兵類 Soldier
class Soldier { /** @var AbstractGun */ private $_gun; public function setGun(AbstractGun $gun) { $this->_gun = $gun; } public function killEnemy() { echo "士兵開始殺敵了 "; $this->_gun->shoot(); } }
實例化一個士兵,讓他使用步槍殺人
// 產生三毛這個士兵 $sanMao = new Soldier(); // 給三毛一支步槍 $sanMao->setGun(new Rifle()); $sanMao->killEnemy();
士兵開始殺敵了 步槍射擊
根據里氏代換原則,父類能出現的地方子類就可以出現,給士兵一把狙擊槍試試
//產生三毛這個士兵 $sanMao = new Soldier(); //給三毛一支狙擊槍 $sanMao->setGun(new AUG()); $sanMao->killEnemy();
士兵開始殺敵了 AUG射擊
可見,使用子類(AUG)代替父類(Rifle)完全沒有問題。
創建狙擊手類 Snipper
class Snipper { /** @var AUG */ private $_gun; public function setGun(AUG $gun) { $this->_gun = $gun; } public function killEnemy() { echo "狙擊手開始殺敵了 "; //首先看看敵人的情況 $this->_gun->zoomOut(); //開始射擊 $this->_gun->shoot(); } }
實例化一個狙擊手,讓他使用狙擊槍殺人
// 產生三毛這個狙擊手 $sanMao = new Snipper(); // 給三毛一支狙擊槍 $sanMao->setGun(new AUG()); $sanMao->killEnemy();
狙擊手開始殺敵了 通過望遠鏡察看敵人... AUG射擊
但是,如果給狙擊手一支步槍,狙擊手反而用不了了,因為步槍沒有用來觀察敵人的望遠鏡
// 產生三毛這個狙擊手 $sanMao = new Snipper(); // 給三毛一支步槍 $sanMao->setGun(new Rifle()); $sanMao->killEnemy();
PHP Fatal error: Uncaught TypeError: Argument 1 passed to Snipper::setGun() must be an instance of AUG, instance of Rifle given3. 最佳實踐
采用里氏替換原則時,盡量避免子類的“個性”,一旦子類有“個性”,這個子類和父類之間的關系就很難調和了。
把子類當做父類使用,子類的“個性”被抹殺 -- 委屈了點;
把子類多帶帶作為一個業務來使用,則會讓代碼間的耦合關系變得撲朔迷離 -- 缺乏類替換的標準。
依賴倒置原則的英文名稱是 Dependence Inversion Principle,簡稱DIP。
依賴倒置原則的英文定義是:
High level modules should not depend upon low level modules.Both should depend uponabstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻譯過來,包含以下含義:
高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象。
抽象不應該依賴細節。
細節應該依賴抽象。
什么是高層模塊?什么是低層模塊?
每一個邏輯的實現都是由原子邏輯組成的,不可分割的原子邏輯就是低層模塊,原子邏輯的再組裝就是高層模塊。
什么是抽象?什么又是細節?
抽象就是指接口或抽象類,兩者都是不能直接被實例化的。
細節就是實現類,實現接口或繼承抽象類而產生的類就是細節,其特點就是可以直接被實例化。
抽象是對實現的約束,對依賴者而言,也是一種規則,不僅僅約束自己,還同時約束自己與外部的關系,其目的是保證所有的細節不脫離規則的范疇,確保約束雙方按照既定的規則(抽象)共同發展,只要抽象這根基線在,細節就脫離不了這個圈圈。
什么是“倒置”?先說“正置”是什么意思,依賴正置就是類間的依賴是實實在在的實現類間的依賴,也就是面
向實現編程,這也是正常人的思維方式,我要開奔馳車就依賴奔馳車,我要使用筆記本電腦
就直接依賴筆記本電腦,而編寫程序需要的是對現實世界的事物進行抽象,抽象的結果就是
有了抽象類和接口,然后我們根據系統設計的需要產生了抽象間的依賴,代替了人們傳統思
維中的事物間的依賴,“倒置”就是從這里產生的。
面向接口編程 OOD(Object-Oriented Design),是面向對象設計的精髓之一。
依賴倒置原則的表現其實就是面向接口編程。
模塊間的依賴通過抽象發生,實現類之間不發生直接的依賴關系,其依賴關系是通過接口或抽象類產生的;
接口或抽象類不依賴于實現類;
實現類依賴接口或抽象類。
3. 依賴倒置原則的優點減少類間的耦合性,提高系統的穩定性。
降低并行開發引起的風險。
提高代碼的可讀性和可維護性。
4. 依賴的三種寫法 構造函數傳遞依賴對象在類中通過構造函數聲明依賴對象,按照依賴注入的說法,這種方式叫做構造函數注入。
interface ICar { public function run(); } interface IDriver { public function drive(); } class Driver implements IDriver { private $_car; public function __construct(ICar $car) { $this->_car = $car; } public function drive() { $this->_car->run(); } }Setter方法傳遞依賴對象
在抽象類中設置Setter方法聲明依賴關系,依照依賴注入的說法,這是Setter依賴注入。
interface ICar { public function run(); } interface IDriver { public function setCar(ICar $car); public function drive(); } class Driver implements IDriver { private $_car; public function setCar(ICar $car) { $this->_car = $car; } public function drive() { $this->_car->run(); } }接口聲明依賴對象
在接口的構造方法中聲明依賴對象,這種方法叫做接口注入。
interface ICar { public function run(); } interface IDriver { public function __construct(ICar $car); public function drive(); }5. 最佳實踐
依賴倒置原則的本質就是通過抽象(接口或抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊間的松耦合。需要遵循以下的幾個規則。
每個類盡量都有接口或抽象類,或者抽象類和接口兩者都具備這是依賴倒置的基本要求,接口和抽象類都是屬于抽象的,有了抽象才可能依賴倒置。
任何類都不應該從具體類派生如果一個項目處于開發狀態,確實不應該有從具體類派生出子類的情況,但這也不是絕對的,因為人都是會犯錯誤的,有時設計缺陷是在所難免的,因此一些情況下的繼承都是可以忍受的。
特別是項目維護階段,維護工作基本上都是進行擴展開發,修復行為。通過一個繼承關系,覆寫一個方法就可以修正一個很大的Bug,何必去繼承最高的基類呢?(當然這種情況盡量發生在不甚了解父類或者無法獲得父類代碼的情況下。)
如果基類是一個抽象類,而且這個方法已經實現了,子類盡量不要覆寫。
類間依賴的是抽象,覆寫了抽象方法,對依賴的穩定性會產生一定的影響。
接口負責定義public屬性和方法,并且聲明與其他對象的依賴關系。
抽象類負責公共構造部分的實現,實現類準確的實現業務邏輯,同時在適當的時候對父類進行細化。
汽車接口 ICar 和司機接口 IDriver
interface ICar { public function run(); } interface IDriver { public function __construct(ICar $car); public function drive(); }
Driver 類實現 IDriver 接口
class Driver implements IDriver { private $_car; public function __construct(ICar $car) { $this->_car = $car; } public function drive() { echo "老司機準備上路 "; $this->_car->run(); } }
Benz 和 BMW 分別實現 ICar 接口
class Benz implements ICar { public function run() { echo "奔馳汽車開始起步 "; } } class BMW implements ICar { public function run() { echo "寶馬汽車開始起步 "; } }
司機張三開奔馳車上路
// 制造一臺奔馳車 $benz = new Benz(); // 創建一個司機張三,把奔馳車給張三 $zhangSan = new Driver($benz); // 張三開奔馳車 $zhangSan->drive();
老司機準備上路 奔馳汽車開始起步
司機李四開寶馬車上路
// 制造一臺寶馬車 $bmw = new BMW(); // 創建一個司機李四,把寶馬車給李四 $lisi = new Driver($bmw); // 李四開寶馬車 $lisi->drive();
老司機準備上路 寶馬汽車開始起步
參考文獻:《設計模式之禪》
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31560.html
摘要:面向對象基本原則單一職責原則與接口隔離原則面向對象基本原則單一職責原則與接口隔離原則面向對象基本原則里式代換原則與依賴倒置原則面向對象基本原則最少知道原則與開閉原則一單一職責原則單一職責原則簡介單一職責原則的英文名稱是,簡稱。 面向對象基本原則(1)- 單一職責原則與接口隔離原則 面向對象基本原則(1)- 單一職責原則與接口隔離原則面向對象基本原則(2)- 里式代換原則與依賴倒置原則面...
摘要:六開閉原則開閉原則簡介開閉原則的英文名稱是,簡稱。開閉原則是面向對象設計中最基礎的設計原則,它指導我們如何建立一個穩定靈活的軟件系統。 面向對象基本原則(3)- 最少知道原則與開閉原則 面向對象基本原則(1)- 單一職責原則與接口隔離原則面向對象基本原則(2)- 里式代換原則與依賴倒置原則面向對象基本原則(3)- 最少知道原則與開閉原則 五、最少知道原則【迪米特法則】 1. 最少知道...
摘要:與類型庫相比,設計模式是一個更為普遍的概念。是在年,由建筑設計大師亞力山大建筑的永恒之道描述模式是一條由三部分組成的通過規則它表示了一個特定環境一類問題和一個解決方案之間的關系。設計模式是在這方面開始探索的一塊里程碑。 設計模式并非類庫 為了方便地編寫java程序,我們會使用類庫,但設計模式不是類庫。 與類型庫相比,設計模式是一個更為普遍的概念。類庫是由程序組合...
摘要:眾多面向對象的編程思想雖不盡一致,但是無論哪種面向對象編程語言都具有以下的共通功能。原型編程以類為中心的傳統面向對象編程,是以類為基礎生成新對象。而原型模式的面向對象編程語言沒有類這樣一個概念。 什么是面向對象?這個問題往往會問到剛畢業的新手or實習生上,也是往往作為一個技術面試的開頭題。在這里我們不去談如何答(fu)好(yan)問(guo)題(qu),僅談談我所理解的面向對象。 從歷...
閱讀 3241·2021-10-13 09:39
閱讀 2007·2021-09-27 13:36
閱讀 3069·2021-09-22 16:02
閱讀 2593·2021-09-10 10:51
閱讀 1574·2019-08-29 17:15
閱讀 1529·2019-08-29 16:14
閱讀 3495·2019-08-26 11:55
閱讀 2544·2019-08-26 11:50