🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# PHP设计模式 > 在软件开发过程中,经常出现的典型场景的典型解决方案,称为设计模式 > > 是为了程序高内聚、低耦合 > > 更深入理解面向对象 > > 有利于开发出扩展性强的程序 ## 单例模式 > 用于场景:经典例子是数据库连接(`redis,mongodb,memcache`等),通过单例模式来实现只做一次`mysql_connect()`来节约资源。 两个对象是一个的时候,才全等 1. 创建一个普通类 ~~~  class Singleton {        }  ​  $single = new Singleton(); ~~~ 2. 封锁`new`操作,将构造函数私有化 ~~~  class Singleton {      private function __construct(){}  } ~~~ 3. 留个静态方法`getInstance()`来`new`对象 ~~~  class Singleton {      private function __construct(){}            public static function getInstance()     {          return new self();     }  } ~~~ 4. `getInstance`先顶一个私有静态属性来保存实例,第一次保存之后,后面进行判断这个私有静态属性是否为空,否则就直接返回这个存储实例的私有静态属性,此时还有问题,额外定义一个子类继承这个类,将构造函数改为公有属性,则还是不行。 ~~~  class Singleton {      private static $instance = null;            private function __construct(){}            public static function getInstance()     {          if (self::$instance === null) {              self::$instance = new self();         }          return self::$instance;     }  } ~~~ 5. 用`final`防止继承时,被修改权限 方法前加`final`,方法不能被覆盖,类前加`final`,则类不能被继承 此时,还是有问题,使用`clone`函数时,又产生了一个对象 ~~~  class Singleton {      private static $instance = null;            final private function __construct(){}            public static function getInstance()     {          if (self::$instance === null) {              self::$instance = new self();         }          return self::$instance;     }  } ~~~ 6. 可以私有化`__clone()`函数 ~~~  class Singleton {      private static $instance = null;            final private function __construct(){}            public static function getInstance()     {          // 判断有没有实例化          if (self::$instance === null) {              self::$instance = new self();         }          return self::$instance;     }            final private function __clone(){}  } ~~~ 7. 优化后的就是使用`!self::$instance instanceof self`来代替上述的判断和`final`,这里前者是用来判断保存的对象不是从本身或者其派生类产生的,这样就可以避免继承之后改变构造函数公有化代来的烦恼。也减少了代码量。 ~~~  <?php  ​  class Singleton  {      private static $instance = null;  ​      // 私有 => 防止外部进行实例化 new Singleton      private function __construct()     {          echo __METHOD__;     }  ​      public static function getInstance()     {          // 判断保存的对象不是从本身或者其派生类产生的          if (!self::$instance instanceof self) {              self::$instance = new self();         }          return self::$instance;     }  ​      private function __clone()     {          // TODO: Implement __clone() method.     }  }  ​  $instance = Singleton::getInstance();  var_dump($instance); ~~~ 运行结果 ~~~  Singleton::__constructobject(Singleton)#1 (0) { } ~~~ ## 观察者模式 > 定义对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖与它的对象都得到通知并被自动更新 > > PHP中提供观察者`observer`与被观察者`subject`的接口 ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/23   * Time: 21:33   * 观察登录的次数   */  ​  class User implements SplSubject  {      public $login_num;      public $hobby;  ​      protected $observers = null;  ​      public function __construct($hobby)     {          $this->login_num = rand(1, 10);          $this->hobby     = $hobby;          $this->observers = new SplObjectStorage();     }  ​      public function login()     {          // 操作session...  ​          // 发送通知          $this->notify();     }  ​      /**       * Attach an SplObserver       * @link https://php.net/manual/en/splsubject.attach.php       * @param SplObserver $observer <p>       * The <b>SplObserver</b> to attach.       * </p>       * @return void       * @since 5.1.0       */      public function attach(SplObserver $observer)     {          // TODO: Implement attach() method.          $this->observers->attach($observer);     }  ​      /**       * Detach an observer       * @link https://php.net/manual/en/splsubject.detach.php       * @param SplObserver $observer <p>       * The <b>SplObserver</b> to detach.       * </p>       * @return void       * @since 5.1.0       */      public function detach(SplObserver $observer)     {          // TODO: Implement detach() method.          $this->observers->detach($observer);     }  ​      /**       * Notify an observer       * @link https://php.net/manual/en/splsubject.notify.php       * @return void       * @since 5.1.0       */      public function notify()     {          // TODO: Implement notify() method.          $this->observers->rewind();          while ($this->observers->valid()) {              $observer = $this->observers->current();              $observer->update($this);              $this->observers->next();         }     }  }  ​  class Security implements SplObserver  {      /**       * Receive update from subject       * @link https://php.net/manual/en/splobserver.update.php       * @param SplSubject $subject <p>       * The <b>SplSubject</b> notifying the observer of an update.       * </p>       * @return void       * @since 5.1.0       */      public function update(SplSubject $subject)     {          // TODO: Implement update() method.          if ($subject->login_num >= 3) {              echo '这是第'.$subject->login_num.'次安全登录';         } else {              echo '这是第'.$subject->login_num.'次登录,异常';         }     }  }  ​  class Ad implements SplObserver  {      /**       * Receive update from subject       * @link https://php.net/manual/en/splobserver.update.php       * @param SplSubject $subject <p>       * The <b>SplSubject</b> notifying the observer of an update.       * </p>       * @return void       * @since 5.1.0       */      public function update(SplSubject $subject)     {          // TODO: Implement update() method.          if ($subject->hobby == 'sports') {              echo '台球锦标赛预定';         } else {              echo '好好学习,天天向上';         }     }  }  ​  $user = new User('study');  $user->attach(new Security());  $user->attach(new Ad());  ​  $user->login(); ~~~ ## 简单工厂模式 > 这里拿连接多种数据库来当作案例 > > 数据库类一般会有一个连接的接口 ,这是每个数据库连接的共同的接口`Db` > > 按照正常面向接口开发来说,我们不知道各种数据库类的具体内部细节,只知道,数据库类都实现了`Db`接口 ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/23   * Time: 18:03   */  ​  // 共同接口  interface db  {      function conn();  }  ​  // 服务端开发(不知道将会被谁调用)  class DbMysql implements db  {      function conn()     {          // TODO: Implement conn() method.          echo '连接上了mysql';     }  }  ​  class DbSqlite implements db  {      function conn()     {          // TODO: Implement conn() method.          echo '连接上了sqlite';     }  }  ​  $db = new DbMysql();  $db->conn(); // 连接上了mysql ~~~ > 此时我们还是知道服务端有哪些数据库类,对于面向接口编程来说,这里我们要开始认为,客户端现在不知道服务端有什么数据库类!的情况,只知道对方开放了一个`SimpleFactory::createDB()`方法,并且方法允许传递数据库名称,由此衍生出简单工厂模式! ~~~  // ...上述代码还是必须的内容,这里省略  ​  class SimpleFactory  {      public static function createDB($type)     {          if ($type == 'mysql') {              return new DbMysql();         } else if ($type == 'sqlite') {              return new DbSqlite();         } else {              return new Exception("Error db type", 1);         }     }  }  ​  $mysql = SimpleFactory::createDB('mysql');  $mysql->conn();  ​  $sqlite = SimpleFactory::createDB('sqlite');  $sqlite->conn(); ~~~ > 此时,如果新增`Oracle`类型咋办,一方面,可以直接在上述`if/else`里再来一个`else if`,这样在`PHP`里修改个5、6条还行,但是对于静态类语言,他们是需要重新编译的,所以会很浪费时间。在`OOP`重,重要的开闭原则,对于修改是封闭的,对于扩展时开放的。这就是要我们将工厂也进行扩展,不要轻易去修改内容。 所以我们新增了一个工厂的接口提供了一个`createDB`的方法 ~~~  interface Factory  {      public function createDB();  }  ​  class MySQLFactory implements Factory  {      public function createDB()     {          // TODO: Implement createDB() method.          return new DbMysql();     }  }  ​  class SqliteFactory implements Factory  {      public function createDB()     {          // TODO: Implement createDB() method.          return new DbSqlite();     }  }  ​  // 新增Oracle的方式就变了  class Oracle implements db  {      function conn()     {          // TODO: Implement conn() method.          echo '连接上了Oracle';     }  }  class OracleFactory implements Factory  {      public function createDB()     {          // TODO: Implement createDB() method.          return new Oracle();     }  } ~~~ ## 职责链模式(chain of responsibility) > 每个对象,存储着对自己上级的引用,如果自己处理不了,就交给上一级 拿举报信息提交给能处理的人的案例来说 ~~~  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>责任链模式</title>  </head>  <body>  <h1>责任链模式</h1>  <form action="action.php" method="post">      <select name="jubao" id="">          <option value="1">粗口</option>          <option value="2">黄赌毒</option>          <option value="3">分裂国家</option>      </select>      <button type="submit">提交</button>  </form>  </body>  </html> ~~~ `action.php` ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/23   * Time: 22:13   */  ​  header('content-type: text/html;charset=utf-8');  ​  // 版主类  class Board  {      // 权限      public $power = 1;      // 上级      protected $top = 'Admin';  ​      public function process($level)     {          // 如果当前的处理者权限不够,就引导去它的上级去处理          if ($level <= $this->power) {              echo '版主删帖子';         } else {              $top = new $this->top;              $top->process($level);         }     }  }  ​  class Admin  {      protected $power = 2;      protected $top = 'Police';      public function process($level)     {          if ($level <= $this->power) {              echo '管理员封锁账号';         } else {              $top = new $this->top;              $top->process($level);         }     }  }  ​  class Police  {      protected $power;      protected $top = null;      public function process($level)     {          echo '警察抓起来';     }  }  ​  /*   * 这里还是偏向面向过程的解决方式  if ($level == 1) {      $processer = new Board();      $processer->process();  } else if ($level == 2) {      $processer = new Admin();      $processer->process();  } else {      $processer = new Police();      $processer->process();  }  */  ​  // 责任链模式来处理举报问题  // 接收举报等级  $level = $_POST['jubao'] + 0;  ​  $judge = new Board();  $judge->process($level); ~~~ ## 策略模式 和工厂模式类似 区别:应用场景进行区别 > 工厂中,根据不同的情况返回过来的对象,就干它本身的方法 > > 策略中,根据不同的情况下返回过来的对象,赋给父类,不必去管返回来的对象。 ***案例:*** ~~~  <!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>策略模式</title>  </head>  <body>  <form action="calc.php" method="post">      <input type="text" name="num1">      <select name="op" id="">          <option value="add">+</option>          <option value="sub">-</option>          <option value="mul">*</option>          <option value="div">/</option>      </select>      <input type="text" name="num2">      <p>          <input type="submit" value="计算">      </p>  </form>  </body>  </html> ~~~ ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/24   * Time: 10:31   */  ​  interface Math  {      public function calc($num1, $num2);  }  ​  class MathAdd implements Math  {      public function calc($num1, $num2)     {          // TODO: Implement calc() method.          return $num1 + $num2;     }  }  ​  class MathSub implements Math  {      public function calc($num1, $num2)     {          // TODO: Implement calc() method.          return $num1 - $num2;     }  }  ​  class MathMul implements Math  {      public function calc($num1, $num2)     {          // TODO: Implement calc() method.          return $num1 * $num2;     }  }  ​  class MathDiv implements Math  {      public function calc($num1, $num2)     {          // TODO: Implement calc() method.          return $num1 / $num2;     }  }  ​  // 一般思路,根据op,制造不同对象,并且调用  ​  // 这里我们封装一个虚拟计算器  ​  class CMath  {      protected $calc = null;  ​      public function __construct($type)     {          $calc       = 'Math'.$type;          $this->calc = new $calc();     }  ​      public function calc($num1, $num2)     {          return $this->calc->calc($num1, $num2);     }  }  ​  $type  = $_POST['op'];  $cmath = new CMath($type);  echo $cmath->calc($_POST['num1'], $_POST['num2']); ~~~ ## 装饰器模式(decorator) > 子类先获取父类的对象,然后进行修饰 > > 可以动态地添加修改类地功能 > > 一个类提供了一项功能,如果要在修改并添加额外地功能,传统地编程模式,需要写一个子类继承它,并重新实现类地方法。 > > 使用装饰器模式 ,仅需在运行时添加一个装饰器对象即可实现,可以实现最大地灵活性。 **装饰器实现文章的编辑** ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/24   * Time: 11:46   */  ​  class BaseArt  {      protected $content;      protected $art = null;  ​      public function __construct($content)     {          $this->content = $content;     }  ​      public function decorator()     {          return $this->content;     }  }  ​  // 编辑文章摘要  class EditorArt extends BaseArt  {      public function __construct(BaseArt $art)     {          $this->art = $art;          $this->decorator();     }  ​      public function decorator()     {          return $this->content = $this->art->decorator().'小编摘要';     }  }  ​  class SeoArt extends BaseArt  {      public function __construct(BaseArt $art)     {          $this->art = $art;          $this->decorator();     }  ​      public function decorator()     {          return $this->content = $this->art->decorator().'seo关键词';     }  }  ​  $art = new SeoArt(new EditorArt(new BaseArt('好好学习')));  echo $art->decorator();  ​  // 此时SeoArt和EditorArt属于兄弟关系 ~~~ ## 适配器模式 > 没搞懂有啥意义。。。 暂时给个示例代码 ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/24   * Time: 15:49   */  ​  // 适配器模式  ​  // 服务器端代码  class Tianqi  {      public static function show()     {          $today = [              'tep'  => 28,              'wind' => 7,              'sun'  => 'sunny',         ];  ​          return serialize($today);     }  }  ​  // 增加一个适配器  class AdapterTianqi extends Tianqi  {      public static function show()     {          $today = parent::show();          $today = unserialize($today);          $today = json_encode($today);  ​          return $today;     }  }  ​  // 客户端调用  $tq = unserialize(Tianqi::show());  echo '温度:', $tq['tep'], '<br>';  echo '风力:', $tq['wind'], '<br>';  echo '太阳:', $tq['sun'], '<br>';  ​  ​  // java、python再来调用,通过适配器调用  $tq1 = AdapterTianqi::show();  $tq1 = json_decode($tq1);  ​  echo '温度:', $tq1->tep, '<br>';  echo '风力:', $tq1->wind, '<br>';  echo '太阳:', $tq1->sun, '<br>'; ~~~ ## 桥接模式 消息发送案例 消息分为站内消息、email发送、手机短信发送 消息也分为普通信息、紧急信息、特急信息 传统方式:M \* N 桥接模式后:M + N可以任意两两组合 ![传统模式与桥接模式的坐标轴化](https://img.kancloud.cn/ab/f0/abf01a284ce0d67647183ae42ed3da80_948x510.png) 传统方式代码: ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/24   * Time: 16:08   */  ​  // 桥接模式  ​  // 论坛给用户发信息,可以实站内短信,email,手机  ​  interface Message  {      public function send($to, $content);  }  ​  class Zn implements Message  {      public function send($to, $content)     {          // TODO: Implement send() method.          echo '站内信给', $to, '内容:', $content;     }  }  ​  class Email implements Message  {      public function send($to, $content)     {          // TODO: Implement send() method.          echo '邮箱给', $to, '内容:', $content;     }  }  ​  class Sms implements Message  {      public function send($to, $content)     {          // TODO: Implement send() method.          echo '短信给', $to, '内容:', $content;     }  }  ​  /*  // 内容也分普通,加急,特急  class ZnCommon extends Zn{}  class ZnWarn extends Zn{}  class ZnDanger extends Zn{}  ​  // 邮箱也分以上等级 。。。  */  ​  // 信息发送方式是一个变化因素  // 信息的紧急程度也是变化因素  // 不修改父类,只考虑2个因素的组合,不停产生新类,肯定是不行的 ~~~ 桥接模式代码 ~~~  <?php  /**   * Created By basic   * Author: Virus   * Date: 2020/5/24   * Time: 16:31   */  ​  abstract class Info  {      protected $send = null;  ​      public function __construct($send)     {          $this->send = $send;     }  ​      public abstract function msg($content);      public function send($to, $content)     {          $content = $this->msg($content);          $this->send->send($to, $content);     }  }  ​  class Zn  {      public function send($to, $content)     {          echo '站内给', $to, '内容是:', $content;     }  }  ​  class Email  {      public function send($to, $content)     {          echo 'Email给', $to, '内容是:', $content;     }  }  ​  class Sms  {      public function send($to, $content)     {          echo '短信给', $to, '内容是:', $content;     }  }  ​  class CommonInfo extends Info  {      public function msg($content)     {          return '普通'.$content;     }  }  ​  class WarnInfo extends Info  {      public function msg($content)     {          return '紧急'.$content;     }  }  ​  class DangerInfo extends Info  {      public function msg($content)     {          return '特急'.$content;     }  }  ​  // 用站内发普通信息  $commonInfo = new CommonInfo(new Zn());  $commonInfo->send('小明', '吃饭了');  ​  echo '<br/>';  ​  $sms = new DangerInfo(new Sms());  $sms->send('小刚', '失火了,快回家');  ​ ~~~