1. 程式人生 > >php設計模式(二):結構型模式

php設計模式(二):結構型模式

     原文請見

     可以線上執行檢視效果哦!    

        上一篇我們介紹了設計模式的特性並且詳細講解了4種建立型模式,建立型模式是負責如何產生物件例項的,現在我們繼續來給大家介紹結構型模式。

一、什麼是結構型模式?

         結構型模式是解析類和物件的內部結構和外部組合,通過優化程式結構解決模組之間的耦合問題。

二、結構型模式的種類:

  •         介面卡模式
  •         橋接模式
  •         裝飾模式
  •         組合模式
  •         外觀模式
  •         享元模式
  •         代理模式

1、 介面卡模式(Adapter)

        將一個類的介面轉換成客戶希望的另一個介面,介面卡模式使得原本的由於介面不相容而不能一起工作的那些類可以一起工作。

        應用場景:老程式碼介面不適應新的介面需求,或者程式碼很多很亂不便於繼續修改,或者使用第三方類庫。

 程式碼實現

 <?php
  /**
   * 優才網公開課示例程式碼
   *
   * 介面卡模式
   *
   * @author 優才網全棧工程師教研組
   * @see http://www.ucai.cn
   */
   
  //老的程式碼
  class User {
         private $name;
         function __construct($name) {
                $this->name = $name;
         }
      public function getName() {
       return$this->name;
      }
  }
   
  //新程式碼,開放平臺標準介面
  interfaceUserInterface {
         function getUserName();
  }
  class UserInfoimplements UserInterface {
         protected $user;
         function __construct($user) {
                $this->user = $user;
         }
         public function getUserName() {
                return$this->user->getName();
         }
  }
    
  $olduser = newUser('張三');
  echo$olduser->getName()."\n";
  $newuser = newUserInfo($olduser);
  echo$newuser->getUserName()."\n";
   
   
  ?>

         注意點:這裡的新介面使用了組合方式,UserInfo內部有一個成員變數儲存老介面User物件,模組之間是鬆耦合的,這種結構其實就是組合模式。不要使用繼承,雖然UserInfo繼承User也能達到同樣的目的,但是耦合度高,相互產生影響。

 2、 橋接模式

         將抽象部分與它的實現部分分離,使它們都可以獨立變化

         特點:獨立存在,擴充套件性強

         應用:需要不斷更換呼叫物件卻執行相同的呼叫方法,實現擴充套件功能

 程式碼實現

  <?php
  /**
   * 優才網公開課示例程式碼
   *
   * 橋接模式
   *
   * @author 優才網全棧工程師教研組
   * @see http://www.ucai.cn
   */
   
  abstract classPerson {
         abstract function getJob();
  }
   
  class Studentextends Person {
         public function getJob() {
                return '學生';
         }
  }
   
  class Teacherextends Person {
         public function getJob() {
                return '老師';
         }
  }
   
  class BridgeObj {
         protected $_person;
   
         public function setPerson($person) {
                $this->_person = $person;
         }
   
         public function getJob() {
                return$this->_person->getJob();
         }
  }
         
  $obj = newBridgeObj();
  $obj->setPerson(newStudent());
  printf("本次橋接物件:%s\n", $obj->getJob());
  $obj->setPerson(newTeacher());
  printf("本次橋接物件:%s\n", $obj->getJob());
   
   
  ?>

3、 裝飾模式

        動態地給一個物件新增額外的職責。在原有的基礎上進行功能增強。

        特點:用來增強原有物件功能,依附於原有物件。

        應用:用於需要對原有物件增加功能而不是完全覆蓋的時候

