摘要:是首個個面向對象設計準則的首字母縮寫,這些準則是由提出的他更為人所熟知的名字是。單一功能原則開閉原則里氏替換原則接口隔離原則依賴反轉原則接下來讓我們看看每個原則,來了解為什么可以幫助我們成為更好的開發人員。
S.O.L.I.D?是?首個 5 個面向對象設計(OOD) 準則的首字母縮寫 ,這些準則是由 Robert C. Martin 提出的, 他更為人所熟知的名字是?Uncle Bob。
這些準則使得開發出易擴展、可維護的軟件變得更容易。也使得代碼更精簡、易于重構。同樣也是敏捷開發和自適應軟件開發的一部分。
備注:?這不是一篇簡單的介紹 "歡迎來到 _S.O.L.I.D" 的文章,這篇文章想要闡明?S.O.L.I.D?是什么。
S.O.L.I.D 意思是:擴展出來的首字母縮略詞看起來可能很復雜,實際上它們很容易理解。
S?- 單一功能原則
O?- 開閉原則
L?- 里氏替換原則
I?- 接口隔離原則
D?- 依賴反轉原則
接下來讓我們看看每個原則,來了解為什么 S.O.L.I.D 可以幫助我們成為更好的開發人員。
單一職責原則縮寫是 S.R.P ,該原則內容是:
一個類有且只能有一個因素使其改變,意思是一個類只應該有單一職責.
例如,假設我們有一些圖形,并且想要計算這些圖形的總面積.是的,這很簡單對不對?
class Circle { public $radius; public function construct($radius) { $this->radius = $radius; } } class Square { public $length; public function construct($length) { $this->length = $length; } }
首先,我們創建圖形類,該類的構造方法初始化必要的參數.接下來,創建AreaCalculator 類,然后編寫計算指定圖形總面積的邏輯代碼.
class AreaCalculator { protected $shapes; public function __construct($shapes = array()) { $this->shapes = $shapes; } public function sum() { // logic to sum the areas } public function output() { return implode("", array( "", "Sum of the areas of provided shapes: ", $this->sum(), "" )); } }
AreaCalculator?使用方法,我們只需簡單的實例化這個類,并且傳遞一個圖形數組,在頁面底部展示輸出內容.
$shapes = array( new Circle(2), new Square(5), new Square(6) ); $areas = new AreaCalculator($shapes); echo $areas->output();
輸出方法的問題在于,AreaCalculator 處理了數據輸出邏輯.因此,假如用戶希望將數據以 json 或者其他格式輸出呢?
所有邏輯都由 AreaCalculator 類處理,這恰恰違反了單一職責原則(SRP); AreaCalculator 類應該只負責計算圖形的總面積,它不應該關心用戶是想要json還是HTML格式數據。
因此,要解決這個問題,可以創建一個 SumCalculatorOutputter 類,并使用它來處理所需的顯示邏輯,以處理所有圖形的總面積該如何顯示。
SumCalculatorOutputter 類的工作方式如下:
$shapes = array( new Circle(2), new Square(5), new Square(6) ); $areas = new AreaCalculator($shapes); $output = new SumCalculatorOutputter($areas); echo $output->JSON(); echo $output->HAML(); echo $output->HTML(); echo $output->JADE();
現在,無論你想向用戶輸出什么格式數據,都由 SumCalculatorOutputter 類處理。
開閉原則對象和實體應該對擴展開放,但是對修改關閉.
簡單的說就是,一個類應該不用修改其自身就能很容易擴展其功能.讓我們看一下?AreaCalculator?類,特別是?sum?方法.
public function sum() { foreach($this->shapes as $shape) { if(is_a($shape, "Square")) { $area[] = pow($shape->length, 2); } else if(is_a($shape, "Circle")) { $area[] = pi() * pow($shape->radius, 2); } } return array_sum($area); }
如果我們想用?sum?方法能計算更多圖形的面積,我們就不得不添加更多的?if/else blocks?,然而這違背了開閉原則.
讓這個 sum 方法變得更好的方式是將計算每個形狀面積的代碼邏輯移出 sum 方法,將其放進各個形狀類中:
class Square { public $length; public function __construct($length) { $this->length = $length; } public function area() { return pow($this->length, 2); } }
相同的操作應該被用來處理?Circle?類, ?在類中添加一個 area 方法。? 現在,計算任何形狀面積之和應該像下邊這樣簡單:
public function sum() { foreach($this->shapes as $shape) { $area[] = $shape->area(); } return array_sum($area); }
接下來我們可以創建另一個形狀類并在計算總和時傳遞它而不破壞我們的代碼。 然而現在又出現了另一個問題,我們怎么能知道傳入 ?AreaCalculator?的對象實際上是一個形狀,或者形狀對象中有一個?area 方法?
接口編碼是實踐 S.O.L.I.D 的一部分,例如下面的例子中我們創建一個接口類,每個形狀類都會實現這個接口類:
interface ShapeInterface { public function area(); } class Circle implements ShapeInterface { public $radius; public function __construct($radius) { $this->radius = $radius; } public function area() { return pi() * pow($this->radius, 2); } }
在我們的?AreaCalculator 的 sum 方法中,我們可以檢查提供的形狀類的實例是否是 ShapeInterface 的實現,否則我們就拋出一個異常:
public function sum() { foreach($this->shapes as $shape) { if(is_a($shape, "ShapeInterface")) { $area[] = $shape->area(); continue; } throw new AreaCalculatorInvalidShapeException; } return array_sum($area); }里氏替換原則
如果對每一個類型為 T1的對象 o1,都有類型為 T2 的對象o2,使得以 T1定義的所有程序 P 在所有的對象 o1 都代換成 o2 時,程序 P 的行為沒有發生變化,那么類型 T2 是類型 T1 的子類型。
這句定義的意思是說:每個子類或者衍生類可以毫無問題地替代基類/父類。
依然使用?AreaCalculator?類, 假設我們有一個 VolumeCalculator?類,這個類繼承了 ?AreaCalculator?類:
class VolumeCalculator extends AreaCalulator { public function construct($shapes = array()) { parent::construct($shapes); } public function sum() { // logic to calculate the volumes and then return and array of output return array($summedData); } }
?SumCalculatorOutputter?類:
class SumCalculatorOutputter { protected $calculator; public function __constructor(AreaCalculator $calculator) { $this->calculator = $calculator; } public function JSON() { $data = array( "sum" => $this->calculator->sum(); ); return json_encode($data); } public function HTML() { return implode("", array( "", "Sum of the areas of provided shapes: ", $this->calculator->sum(), "" )); } }
如果我們運行像這樣一個例子:
$areas = new AreaCalculator($shapes); $volumes = new AreaCalculator($solidShapes); $output = new SumCalculatorOutputter($areas); $output2 = new SumCalculatorOutputter($volumes);
程序不會出問題, 但當我們使用$output2 對象調用?HTML?方法時 ,我們接收到一個?E_NOTICE?錯誤,提示我們 數組被當做字符串使用的錯誤。
為了修復這個問題,只需:
public function sum() { // logic to calculate the volumes and then return and array of output return $summedData; }
而不是讓VolumeCalculator?類的 sum 方法返回數組。
$summedData 是一個浮點數、雙精度浮點數或者整型。
接口隔離原則使用方(client)不應該依賴強制實現不使用的接口,或不應該依賴不使用的方法。
繼續使用上面的 shapes 例子,已知擁有一個實心塊,如果我們需要計算形狀的體積,我們可以在 ShapeInterface 中添加一個方法:
interface ShapeInterface { public function area(); public function volume(); }
任何形狀創建的時候必須實現 volume 方法,但是【平面】是沒有體積的,實現這個接口會強制的讓【平面】類去實現一個自己用不到的方法。
ISP?原則不允許這么去做,所以我們應該創建另外一個擁有 volume 方法的SolidShapeInterface 接口去代替這種方式,這樣類似立方體的實心體就可以實現這個接口了:
interface ShapeInterface { public function area(); } interface SolidShapeInterface { public function volume(); } class Cuboid implements ShapeInterface, SolidShapeInterface { public function area() { //計算長方體的表面積 } public function volume() { // 計算長方體的體積 } }
這是一個更好的方式,但是要注意提示類型時不要僅僅提示一個 ShapeInterface 或 SolidShapeInterface。
你能創建其它的接口,比如 ManageShapeInterface ,并在平面和立方體的類上實現它,這樣你能很容易的看到有一個用于管理形狀的api。例:
interface ManageShapeInterface { public function calculate(); } class Square implements ShapeInterface, ManageShapeInterface { public function area() { /Do stuff here/ } public function calculate() { return $this->area(); } } class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface { public function area() { /Do stuff here/ } public function volume() { /Do stuff here/ } public function calculate() { return $this->area() + $this->volume(); } }
現在在 AreaCalculator 類中,我們可以很容易地用 calculate替換對area 方法的調用,并檢查對象是否是 ManageShapeInterface 的實例,而不是 ShapeInterface 。
依賴倒置原則最后,但絕不是最不重要的:
實體必須依賴抽象而不是具體的實現.即高等級模塊不應該依賴低等級模塊,他們都應該依賴抽象.
這也許聽起來讓人頭大,但是它很容易理解.這個原則能夠很好的解耦,舉個例子似乎是解釋這個原則最好的方法:
class PasswordReminder { private $dbConnection; public function __construct(MySQLConnection $dbConnection) { $this->dbConnection = $dbConnection; } }
首先?MySQLConnection?是低等級模塊,然而 PasswordReminder?是高等級模塊,但是根據 S.O.L.I.D. 中?D?的解釋:依賴于抽象而不依賴與實現, 上面的代碼段違背了這一原則,因為?PasswordReminder?類被強制依賴于?MySQLConnection?類.
稍后,如果你希望修改數據庫驅動,你也不得不修改?PasswordReminder?類,因此就違背了?Open-close principle.
此?PasswordReminder?類不應該關注你的應用使用了什么數據庫,為了進一步解決這個問題,我們「面向接口寫代碼」,由于高等級和低等級模塊都應該依賴于抽象,我們可以創建一個接口:
interface DBConnectionInterface { public function connect(); }
這個接口有一個連接數據庫的方法,MySQLConnection?類實現該接口,在?PasswordReminder?的構造方法中不要直接將類型約束設置為?MySQLConnection?類,而是設置為接口類,這樣無論你的應用使用什么類型的數據庫,PasswordReminder?類都能毫無問題地連接數據庫,且不違背 開閉原則.?
class MySQLConnection implements DBConnectionInterface { public function connect() { return "Database connection"; } } class PasswordReminder { private $dbConnection; public function __construct(DBConnectionInterface $dbConnection) { $this->dbConnection = $dbConnection; } }
從上面一小段代碼,你現在能看出高等級和低等級模塊都依賴于抽象了。
總結說實話,S.O.L.I.D 一開始似乎很難掌握,但只要不斷地使用和遵守其原則,它將成為你的一部分,使你的代碼易被擴展、修改,測試,即使重構也不容易出現問題。
文章轉自:https://learnku.com/php/t/28922
更多文章:https://learnku.com/php/c/tra...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31593.html
摘要:前言本章我們要講解的是五大原則語言實現的第篇,里氏替換原則。因此,違反了里氏替換原則。與行為有關,而不是繼承到現在,我們討論了和繼承上下文在內的里氏替換原則,指示出的面向對象。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第3篇,里氏替換原則LSP(The Liskov Substitution Principle )。英文原文:http://fre...
摘要:如何實現持久化持久化,將在內存中的的狀態保存到硬盤中,相當于備份數據庫狀態持久化,持久化是通過保存服務器鎖執行的寫狀態來記錄數據庫的。 showImg(https://segmentfault.com/img/bVbejmD?w=643&h=404); 這是我整理的一套面試題,老鐵們看看就當復習了哦 概述 感覺現在發面試題有些冷門,就跟昨天德國那場似的,不過看看當提前復習了。提前備戰。...
摘要:,開始我們的第一篇單一職責。通過解耦可以讓每個職責工更加有彈性地變化。關于本文本文轉自大叔的深入理解系列。深入理解系列文章,包括了原創,翻譯,轉載,整理等各類型文章,原文是大叔的一個非常不錯的專題,現將其重新整理發布。 前言 Bob大叔提出并發揚了S.O.L.I.D五大原則,用來更好地進行面向對象編程,五大原則分別是: The Single Responsibility Princi...
摘要:前言本章我們要講解的是五大原則語言實現的第篇,接口隔離原則。接口隔離原則和單一職責有點類似,都是用于聚集功能職責的,實際上可以被理解才具有單一職責的程序轉化到一個具有公共接口的對象。與我們下面討論的一些小節是里關于違反接口隔離原則的影響。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第4篇,接口隔離原則ISP(The Interface Segreg...
摘要:前言本章我們要講解的是五大原則語言實現的第篇,依賴倒置原則。當應用依賴倒置原則的時候,關系就反過來了。在當靜態類型語言的上下文里討論依賴倒置原則的時候,耦合的概念包括語義和物理兩種。依賴倒置原則和依賴注入都是關注依賴,并且都是用于反轉。 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語言實現的第5篇,依賴倒置原則LSP(The Dependency Invers...
閱讀 2234·2021-11-17 09:33
閱讀 2774·2021-11-12 10:36
閱讀 3396·2021-09-27 13:47
閱讀 884·2021-09-22 15:10
閱讀 3485·2021-09-09 11:51
閱讀 1392·2021-08-25 09:38
閱讀 2757·2019-08-30 15:55
閱讀 2608·2019-08-30 15:53