GOF《設計模式》一書中提出了七條設計原則,七原則是一種理想狀態的表達,但實際項目開發中可能會不得不打破這些原則的限制。任何基類可以出現的地方,子類一定可以出現,且必須遵從基類所有規則定義,但反過來說,除了擴展基類,我們又為什麼要違背基類規則定義呢?這一條與開關原則結合起來理解就是,基類遵循關原則,... ...
GOF《設計模式》一書中提出了七條設計原則,七原則是一種理想狀態的表達,但實際項目開發中可能會不得不打破這些原則的限制。
1. 單一職責原則(Single Responsibility Principle, SRP): There should never be more than one reason for a class to change. 言下之意做到類只承擔單一職責(最細粒度)也就能儘可能地降低類變更的可能性,不同職責要分開單獨定義。其實這一原則不僅僅適用於類,還適用於介面以及方法的設計;
2. 開閉原則(Open-Closed Principle, OCP): Softeware entities like classes,modules and functions should be open for extension but closed for modifications. 這句話翻譯過來大意就是,一個軟體實體如類、模塊和函數,應該通過擴展來實現變化,而不是通過修改已有代碼來實現變化。比如參數類型、引用對象儘量使用介面或者抽象類,而不是具體實現類;
3. 依賴倒轉原則(Dependence Inversion Principle, DIP): High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. 依賴多出現在方法參數中。高層模塊不應該依賴低層模塊(具體實現),以防止一旦低層模塊(具體實現)發生變化,將引起高層模塊不必要的改變,同時高層模塊之上可能有更高層的模塊存在,因此兩者都應該依賴於抽象。抽象不依賴具體實現細節,而讓具體實現細節依賴抽象。抽象不變,具體實現細節改變可以使影響最小化,也就是要針對介面編程。這一條與里氏代換原則結合起來更容易理解,也可以看出這些原則並不應該被孤立地運用於系統設計中,而應該協同配合起來運用;
4. 里氏代換原則(Liskov Substitution Principle, LSP): Functions that use pointers or referrnces to base classes must be able to use objects of derived classes without knowing it. 任何基類可以出現的地方,子類一定可以出現,且必須遵從基類所有規則定義,但反過來說,除了擴展基類,我們又為什麼要違背基類規則定義呢?這一條與開關原則結合起來理解就是,基類遵循關原則,子類遵循開原則,子類必須滿足LSP才允許繼承,否則就斷開這種繼承關係;
5. 介面隔離原則(Interface Segregation Principle, ISP): Clients should not be forced to depend upon interfaces that they don't use.The dependcy of one class to another one should depend on the smallest possible interface. 使用多個隔離的介面,比使用單個介面要好,也有利於降低類之間的耦合度。類間的依賴關係要建立在最小的介面之上,要防止類必須實現介面中對於自己來說無用的方法情形的出現;
6. 迪米特法則(Law of Demeter, LoD),也稱最少知識原則(Least Knowledge Principle, LKP): Only talk to your immedate friends. 一個模塊或子系統應當儘量少地與其他模塊或子系統之間發生直接相互作用,可以通過增加“即時朋友”這個中間人來中轉通信,只與“朋友”保持聯繫,與“陌生人”概不謀面,當模塊或子系統出現版本升級更新或環境移植之後,只要朋友不變就好;
7. 合成/聚合復用原則(Composite/Aggregate ReusePrinciple ,CARP):該原則要求在設計上儘量使用合成/聚合來達到復用的目的,而不是使用繼承,也就是說前者優先於後者而被運用。繼承會將基類的細節暴露給子類,也稱白箱復用,如果基類發生改變,子類也必須相應做出變動,且多繼承不易維護。CARP幾乎可用於任何環境,依賴少,但是合成/聚合造成類中多對象需要管理。下邊是CARP運用的一個實例:
/** * 合成 */ class Person { private $hand; public function Person() { $hand = New Hand(); } } /** * 聚合 */ class Person { private $hand; public function setHand() { $this->hand = New Hand(); } }
下邊是一段用來表達上述某些設計原則精神的代碼(PHP):
<?php /** * 下列代碼只用於表達《設計模式》中的某些設計原則精神。 * 如果是場景中會多次重覆創建同一計算器,可以考慮單例模式或靜態調用。 */ /** * * Calculation介面對add、sub、mul和div等二元運算類型進行了簡單的抽象定義。 * * 要執行新的計算類型,只需在新的計算器類中實現該介面即可,不必修改任何已有代碼。 * */ interface Calculation { /** * do()方法可擴展到其他二元運算類型 * * */ public function do(float $operand1, float $operand2) : float; } /** * AddCalculator 加法運算器 */ class AddCalculator implements Calculation { /** * 執行加法運算 */ public function do(float $operand1, float $operand2) : float { return $operand1 + $operand2; } } /** * SubCalculator 減法運算器 */ class SubCalculator implements Calculation { /** * 執行減法運算 */ public function do(float $operand1, float $operand2) : float { return $operand1 - $operand2; } } /** * MulCalculator 乘法運算器 */ class MulCalculator implements Calculation { /** * 執行乘法運算 */ public function do(float $operand1, float $operand2) : float { return $operand1 * $operand2; } } /** * DivCalculator 除法運算器 */ class DivCalculator implements Calculation { /** * 執行除法運算 */ public function do(float $operand1, float $operand2) : float { return $operand1 / $operand2; } } class Calculator { /** * 當前計算器 */ private $calculator; /** * 用於保存當前運算結果,可用作下一次運算的 $operand1 */ private $result = NULL; public function __construct(Calculation $calculator = NULL) { $this->calculator = $calculator; } /** * 更換當前計算器 * * @return void */ public function renewCalculator(Calculation $calculator) { $this->calculator = $calculator; } /** * 執行計算 */ public function do(float $operand1, float $operand2 = NULL, Calculation $calculator = NULL) : float { //如果$calculator != NULL,則更換當前計算器 $this->calculator = $calculator ?? $this->calculator; if ($operand2 === NULL) {//啟用前一次運算結果 $operand2 = $operand1; $operand1 = $this->result; } $this->result = $this->calculator->do($operand1, $operand2); echo "{$this->result}\n"; return $this->result; } } $cal = new Calculator(new AddCalculator()); $cal->do(12, 104.5);//116.5 $cal->do(12);//128.5 $cal->renewCalculator(new SubCalculator()); $cal->do(12);//116.5 $cal->do(104.5);//12 $cal->do(12);//0 $cal->do(12, null, new AddCalculator());//12 $cal->renewCalculator(new MulCalculator()); $cal->do(12, 104.5);//1254 $cal->do(12);//15048 $cal->renewCalculator(new DivCalculator()); $cal->do(12);//1254 $cal->do(12);//104.5 $cal->do(0);//INF. Warning: Division by zero $cal->do(INF);//NAN $cal->do('INF');//Fatal error: Uncaught TypeError: Argument 1 passed to Calculator::do() must be of the type float, string given