設(shè)計(jì)模式入門指南
![](http://image.woshipm.com/wp-files/img/95.jpg)
想知道設(shè)計(jì)模式是什么?在這篇文章中,我會(huì)解釋為什么設(shè)計(jì)模式重要。我也會(huì)提供一些PHP的例子來解釋什么時(shí)候什么情況下來使用設(shè)計(jì)模式。
什么是設(shè)計(jì)模式?
設(shè)計(jì)模式是針對(duì)我們?nèi)粘>幊虇栴}的經(jīng)過優(yōu)化的可重用的方法。一種設(shè)計(jì)模式不僅僅是可以簡(jiǎn)單集成到系統(tǒng)中的一個(gè)類或者一個(gè)庫。它是一個(gè)只能在正確的情境下使用的模板。它也不是只針對(duì)某種語言。一個(gè)好的設(shè)計(jì)模式應(yīng)該可以適用于絕大多數(shù)語言中,同時(shí)也要依賴于語言的特性。最重要的是,任何設(shè)計(jì)模式如果用錯(cuò)地方的話,就有可能變成一把雙刃劍,它可以是災(zāi)難性的而且為你造成許多問題。當(dāng)然,用在合適的地方,它就是你的救世主。
有三種基本的設(shè)計(jì)模式
- 結(jié)構(gòu)型
- 創(chuàng)造型
- 行為型
結(jié)構(gòu)型設(shè)計(jì)模式通常處理實(shí)體之間的關(guān)系,使這些實(shí)體之間更容易協(xié)同工作。
創(chuàng)造性設(shè)計(jì)模式提供了實(shí)例化機(jī)制,在合適的情境下創(chuàng)建對(duì)象變得更容易。
行為型設(shè)計(jì)模式用于實(shí)體之間的通訊,使得這些實(shí)體之間互相交流更容易和靈活。
我們?yōu)槭裁匆褂迷O(shè)計(jì)模式?
設(shè)計(jì)模式是針對(duì)程序問題按照原則仔細(xì)思考之后的解決方法。許多程序員都碰到過這些問題,并且針對(duì)這些問題對(duì)癥下藥。如果你遇到這些問題,為什么不使用已經(jīng)被證明過的方法而要自己重新創(chuàng)建一個(gè)呢?
示例
讓我們?cè)O(shè)想一下,你得到了一個(gè)任務(wù),根據(jù)情況將兩個(gè)不同行為的類合并到一起。這兩個(gè)類大量應(yīng)用于現(xiàn)有系統(tǒng)中的不同地方,這將使得移除這兩個(gè)類而且改變現(xiàn)有的代碼非常困難。為了作出這些改變,改變現(xiàn)有代碼同樣要測(cè)試改變后的代碼,因?yàn)橄到y(tǒng)中可能在不同的組件中依賴這些改變,這將引入新的bug。取而代之,你可以實(shí)現(xiàn)一個(gè)基于策略模式和適配器模式的變種,就可以很容易的處理這種類型的情況。
- 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();
- }
- }
- }
很簡(jiǎn)單吧?,F(xiàn)在,我們可以仔細(xì)了解一下策略模式。
策略模式
在上面的例子中,采用的策略是根據(jù)類初始化時(shí)$context變量的值決定。如果context值為class_one,將使用class_one,否則使用class_two。
聰明吧,但是我能在什么地方使用呢?
設(shè)想你現(xiàn)在正在設(shè)計(jì)一個(gè)可以更新或者創(chuàng)建新的用戶記錄的類。它仍然需要同樣的輸入(name, address, mobile number等等),但是,根據(jù)給定的情況,當(dāng)更新或者創(chuàng)建時(shí)不得不采用不同的方法?,F(xiàn)在,你可能只使用一個(gè)if-else來完成這個(gè)。但是,要是你在一個(gè)不同的地方需要這個(gè)類咋辦?在這種情況下,你將不得不一遍又一遍地重寫同樣的if-else語句。在這種上下文環(huán)境中使用策略模式不是更輕松么?
- 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
- }
- }
- }
現(xiàn)在,通常的策略模式包括封裝你的算法在另外一個(gè)類中,但是在這種情況下,創(chuàng)建另外一個(gè)類可能會(huì)比較浪費(fèi)。記住你并不是必須采用這種模板。在類似的情況中采用這種變化,就可以解決問題。
適配器模式
這同樣可以讓你改變一些從客戶端類接收到的輸入,使其和被適配者的功能吻合。
我能怎樣使用它?
表述一個(gè)適配器類的另外一個(gè)術(shù)語是封裝,表示允許你把行為封裝到一個(gè)類中,并且在正確的情形下重用這些行為。一個(gè)經(jīng)典的例子,當(dāng)你為表創(chuàng)建一個(gè)領(lǐng)域類,你可以使用一個(gè)適配器類封裝所有的方法到一個(gè)方法中,而不是調(diào)用不同的表并且一個(gè)一個(gè)的使用它們的方法。這不僅允許你重用你想使用的任何行為,如果你需要在不同的地方使用相同的行為的話,同樣使你不必重寫代碼。
比較著兩個(gè)實(shí)現(xiàn),
非適配器方法
- $user = new User();
- $user->CreateOrUpdate( //inputs?);
- $profile = new Profile();
- $profile->CreateOrUpdate( //inputs?);
如果我們需要在不同的地方這么做,或者甚至在不同的項(xiàng)目中重用這些代碼,我們將不得不重新寫下這些東西。
更好的
相反我們可以這樣做:
- $account_domain = new Account();
- $account_domain->NewAccount( //inputs?);
在這種情況下,我們有一個(gè)封裝類作為我們的賬號(hào)(Account)類:
- class Account()
- {
- public function NewAccount( //inputs?)
- {
- $user = new User();
- $user->CreateOrUpdate( //subset?of?inputs?);
- $profile = new Profile();
- $profile->CreateOrUpdate( //subset?of?inputs?);
- }
- }
這樣,每當(dāng)你需要賬戶類的時(shí)候你就能使用它。此外,你也可以在領(lǐng)域類中封裝其他類。
工廠方法模式
這個(gè)模式的主要目標(biāo)是把不同類的創(chuàng)建過程封裝到一個(gè)單獨(dú)的方法中。通過為工廠方法提供正確的上下文環(huán)境,它能夠返回正確的對(duì)象。
何時(shí)能使用它?
使用工廠方法模式的最佳時(shí)機(jī)是當(dāng)你有各種各樣的不同的獨(dú)立實(shí)體的時(shí)候。比如說你有個(gè)按鈕類,這個(gè)類有很多不同的變種,如圖片按鈕,輸入按鈕和Flash按鈕。根據(jù)需要,你可能要?jiǎng)?chuàng)建不同的按鈕——這就是你能使用工廠為你創(chuàng)建按鈕的地方。
- 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
- }
現(xiàn)在,我們能創(chuàng)建我們的工廠類:
- 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()
- }
輸出的應(yīng)該是所有的HTML按鈕類型。這樣,你將能夠根據(jù)情況說明該創(chuàng)建哪個(gè)按鈕并且重用這些條件。
裝飾者模式
裝飾者模式的目標(biāo)就是擴(kuò)展的功能可以被適用在一個(gè)特定的實(shí)例,而且同時(shí)可以能夠創(chuàng)建一個(gè)不具備這個(gè)擴(kuò)展功能的原始實(shí)例。裝飾者模式同時(shí)允許為一個(gè)實(shí)例使用多個(gè)裝飾者類,這樣你就不必糾纏于為每個(gè)實(shí)例創(chuàng)建一個(gè)裝飾者類。這個(gè)模式在繼承時(shí)是可選擇的,繼承指的是你可以從一個(gè)父類中繼承父類的功能。不同于繼承在編譯時(shí)添加行為,在情況允許下,裝飾允許你在運(yùn)行時(shí)添加一個(gè)新的行為。
我們可以根據(jù)以下幾步實(shí)現(xiàn)裝飾者模式:
1. 創(chuàng)建一個(gè)裝飾者類繼承原始組件。
2. 在裝飾者類中,添加一個(gè)組件域。
3. 在裝飾者類的構(gòu)造函數(shù)中初始化這個(gè)組件。
4. 在裝飾者類中,重新將所有的調(diào)用新組件的方法。
5. 在裝飾者類中,重寫所有需要改變行為的組件方法。
我該何時(shí)使用?
當(dāng)你擁有一個(gè)實(shí)體,這個(gè)實(shí)體僅在環(huán)境需要的時(shí)候擁有新的行為,這就是使用裝飾者模式的地方。比如你有一個(gè)HTML連接元素,一個(gè)退出連接,你想根據(jù)當(dāng)前的頁面做一些稍微不同的事情。為了達(dá)到那個(gè)目標(biāo),我們可以使用裝飾者模式。
首先,我們根據(jù)需要建立不同封裝者。
1. 如果在首頁并且已經(jīng)登入了,我們希望這個(gè)連接被
標(biāo)簽封裝起來。
2. 如果我們?cè)谝粋€(gè)不同的頁面并且已經(jīng)登入,我們希望這個(gè)連接被underline標(biāo)簽封裝起來。
3. 如果我們登入了,我們希望這個(gè)連接字體被加粗。
一旦我們建立好我們的封裝類,我們可以開始編寫了。
- 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();
這里我們能夠看到我們是如何在需要的時(shí)候結(jié)合多個(gè)裝飾者類的。既然所有的裝飾者類使用__call方法,我們?nèi)匀丝梢哉{(diào)用原始的方法。如果我們假設(shè)我們現(xiàn)在在首頁并且已經(jīng)登入了,HTML輸出應(yīng)該是:
- <strong><h2><a?href=”logout.php”>Logouta>h2>strong>
單件模式
因?yàn)閱渭兞繉?duì)于所有的調(diào)用都是一樣的,這使得其他對(duì)象使用單件實(shí)例更簡(jiǎn)單。
我該何時(shí)使用?
如果你需要把一個(gè)特定的實(shí)例從一個(gè)類傳遞到另外一個(gè)類,你能夠使用單件模式來避免不得不通過構(gòu)造函數(shù)或者參數(shù)傳遞這個(gè)實(shí)例。設(shè)想你已經(jīng)創(chuàng)建了一個(gè)會(huì)話(Session)類,模仿了$_SESSION全局?jǐn)?shù)組。既然這個(gè)類僅需要被實(shí)例化一次,我們可以這樣實(shí)現(xiàn)一個(gè)單件模式:
- 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();
通過這樣,我們可以在代碼中不同的部分訪問我們的會(huì)話類,即使在不同的類中。這個(gè)類將存在于所有調(diào)用getInstance方法中。
結(jié)論
其實(shí)還有更多的設(shè)計(jì)模式需要學(xué)習(xí);在這篇文章中,我僅列舉了在我編程過程中使用的其中一些著名的模式。如果你對(duì)其他設(shè)計(jì)模式感興趣,Wikipedia的設(shè)計(jì)模式頁面有足夠的信息。如果那還不夠,你可以參閱設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖A(chǔ),這是一本最棒的設(shè)計(jì)模式書籍之一。
最后:當(dāng)你使用這些設(shè)計(jì)模式時(shí),一定要明確你正在解決正確的問題。如我前面所提到的,這些設(shè)計(jì)模式是一把雙刃劍:如果在錯(cuò)誤的環(huán)境下使用,它們可以使事情變得更糟:但是如果正確的使用,它們就是不可或缺的。
- 目前還沒評(píng)論,等你發(fā)揮!