程式碼實現

  <?php
  /**
   * 優才網公開課示例程式碼
   *
   * 裝飾模式
   *
   * @author 優才網全棧工程師教研組
   * @see http://www.ucai.cn
   */
   
  //產品
  abstract classPerson {
         abstract function getPermission();
  }
  //被裝飾者
  class User extendsPerson {
         public function getPermission() {
                return '公開文件';
         }
  }
  //裝飾類
  class PermUserextends Person {
         protected $_user;
         protected $_special = '';
         function __construct($user) {
                $this->_user = $user;
         }
         public function getPermission() {
                return$this->_user->getPermission() . $this->_special;
         }
  }
  //裝飾類產品
  class JavaUserextends PermUser {
         protected $_special = ' java程式';
  }
  class CPlusUserextends PermUser {
         protected $_special = ' c++程式';
  }
   
   
  $user = new User();
  printf("permission:%s\n", $user->getPermission());
  $user = newJavaUser($user);
  printf("permission:%s\n", $user->getPermission());
  $user = newCPlusUser($user);
  printf("permission:%s\n", $user->getPermission());
   
   
  ?>

        大家想想裝飾和繼承的區別在哪?

        如果是上面的例子,如果用繼承,是CPlusUser繼承JavaUser還是反過來呢?誰也不知道最終使用者需要哪一種。

        在多層關係的情況下,裝飾是和順序無關並且隨時增加裝飾,而繼承只能是特定的順序,所以裝飾模式會更加的靈活。

4、組合模式

       將物件組合成樹形結構表示“部分-整體”的層次結構。

       特點:靈活性強

       應用:物件的部分-整體的層次結構,模糊組合物件和簡單物件處理問題

程式碼實現

  <?php
  /**
   * 優才網公開課示例程式碼
   *
   * 組合模式
   *
   * @author 優才網全棧工程師教研組
   * @see http://www.ucai.cn
   */
   
  //繼承模式
   
  class UserBaseInfo {
         private $name;
   
         function __construct($name) {
                $this->name = $name;
         }
      public function getName() {
       return$this->name;
      }
  }
  class User extendsUserBaseInfo {
         private $login = false;
         
      public function setLogin($islogin) {
       $this->login= $islogin;
      }
      public function isLogin() {
       return$this->login;
      }
  }
   
  $user = new User('張三');
  $user->setLogin(true);
  if($user->isLogin()) {
         echo $user->getName()."已經登入了\n";
  } else {
         echo $user->getName()."還沒有登入\n";
  }
   
   
  //組合模式
   
  class LoginInfo {
         protected $user;
         protected $login = false;
         
         public function setLogin($user, $isLogin){
                $this->user = $user;
                $this->login = $isLogin;
         }
         public function isLogin() {
                return $this->login;
         }
  }
   
  $user = new User('張三');
  $login = newLoginInfo();
  $login->setLogin($user,true);
  if($login->isLogin()) {
         echo $user->getName()."已經登入了\n";
  } else {
         echo $user->getName()."還沒有登入\n";
  }
   
  //部分可以更換,用繼承則不行
  class Admin {
         protected $level;
         function __construct($level) {
                $this->level = $level;
         }
         function getLevel() {
                return $this->level;
         }
  }
  $admin = newAdmin(1);
  $login->setLogin($admin,true);
  if($login->isLogin()) {
         printf("級別為 %d 的管理員已經登入了\n",$admin->getLevel());
  } else {
         printf("級別為 %d 的管理員還沒有登入\n",$admin->getLevel());
  }
   
  ?>

        上面的例子分別展示了使用繼承和組合來處理新功能,在簡單的情況下看似區別不大,但在專案後期越來越複雜的時候組合模式的優勢就越來越明顯了。

        例如上面的登入資訊,如果要增加登入次數、最後登入時間、登入ip等資訊,登入本身就會變成一個比較複雜的物件。如果以後有新的需求比如好友資訊、使用者的訪問資訊等,再要繼承的話,使用者類就會變得非常龐大,難免各父類之間沒有衝突的變數和方法,而外部訪問使用者類的眾多方法也變得很費勁。採用組合模式後,一個類負責一個角色,功能區分非常明顯,擴充套件方便。

5、 外觀模式(門面模式)

        為了系統中的一組介面提供一個一致的介面

        特點:向上抽取,有共性

        應用:內部介面眾多,由統一的介面來呼叫

