設計模式入門指南
想知道設計模式是什么?在這篇文章中,我會解釋為什么設計模式重要。我也會提供一些PHP的例子來解釋什么時候什么情況下來使用設計模式。
什么是設計模式?
設計模式是針對我們日常編程問題的經過優化的可重用的方法。一種設計模式不僅僅是可以簡單集成到系統中的一個類或者一個庫。它是一個只能在正確的情境下使用的模板。它也不是只針對某種語言。一個好的設計模式應該可以適用于絕大多數語言中,同時也要依賴于語言的特性。最重要的是,任何設計模式如果用錯地方的話,就有可能變成一把雙刃劍,它可以是災難性的而且為你造成許多問題。當然,用在合適的地方,它就是你的救世主。
有三種基本的設計模式
- 結構型
- 創造型
- 行為型
結構型設計模式通常處理實體之間的關系,使這些實體之間更容易協同工作。
創造性設計模式提供了實例化機制,在合適的情境下創建對象變得更容易。
行為型設計模式用于實體之間的通訊,使得這些實體之間互相交流更容易和靈活。
我們為什么要使用設計模式?
設計模式是針對程序問題按照原則仔細思考之后的解決方法。許多程序員都碰到過這些問題,并且針對這些問題對癥下藥。如果你遇到這些問題,為什么不使用已經被證明過的方法而要自己重新創建一個呢?
示例
讓我們設想一下,你得到了一個任務,根據情況將兩個不同行為的類合并到一起。這兩個類大量應用于現有系統中的不同地方,這將使得移除這兩個類而且改變現有的代碼非常困難。為了作出這些改變,改變現有代碼同樣要測試改變后的代碼,因為系統中可能在不同的組件中依賴這些改變,這將引入新的bug。取而代之,你可以實現一個基于策略模式和適配器模式的變種,就可以很容易的處理這種類型的情況。
- class StrategyAndAdapterExampleClass?{
- private $_class_one;
- private $_class_two;
- private $_context;
- public function __construct( $context )?{
- $this->_context?= $context;
- }
- public function operation1()?{
- if( $this->_context?== “context_for_class_one” )?{
- $this->_class_one->operation1_in_class_one_context();
- }?else ( $this->_context?== “context_for_class_two” )?{
- $this->_class_two->operation1_in_class_two_context();
- }
- }
- }
很簡單吧?,F在,我們可以仔細了解一下策略模式。
策略模式
在上面的例子中,采用的策略是根據類初始化時$context變量的值決定。如果context值為class_one,將使用class_one,否則使用class_two。
聰明吧,但是我能在什么地方使用呢?
設想你現在正在設計一個可以更新或者創建新的用戶記錄的類。它仍然需要同樣的輸入(name, address, mobile number等等),但是,根據給定的情況,當更新或者創建時不得不采用不同的方法。現在,你可能只使用一個if-else來完成這個。但是,要是你在一個不同的地方需要這個類咋辦?在這種情況下,你將不得不一遍又一遍地重寫同樣的if-else語句。在這種上下文環境中使用策略模式不是更輕松么?
- class User?{
- public function CreateOrUpdate($name, $address, $mobile, $userid =?null)
- {
- if( is_null($userid)?)?{
- //?it?means?the?user?doesn’t?exist?yet,?create?a?new?record
- }?else {
- //?it?means?the?user?already?exists,?just?update?based?on?the?given?userid
- }
- }
- }
現在,通常的策略模式包括封裝你的算法在另外一個類中,但是在這種情況下,創建另外一個類可能會比較浪費。記住你并不是必須采用這種模板。在類似的情況中采用這種變化,就可以解決問題。
適配器模式
這同樣可以讓你改變一些從客戶端類接收到的輸入,使其和被適配者的功能吻合。
我能怎樣使用它?
表述一個適配器類的另外一個術語是封裝,表示允許你把行為封裝到一個類中,并且在正確的情形下重用這些行為。一個經典的例子,當你為表創建一個領域類,你可以使用一個適配器類封裝所有的方法到一個方法中,而不是調用不同的表并且一個一個的使用它們的方法。這不僅允許你重用你想使用的任何行為,如果你需要在不同的地方使用相同的行為的話,同樣使你不必重寫代碼。
比較著兩個實現,
非適配器方法
- $user = new User();
- $user->CreateOrUpdate( //inputs?);
- $profile = new Profile();
- $profile->CreateOrUpdate( //inputs?);
如果我們需要在不同的地方這么做,或者甚至在不同的項目中重用這些代碼,我們將不得不重新寫下這些東西。
更好的
相反我們可以這樣做:
- $account_domain = new Account();
- $account_domain->NewAccount( //inputs?);
在這種情況下,我們有一個封裝類作為我們的賬號(Account)類:
- class Account()
- {
- public function NewAccount( //inputs?)
- {
- $user = new User();
- $user->CreateOrUpdate( //subset?of?inputs?);
- $profile = new Profile();
- $profile->CreateOrUpdate( //subset?of?inputs?);
- }
- }
這樣,每當你需要賬戶類的時候你就能使用它。此外,你也可以在領域類中封裝其他類。
工廠方法模式
這個模式的主要目標是把不同類的創建過程封裝到一個單獨的方法中。通過為工廠方法提供正確的上下文環境,它能夠返回正確的對象。
何時能使用它?
使用工廠方法模式的最佳時機是當你有各種各樣的不同的獨立實體的時候。比如說你有個按鈕類,這個類有很多不同的變種,如圖片按鈕,輸入按鈕和Flash按鈕。根據需要,你可能要創建不同的按鈕——這就是你能使用工廠為你創建按鈕的地方。
- abstract class Button?{
- protected $_html;
- public function getHtml()
- {
- return $this->_html;
- }
- }
- class ImageButton extends Button?{
- protected $_html = “…”; //This?should?be?whatever?HTML?you?want?for?your?image-based?button
- }
- class InputButton extends Button?{
- protected $_html = “…”; //This?should?be?whatever?HTML?you?want?for?your?normal?button?();
- }
- class FlashButton extends Button?{
- protected $_html = “…”; //This?should?be?whatever?HTML?you?want?for?your?flash-based?button
- }
現在,我們能創建我們的工廠類:
- class ButtonFactory
- {
- public static function createButton($type)
- {
- $baseClass = ‘Button’;
- $targetClass =?ucfirst($type).$baseClass;
- if (class_exists($targetClass)?&& is_subclass_of($targetClass, $baseClass))?{
- return new $targetClass;
- }?else {
- throw new Exception(“The?button?type?’$type’?is?not?recognized.”);
- }
- }
- }
我們能像這樣使用這段代碼:
- $buttons = array(‘image’,‘input’,‘flash’);
- foreach($buttons as $b)?{
- echo ButtonFactory::createButton($b)->getHtml()
- }
輸出的應該是所有的HTML按鈕類型。這樣,你將能夠根據情況說明該創建哪個按鈕并且重用這些條件。
裝飾者模式
裝飾者模式的目標就是擴展的功能可以被適用在一個特定的實例,而且同時可以能夠創建一個不具備這個擴展功能的原始實例。裝飾者模式同時允許為一個實例使用多個裝飾者類,這樣你就不必糾纏于為每個實例創建一個裝飾者類。這個模式在繼承時是可選擇的,繼承指的是你可以從一個父類中繼承父類的功能。不同于繼承在編譯時添加行為,在情況允許下,裝飾允許你在運行時添加一個新的行為。
我們可以根據以下幾步實現裝飾者模式:
1. 創建一個裝飾者類繼承原始組件。
2. 在裝飾者類中,添加一個組件域。
3. 在裝飾者類的構造函數中初始化這個組件。
4. 在裝飾者類中,重新將所有的調用新組件的方法。
5. 在裝飾者類中,重寫所有需要改變行為的組件方法。
我該何時使用?
當你擁有一個實體,這個實體僅在環境需要的時候擁有新的行為,這就是使用裝飾者模式的地方。比如你有一個HTML連接元素,一個退出連接,你想根據當前的頁面做一些稍微不同的事情。為了達到那個目標,我們可以使用裝飾者模式。
首先,我們根據需要建立不同封裝者。
1. 如果在首頁并且已經登入了,我們希望這個連接被
標簽封裝起來。
2. 如果我們在一個不同的頁面并且已經登入,我們希望這個連接被underline標簽封裝起來。
3. 如果我們登入了,我們希望這個連接字體被加粗。
一旦我們建立好我們的封裝類,我們可以開始編寫了。
- class?HtmlLinks?{
- //some?methods?which?is?available?to?all?html?links
- }
- class?LogoutLink?extends?HtmlLinks?{
- protected?$_html;
- public?function?__construct()?{
- $this->_html?=?”Logout”;
- }
- public?function?setHtml($html)
- {
- $this->_html?=?$html;
- }
- public?function?render()
- {
- echo?$this->_html;
- }
- }
- class?LogoutLinkH2Decorator?extends?HtmlLinks?{
- protected?$_logout_link;
- public?function?__construct(?$logout_link?)
- {
- $this->_logout_link?=?$logout_link;
- $this->setHtml(“
“?.?$this->_html?.?”
“);
- }
- public?function?__call(?$name,?$args?)
- {
- $this->_logout_link->$name($args[0]);
- }
- }
- class?LogoutLinkUnderlineDecorator?extends?HtmlLinks?{
- protected?$_logout_link;
- public?function?__construct(?$logout_link?)
- {
- $this->_logout_link?=?$logout_link;
- $this->setHtml(““ .?$this->_html?.?”“);
- }
- public?function?__call(?$name,?$args?)
- {
- $this->_logout_link->$name($args[0]);
- }
- }
- class?LogoutLinkStrongDecorator?extends?HtmlLinks?{
- protected?$_logout_link;
- public?function?__construct(?$logout_link?)
- {
- $this->_logout_link?=?$logout_link;
- $this->setHtml(“”?.?$this->_html?.?””);
- }
- public?function?__call(?$name,?$args?)
- {
- $this->_logout_link->$name($args[0]);
- }
- }
我們可以這么使用它們:
- $logout_link?=?new?LogoutLink();
- if(?$is_logged_in?)?{
- $logout_link?=?new?LogoutLinkStrongDecorator($logout_link);
- }
- if(?$in_home_page?)?{
- $logout_link?=?new?LogoutLinkH2Decorator($logout_link);
- }?else?{
- $logout_link?=?new?LogoutLinkUnderlineDecorator($logout_link);
- }
- $logout_link->render();
這里我們能夠看到我們是如何在需要的時候結合多個裝飾者類的。既然所有的裝飾者類使用__call方法,我們仍人可以調用原始的方法。如果我們假設我們現在在首頁并且已經登入了,HTML輸出應該是:
- <strong><h2><a?href=”logout.php”>Logouta>h2>strong>
單件模式
因為單件變量對于所有的調用都是一樣的,這使得其他對象使用單件實例更簡單。
我該何時使用?
如果你需要把一個特定的實例從一個類傳遞到另外一個類,你能夠使用單件模式來避免不得不通過構造函數或者參數傳遞這個實例。設想你已經創建了一個會話(Session)類,模仿了$_SESSION全局數組。既然這個類僅需要被實例化一次,我們可以這樣實現一個單件模式:
- php
- class?Session
- {
- private?static?$instance;
- public?static?function?getInstance()
- {
- if(?is_null(self::$instance)?)?{
- self::$instance?=?new?self();
- }
- return?self::$instance;
- }
- private?function?__construct()?{?}
- private?function?__clone()?{?}
- //??any?other?session?methods?we?might?use
- …
- …
- …
- }
- //?get?a?session?instance
- $session?=?Session::getInstance();
通過這樣,我們可以在代碼中不同的部分訪問我們的會話類,即使在不同的類中。這個類將存在于所有調用getInstance方法中。
結論
其實還有更多的設計模式需要學習;在這篇文章中,我僅列舉了在我編程過程中使用的其中一些著名的模式。如果你對其他設計模式感興趣,Wikipedia的設計模式頁面有足夠的信息。如果那還不夠,你可以參閱設計模式:可復用面向對象軟件基礎,這是一本最棒的設計模式書籍之一。
最后:當你使用這些設計模式時,一定要明確你正在解決正確的問題。如我前面所提到的,這些設計模式是一把雙刃劍:如果在錯誤的環境下使用,它們可以使事情變得更糟:但是如果正確的使用,它們就是不可或缺的。
- 目前還沒評論,等你發揮!