設(shè)計(jì)模式入門指南

0 評(píng)論 3341 瀏覽 1 收藏 17 分鐘

想知道設(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è)基于策略模式和適配器模式的變種,就可以很容易的處理這種類型的情況。

  1. class StrategyAndAdapterExampleClass?{
  2. private $_class_one;
  3. private $_class_two;
  4. private $_context;
  5. public function __construct( $context )?{
  6. $this->_context?= $context;
  7. }
  8. public function operation1()?{
  9. if( $this->_context?== “context_for_class_one” )?{
  10. $this->_class_one->operation1_in_class_one_context();
  11. }?else ( $this->_context?== “context_for_class_two” )?{
  12. $this->_class_two->operation1_in_class_two_context();
  13. }
  14. }
  15. }

很簡(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)境中使用策略模式不是更輕松么?

  1. class User?{
  2. public function CreateOrUpdate($name, $address, $mobile, $userid =?null)
  3. {
  4. if( is_null($userid)?)?{
  5. //?it?means?the?user?doesn’t?exist?yet,?create?a?new?record
  6. }?else {
  7. //?it?means?the?user?already?exists,?just?update?based?on?the?given?userid
  8. }
  9. }
  10. }

現(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),

非適配器方法

  1. $user = new User();
  2. $user->CreateOrUpdate( //inputs?);
  3. $profile = new Profile();
  4. $profile->CreateOrUpdate( //inputs?);

如果我們需要在不同的地方這么做,或者甚至在不同的項(xiàng)目中重用這些代碼,我們將不得不重新寫下這些東西。

更好的

相反我們可以這樣做:

  1. $account_domain = new Account();
  2. $account_domain->NewAccount( //inputs?);

在這種情況下,我們有一個(gè)封裝類作為我們的賬號(hào)(Account)類:

  1. class Account()
  2. {
  3. public function NewAccount( //inputs?)
  4. {
  5. $user = new User();
  6. $user->CreateOrUpdate( //subset?of?inputs?);
  7. $profile = new Profile();
  8. $profile->CreateOrUpdate( //subset?of?inputs?);
  9. }
  10. }

這樣,每當(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)建按鈕的地方。

  1. abstract class Button?{
  2. protected $_html;
  3. public function getHtml()
  4. {
  5. return $this->_html;
  6. }
  7. }
  8. class ImageButton extends Button?{
  9. protected $_html = “…”; //This?should?be?whatever?HTML?you?want?for?your?image-based?button
  10. }
  11. class InputButton extends Button?{
  12. protected $_html = “…”; //This?should?be?whatever?HTML?you?want?for?your?normal?button?();
  13. }
  14. class FlashButton extends Button?{
  15. protected $_html = “…”; //This?should?be?whatever?HTML?you?want?for?your?flash-based?button
  16. }

現(xiàn)在,我們能創(chuàng)建我們的工廠類:

  1. class ButtonFactory
  2. {
  3. public static function createButton($type)
  4. {
  5. $baseClass = ‘Button’;
  6. $targetClass =?ucfirst($type).$baseClass;
  7. if (class_exists($targetClass)?&& is_subclass_of($targetClass, $baseClass))?{
  8. return new $targetClass;
  9. }?else {
  10. throw new Exception(“The?button?type?’$type’?is?not?recognized.”);
  11. }
  12. }
  13. }

我們能像這樣使用這段代碼:

  1. $buttons = array(‘image’,‘input’,‘flash’);
  2. foreach($buttons as $b)?{
  3. echo ButtonFactory::createButton($b)->getHtml()
  4. }

輸出的應(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è)連接字體被加粗。

一旦我們建立好我們的封裝類,我們可以開始編寫了。

  1. class?HtmlLinks?{
  2. //some?methods?which?is?available?to?all?html?links
  3. }
  4. class?LogoutLink?extends?HtmlLinks?{
  5. protected?$_html;
  6. public?function?__construct()?{
  7. $this->_html?=?”Logout”;
  8. }
  9. public?function?setHtml($html)
  10. {
  11. $this->_html?=?$html;
  12. }
  13. public?function?render()
  14. {
  15. echo?$this->_html;
  16. }
  17. }
  18. class?LogoutLinkH2Decorator?extends?HtmlLinks?{
  19. protected?$_logout_link;
  20. public?function?__construct(?$logout_link?)
  21. {
  22. $this->_logout_link?=?$logout_link;
  23. $this->setHtml(“

    “?.?$this->_html?.?”

    “);

  24. }
  25. public?function?__call(?$name,?$args?)
  26. {
  27. $this->_logout_link->$name($args[0]);
  28. }
  29. }
  30. class?LogoutLinkUnderlineDecorator?extends?HtmlLinks?{
  31. protected?$_logout_link;
  32. public?function?__construct(?$logout_link?)
  33. {
  34. $this->_logout_link?=?$logout_link;
  35. $this->setHtml(““ .?$this->_html?.?”“);
  36. }
  37. public?function?__call(?$name,?$args?)
  38. {
  39. $this->_logout_link->$name($args[0]);
  40. }
  41. }
  42. class?LogoutLinkStrongDecorator?extends?HtmlLinks?{
  43. protected?$_logout_link;
  44. public?function?__construct(?$logout_link?)
  45. {
  46. $this->_logout_link?=?$logout_link;
  47. $this->setHtml(“”?.?$this->_html?.?””);
  48. }
  49. public?function?__call(?$name,?$args?)
  50. {
  51. $this->_logout_link->$name($args[0]);
  52. }
  53. }

我們可以這么使用它們:

  1. $logout_link?=?new?LogoutLink();
  2. if(?$is_logged_in?)?{
  3. $logout_link?=?new?LogoutLinkStrongDecorator($logout_link);
  4. }
  5. if(?$in_home_page?)?{
  6. $logout_link?=?new?LogoutLinkH2Decorator($logout_link);
  7. }?else?{
  8. $logout_link?=?new?LogoutLinkUnderlineDecorator($logout_link);
  9. }
  10. $logout_link->render();

這里我們能夠看到我們是如何在需要的時(shí)候結(jié)合多個(gè)裝飾者類的。既然所有的裝飾者類使用__call方法,我們?nèi)匀丝梢哉{(diào)用原始的方法。如果我們假設(shè)我們現(xiàn)在在首頁并且已經(jīng)登入了,HTML輸出應(yīng)該是:

  1. <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è)單件模式:

  1. php
  2. class?Session
  3. {
  4. private?static?$instance;
  5. public?static?function?getInstance()
  6. {
  7. if(?is_null(self::$instance)?)?{
  8. self::$instance?=?new?self();
  9. }
  10. return?self::$instance;
  11. }
  12. private?function?__construct()?{?}
  13. private?function?__clone()?{?}
  14. //??any?other?session?methods?we?might?use
  15. }
  16. //?get?a?session?instance
  17. $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)境下使用,它們可以使事情變得更糟:但是如果正確的使用,它們就是不可或缺的。

更多精彩內(nèi)容,請(qǐng)關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號(hào)或下載App
評(píng)論
評(píng)論請(qǐng)登錄
  1. 目前還沒評(píng)論,等你發(fā)揮!