上一篇php
Over the life time of an application, more time is spent adding to the existing codebase rather than constantly adding new features from scratch. As you are probably aware, this can be a tedious and frustrating process. Anytime you modify code, you risk introducing new bugs, or breaking old functionality completely. Ideally, we should be able to modify an existing codebase as quickly and easily as writing brand new code. If we correctly design our application according to the Open Closed principle, we can just do that!css
在一個應用的生命週期裏,大部分時間都花在了向現有代碼庫增長功能,而非一直從零開始寫新功能。正像你所想的那樣,這會是一個繁瑣且使人痛苦的過程。當你修改代碼的時候,你可能引入新的程序錯誤,或者將原來管用的功能搞壞掉。理想狀況下,咱們應該能夠像寫全新的代碼同樣,來快速且簡單的修改現有的代碼。只要採用開放封閉原則來正確的設計咱們的應用程序,那麼這是能夠作到的!數組
Open Closed Principle 開放封閉原則
The Open Closed principle of SOLID design states that code is open for extension but closed for modification.架構
開放封閉原則規定代碼對擴展是開放的,對修改是封閉的。app
To demonstrate the Open Closed principle, let's continue working with our OrderProcessor
from the previous chapter. Consider the following section of the process
method:ide
爲了演示開放封閉原則,咱們來繼續編寫上一章節的OrderProcecssor
。考慮下面的process
方法:oop
<!-- lang:php --> $recent = $this->orders->getRecentOrderCount($order->account); if($recent > 0) { throw new Exception('Duplicate order likely.'); }
This code is quite readable, and even easy to test since we are properly using dependency injection. However, what if our business rules regarding order validation change? What if we get new rules? In fact, what if, as our business grows, we get many new rules? Our process
method will quickly grow into a beast of spaghetti code that is hard to maintain. Because this code must be changed each time our business rules change, it is open for modification and violates the Open Closed principle. Remember, we want our code to be open for extension, not modification.單元測試
這段代碼可讀性很高,且由於咱們使用了依賴注入,變得很容易測試。然而,若是咱們判斷訂單的規則改變了呢?若是咱們又有新的規則了呢?更進一步,若是隨着咱們的業務發展,要增長一大堆新規則呢?那咱們的process
方法會很快變成一坨難以維護的漿糊。由於這段代碼必須隨着每次業務邏輯的改變而跟着改變,它對修改是開放的,這違反了開放封閉原則。記住,咱們但願代碼對擴展開放,而不是修改。學習
Instead of performing all of our order validation directly inside the process
method, let's define a new interface: OrderValidator
:測試
沒必要再把訂單驗證直接寫在process
方法裏面,咱們來定義一個新的接口:OrderValidator
:
<!-- lang:php --> interface OrderValidatorInterface { public function validate(Order $order); }
Next, let's define an implementation that protects against duplicate orders:
下一步咱們來定義一個實現接口的類,來預防重複訂單:
<!-- lang:php --> class RecentOrderValidator implements OrderValidatorInterface { public function __construct(OrderRepository $orders) { $this->orders = $orders; } public function validate(Order $order) { $recent = $this->orders->getRecentOrderCount($order->account); if($recent > 0) { throw new Exception('Duplicate order likely.'); } } }
Great! Now we have a small, testable encapsulation of a single business rule. Let's create another implementation that verifies the account is not suspended:
很好!咱們封裝了一個小巧的、可測試的單一業務邏輯。我們來再建立一個來驗證帳號是否停用吧:
<!-- lang:php --> class SuspendedAccountValidator implements OrderValidatorInterface { public function validate(Order $order) { if($order->account->isSuspended()) { throw new Exception("Suspended accounts may not order."); } } }
Now that we have two different implementations of our OrderValidatorInterface
, let's use them within our OrderProcessor
. We'll simply inject an array of validators into the processor instance, which will allow us to easily add and remove validation rules as our codebase evolves.
如今咱們有兩個不一樣的類實現了OrderValidatorInterface
接口。我們將在OrderProcessor
裏面使用它們。咱們只需簡單的將一個驗證器數組注入進訂單處理器實例中。這將使咱們之後修改代碼時能輕鬆的添加和刪除驗證器規則。
<!-- lang:php --> class OrderProcessor { public function __construct(BillerInterface $biller, OrderRepository $orders, array $validators = array()) { $this->biller = $bller; $this->orders = $orders; $this->validators = $validators; } }
Next, we can simply loop through the validators in the process
method:
而後咱們只要在process
方法裏面循環這個驗證器數組便可:
<!-- lang:php --> public function process(Order $order) { foreach($this->validators as $validator) { $validator->validate($order); } // Process valid order... }
Finally, we will register our OrderProcessor
class in the application IoC container:
最後咱們在 IoC 容器裏面註冊OrderProcessor
類:
<!-- lang:php --> App::bind('OrderProcessor', function() { return new OrderProcessor( App::make('BillerInterface'), App::make('OrderRepository'), array( App::make('RecentOrderValidator'), App::make('SuspendedAccountValidator') ) ); });
With these few changes, which took only minimal effort to build from our existing codebase, we can now add and remove new validation rules without modifying a single line of existing code. Each new validation rule is simply a new implementation of the OrderValidatorInterface
, and is registered with the IoC container. Instead of unit testing a large, unwieldy process
method, we can now test each validation rule in isolation. Our code is now open for extension, but closed for modification.
在現有代碼裏付出些小努力,作一些小改動以後,咱們如今能夠添加刪除新的驗證規則而沒必要修改任何一行現有代碼了。每個新的驗證規則就是對OrderValidatorInterface
的一個實現類,而後註冊進IoC容器裏。沒必要再爲那個又大又笨的process
方法作單元測試了,咱們如今能夠單獨測試每個驗證規則。如今,咱們的代碼對擴展是開放的,對修改是封閉的。
Leaky Abstractions 抽象的漏洞
Watch out for dependencies that leak implementation details. An implementation change in a dependency should not require any changes by its consumer. When changes to the consumer are required, it is said that the dependency is "leaking" implementation details. When your abstractions are leaky, the Open Closed principle has likely been broken.
當心那些缺乏實現細節的依賴(譯者注:好比上面的RecentOrderValidator)。當一個依賴的實現須要改變時,不該該要求它的調用者作任何修改。當須要調用者進行修改時,這就意味着該依賴遺漏了一些實現的細節。當你的抽象有漏洞的話,開放封閉原則就無論用了。
Before processing further, remember that this principle is not a law. It does not state that every piece of code in your application must be "pluggable". For instance, a small application that retrieves a few records out of a MySQL database does not warrant a strict adherence to every design principle you can imagine. Do not blindly apply a given design principle out of guilt as you will likely create an over-designed, cumbersome system. Keep in mind that many of these design principles were created to address common architectural problems in large, robust applications. That being said, don't use this paragraph as an excuse to be lazy!
在咱們繼續學習前,要記住這些原則不是法律。這不是說你應用中每一塊代碼都應該是「熱插拔」式的。例如,一個僅僅從MySQL檢索幾條記錄的小應用程序,不值得去嚴格遵照每一條你想到的設計原則。不要盲目的應用設計原則,那樣你會造出一個「過分設計」的繁瑣的系統。記住這些設計原則是用來解決通用的架構問題,製造大型容錯能力強的應用。我就這麼一說,你可別把它看成懶惰的藉口!