程式碼實現      

  <?php
  /**
   * 優才網公開課示例程式碼
   *
   * 外觀模式,也叫門面模式
   *
   * @author 優才網全棧工程師教研組
   * @see http://www.ucai.cn
   */
   
   
  class Operation {
         public function testPlus() {
                printf("plus: %s\n", (1+ 2 == 3 ? 'true' : 'false'));
         }
         public function testMinus() {
                printf("minus: %s\n", (3- 2 == 2 ? 'true' : 'false'));
         }
         public function testTimes() {
                printf("times: %s\n", (2* 3 == 6 ? 'true' : 'false'));
         }
  }
   
  class Tester {
         protected $_operation;
         function __construct() {
                $this->_operation = newOperation();
         }
         public function testAll() {
                $this->_operation->testPlus();
                $this->_operation->testMinus();
                $this->_operation->testTimes();
         }
  }
   
  //測試用例,測試全部介面
  $tester = newTester();
  $tester->testAll();
   
   
  ?>

        門面模式估計大家在實際程式碼中都已經使用到了,介面較多時把相似功能的介面封裝成一個介面供外部呼叫,這就是門面模式。

6、 享元模式

        運用共享技術有效地支援大量細粒度物件,採用一個共享來避免大量有相同內容物件的開銷。這種開銷中最直觀的就是記憶體的損耗。

        特點:高效性,共享性

        應用:系統底層的設計。例如字串的建立。如果兩個字串相同,則不會建立第二個字串,而是第二個的引用直接指向第一個字串。$str1=”abc”,$str2=”abc”.則記憶體儲存中只會建立一個字串“abc”而引用$str1.$str2都會指向它。

7、  代理模式

         為其他物件提供一個代理來控制對這個物件的訪問,就是給某一物件提供代理物件,並由代理物件控制具體物件的引用。能夠協調呼叫者和被呼叫者,能夠在一定程度上降低系統的耦合性。

        特點:低耦合性,獨立性好,安全性

        應用:客戶訪問不到或者被訪問者希望隱藏自己,所以通過代理來訪問自己。

程式碼實現

  <?php
  /**
   * 優才網公開課示例程式碼
   *
   * 代理模式
   *
   * @author 優才網全棧工程師教研組
   * @see http://www.ucai.cn
   */
   
  //內部物件
  class User {
         public function getName() {
                return '張三';
         }
         public function getType() {
                return '付費使用者';
         }
  }
   
  //代理介面定義,例如開放平臺
  interfaceUserInterface {
         function getName();
  }
  //代理物件
  class UserProxyimplements UserInterface {
         protected $_user;
         function __construct() {
                $this->_user = new User();
         }
         public function getName() {
                return$this->_user->getName();
         }
  }
   
  //內部呼叫
  $user = new User();
  printf("username:%s\n", $user->getName());
  printf("usertype:%s\n", $user->getType());
  //外部呼叫
  // $user = newUserProxy();
  // printf("username:%s\n", $user->getName());
  // printf("usertype:%s\n", $user->getType());//不能訪問,及時知道內部物件有這個方法
   
  ?>

三、總結

      1、代理模式、介面卡模式、門面模式、裝飾模式的區別 

           a、 相同之處:都封裝一個內部物件,呼叫內部物件的方法

           b、 不同之處:各自有各自的特性和應用場景,不能相互替代。所以用的時候要仔細分析用那種合適。

      2、 關於模式的選用問題

           模式的選用要根據實際的業務需求,通過對業務邏輯的仔細分析,再根據模式具有的特性和應用場景進行合理的選擇和區分。大部分情況下業務的場景決定了哪種模式,而不是選擇哪個模式去實現一個業務,少數情況幾種模式確實都能解決問題,那主要就是考慮以後的擴充套件了。

       到這裡我們已經瞭解了7種結構型模式,下一篇我們繼續給大家介紹設計模式的行為型模式,先預覽一下行為型模式的種類吧:

         1、模版方法模式

         2、命令模式

         3、迭代器模式

         4、觀察者模式

         5、終結者模式

         6、備忘錄模式

         7、直譯器模式

         8、狀態模式

         9、策略模式

        10、職責鏈模式

        11、訪問者模式