• 初识设计模式


    初识设计模式

    一、设计原则

    1. SOLID

    SOLID 是最常提到的最经典的五个设计原则,分别是单一职责原则开放封闭原则里氏替换原则接口隔离原则依赖反转原则

    • 单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。它的英文描述是:

      A class or module should have a single responsibility.

      如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。

      代码示例:

      <?php
      
      class User
      {
          public int $id;
          public string $openId;
          public string $unionId;
          public string $username;
          public string $avatar;
          public string $country;
          public string $province;
          public string $city;
          public int $gender;
      
          public function getAddress()
          {
              return $this->country . ',' . $this->province . ',' . $this->city;
          }
      }
      
      # User 类里面的 getAddress() 方法是否满足单一职责原则?
      # 其实是满足的,因为在这里面 country、province、city 都是通过微信授权获取的,指用户的基本信息。
      # 如果例子里面 getAddress() 代表的是获取用户的收货地址,那么就不符合单一职责原则了,因为微信信息中的地区并不就代表收货地址,所以我们最好再新建一个 UserAddress 类。
      
    • 开放封闭原则的英文是 Open Closed Principle,缩写为 OCP。它的英文描述是:

      Software entities (modules, classes, functions, etc.) should be open for extension, but closed for modification.

      我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

      代码示例:

      class PayService
      {
          public function pay(array $params)
          {
          }
      }
      
      class PayController
      {
          public function pay()
          {
              $payService = new PayService();
              $payService->pay([
                  'order_id' => 1,
                  'order_amount' => 100,
                  'pay_amount' => 100
              ]);
          }
      }
      
      # 上面是一个支付的例子,目前只有一种支付方式,现在要支持第二种支付方式。
      # 我们做出如下改造:
      
      class PayService
      {
          public function pay(array $params, $tpye = 0)
          {
              if ($tpye) {
                  // do pay2
              } else {
                  // do pay1
              }
          }
      }
      
      class PayController
      {
          public function pay()
          {
              $payService = new PayService();
              $payService->pay([
                  'order_id' => 2,
                  'order_amount' => 100,
                  'pay_amount' => 100
              ], 1);
          }
      }
      
      # 如此一来,好像完美解决了两种支付的需求,而且能肯定不会影响到老的逻辑。
      # 但是万一又要有第三种支付方式呢?继续这样改造下去代码将混乱不堪。而且这种改造很明显违背了开闭原则。
      # 所以,正确而优雅的支付方式应该是这样的:
      
      interface Pay
      {
          public function pay(array $params);
      }
      
      class Pay1 implements Pay
      {
          public function pay(array $params)
          {
          }
      }
      
      class Pay2 implements Pay
      {
          public function pay(array $params)
          {
          }
      }
      
      class PayService
      {
          public function pay(Pay $pay, $params)
          {
              $pay->pay($params);
          }
      }
      
      class PayController
      {
          public function pay()
          {
              $payService = new PayService();
      
              // do pay1
              $payService->pay(new Pay1, [
                  'order_id' => 3,
                  'order_amount' => 100,
                  'pay_amount' => 100
              ]);
      
              // do pay2
              $payService->pay(new Pay2, [
                  'order_id' => 4,
                  'order_amount' => 100,
                  'pay_amount' => 100
              ]);
          }
      }
      
      # 这样一来,即使再加上几种支付方式,只需要创建 PayN 并实现 pay() 方法就行了。满足了对扩展开放、修改关闭。
      
    • 里式替换原则的英文是:Liskov Substitution Principle,缩写为 LSP。这个原则最早是在 1986 年由 Barbara Liskov 提出,他是这么描述这条原则的:

      If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program.

      在 1996 年,Robert Martin 在他的 SOLID 原则中,重新描述了这个原则,英文原话是这样的:

      Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.

      我们综合两者的描述,将这条原则用中文描述出来,是这样的:子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

      代码示例:

      class P
      {
          public function sortOrderByAmount(array $params)
          {
              // 根据订单金额排序
              foreach ($params as $param) {
                  $amount[] = $param['order_amount'];
              }
              array_multisort($amount, SORT_DESC, $params);
              return $params;
          }
      }
      
      class C extends P
      {
          public function sortOrderByAmount(array $params)
          {
              // 根据支付金额排序
              foreach ($params as $param) {
                  $amount[] = $param['pay_amount'];
              }
              array_multisort($amount, SORT_DESC, $params);
              return $params;
          }
      }
      
      # 此处很明显就是违背里氏替换原则的,因为子类对象不能在任何地方替换父类对象,这种情况应该在子类对象中新增一个 sortOrderByPayAmount() 方法,而不是去重写父类方法。
      
    • 接口隔离原则的英文是: Interface Segregation Principle,缩写为 ISP。Robert Martin 在 SOLID 原则中是这样定义它的:

      Clients should not be forced to depend upon interfaces that they do not use.

      直译成中文的话就是:客户端不应该被强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

      代码示例:

      interface Pay
      {
          public function pay();
          public function callback();
      }
      
      class WechatPay implements Pay
      {
          public function pay()
          {
          }
      
          public function callback()
          {
          }
      }
      
      class WalletPay implements Pay
      {
          public function pay()
          {
          }
      
          public function callback()
          {
          }
      }
      
      # 这里有两种支付方式,微信支付和钱包支付。其中微信支付是需要回调的,而钱包支付不需要,所以这么写违背了接口隔离原则。应该作如下改动:
      
      interface ExternalPay
      {
          public function pay();
          public function callback();
      }
      
      interface InteriorPay
      {
          public function pay();
      }
      
      class WechatPay implements ExternalPay
      {
          public function pay()
          {
          }
      
          public function callback()
          {
          }
      }
      
      class WalletPay implements InteriorPay
      {
          public function pay()
          {
          }
      }
      
    • 依赖反转原则的英文是:Dependency Inversion Principle,缩写为 DIP。中文翻译有时候也叫依赖倒置原则。它的英文描述是:

      High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.

      我们将它翻译成中文,大概意思就是:高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。

      代码示例:

      interface Pay
      {
          public function pay(array $params);
      }
      
      class PayService
      {
          public function pay(Pay $pay, $params)
          {
              $pay->pay($params);
          }
      }
      
      # 拿上面展示过的例子,PayService 类不应该依赖于具体的 Pay1 或者 Pay2,依赖于低层的抽象即可。
      

    2. KISS

    如此浪漫的一个单词,在这里的意思却是“简短”。

    Keep It Short and Simple.

    我觉得“简短”是一个抽象的词,它的意思并不是代表越短越好,比如说你的代码不空行,或者一行写很多东西。而是上升到一种哲学的思想高度,简单就好。为了满足 KISS 原则,我们在日常开发中需要注意如下几点:

    • 不要用复杂的技术解决简单的问题。
    • 不要重复造轮子。
    • 不要过度优化。

    3. YAGNI

    You Ain’t Gonna Need It.

    活在当下,不要为不大可能发生之事而杞人忧天。当用在软件开发中的时候,它的意思是:不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。

    4. DRY

    Don’t Repeat Yourself.

    我觉得这句话对程序员来说,奉为金科玉律也不为过。写代码不要老是复制粘贴,不要重复执行相同的逻辑代码,不要十年如一日的 CRUD。

    class EmailController
    {
        public function sendEmail()
        {
            $params = $_POST;
            if (validateParams($params)) {
                $emailService = new EmailService();
                $emailService->send($params['email'], $params['content']);
            }
        }
    }
    
    class EmailService
    {
        public function send($email, $content)
        {
            if (validateEmail($email) && $content) {
                // do send
            }
        }
    }
    
    function validateParams($params)
    {
        if (!empty($params['email'])) {
            validateEmail($params['email']);
        }
    }
    
    function validateEmail($email)
    {
        // do validate email
    }
    
    # 我们通常会在 controller 层验证参数,但也有人喜欢在 service 层再做一次验证,这是没有必要的。如果仅仅是在内存中操作还好,但万一涉及到磁盘 IO,那就得不偿失了。
    

    5. LOD

    迪米特法则的英文翻译是:Law of Demeter,缩写是 LOD。它的英文描述是:

    Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.

    翻译成中文就是:每个模块(unit)只应该了解那些与它关系密切的模块(units: only units "closely" related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。也称最少知识原则。

    简而言之就是:不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

    class Sms
    {
        public function sendSmsByYunPian(YunPian $yunPian)
        {
            $yunPianService = new YunPianService();
            $yunPianService->send($yunPian);
        }
    
        public function sendSmsByAli(Ali $ali)
        {
            $aliService = new AliService();
            $aliService->send($ali);
        }
    }
    
    class YunPianService
    {
        public function send(YunPian $yunPian)
        {
        }
    }
    
    class YunPian
    {
        public $phoneNumber;
        public $msg;
    }
    
    class AliService
    {
        public function send(Ali $ali)
        {
        }
    }
    
    class Ali
    {
        public $phoneNumber;
        public $msg;
    }
    
    # 这里我们以发送短信的业务为例,要同时支持云片网和阿里云发送短信。上述代码 Sms 类依赖于 YunPian、YunPianService、Ali、AliService 类,违背了迪米特法则。
    # 我们作出如下改造:
    
    interface SendSms
    {
        public function send($phoneNumber, $msg);
    }
    
    class YunPianSms implements SendSms
    {
        public function send($phoneNumber, $msg)
        {
        }
    }
    
    class AliSms implements SendSms
    {
        public function send($phoneNumber, $msg)
        {
        }
    }
    
    class Sms
    {
        public function send(SendSms $sendSms, $phoneNumber, $msg)
        {
            $sendSms->send($phoneNumber, $msg);
        }
    }
    
    # 这里我取消了先前的 YunPian 类和 Ali 类,因为其实我们不需要它们,我们需要的只是手机号($phoneNumber)和短信内容($msg)。
    # 除此之外,我们把具体的类(YunPianService、AliService)抽象出来(SendSms),如此,Sms 类只依赖于抽象的 SendSms 类,符合迪米特法则。
    

    二、常用的设计模式

    1. 创建型模式

    单例模式

    一个类只允许创建一个对象(或者实例)。

    代码示例:

    interface Single
    {
        public static function getInstance();
    }
    
    class JWT implements Single
    {
        private $key;
        private static $instance;
    
        public static function getInstance()
        {
            if (is_null(self::$instance)) {
                self::$instance = new self;
            }
            return self::$instance;
        }
    
        public function __construct()
        {
            $this->key = getenv('JWT_KEY');
        }
    
        public function encode(int $uid)
        {
        }
    
        public function decode(string $token)
        {
        }
    }
    
    # 这里我们以 JWT 为例,单例模式保证了整个项目中只有唯一的一个实例。
    

    工厂模式

    • 简单工厂

      有这样一个业务场景,根据订单选择最合适的优惠券(显示最大优惠金额)。我们可以使用简单工厂模式:

      class CouponFactory
      {
          public static function createCoupon($type)
          {
              $coupon = null;
              if ($type == 1) { // 满减券
                  $coupon = new MJCoupon();
              } elseif ($type == 2) { // 兑换券
                  $coupon = new DHCoupon();
              } elseif ($type == 3) { // 折扣券
                  $coupon = new ZKCoupon();
              }
              return $coupon;
          }
      }
      
      class Coupon
      {
          public function getMaxReduceAmount(Order $order)
          {
              $maxReduce = 0;
              $couponTypes = [1, 2, 3];
              foreach ($couponTypes as $type) {
                  $coupon = CouponFactory::createCoupon($type);
                  $reduceAmount = $coupon->getMaxReduceAmountByOrder($order);
                  $maxReduce = $reduceAmount > $maxReduce ? $reduceAmount : $maxReduce;
              }
              return $maxReduce;
          }
      }
      
      class MJCoupon
      {
          public function getMaxReduceAmountByOrder(Order $order)
          {
          }
      }
      
      // ...
      
    • 工厂方法

      上面的简单工厂模式有一个小小的问题,就是 if else 语句太多,如果要新增一种优惠券支持的话就需要修改代码,不符合开闭原则,为此我们可以作出如下改造:

      class CouponFactory
      {
          public static function createCoupon(BaseCouponFactory $couponFactory)
          {
              return $couponFactory->createCoupon();
          }
      }
      
      class Coupon
      {
          public function getMaxReduceAmount(Order $order)
          {
              $maxReduce = 0;
              $couponTypes = [1, 2, 3];
              foreach ($couponTypes as $type) {
                  if ($type == 1) {
                      $baseCoupon = new MJCoupon();
                  } elseif ($type == 2) {
                      $baseCoupon = new DHCoupon();
                  } elseif ($type == 3) {
                      $baseCoupon = new ZKCoupon();
                  }
                  $coupon = CouponFactory::createCoupon($baseCoupon);
                  $reduceAmount = $coupon->getMaxReduceAmountByOrder($order);
                  $maxReduce = $reduceAmount > $maxReduce ? $reduceAmount : $maxReduce;
              }
              return $maxReduce;
          }
      }
      
      interface BaseCouponFactory
      {
          public function createCoupon();
      }
      
      class MJCouponFactory implements BaseCouponFactory
      {
          public function createCoupon()
          {
              return new MJCoupon();
          }
      }
      
      class MJCoupon
      {
          public function getMaxReduceAmountByOrder(Order $order)
          {
          }
      }
      
      // ...
      
      # 如此,当我们还需要新增优惠券的时候,只需要创建一个新的实现 BaseCouponFactory 接口的类就可以了,而不需要修改 CouponFactory 类,满足开闭原则。与简单工厂相比,工厂方法需要新增 BaseCouponFactory 类和 MJCouponFactory 类,无疑是变复杂了,而且 if else 没有消失,只是移到了 Coupon 类里。如果不是 CouponFactory 类需要创建很多复杂的类,还是用简单工厂更为方便。
      
    • 抽象工厂

      在简单工厂和工厂方法中,类只有一种分类方式,示例中就是 createCoupon() 方法。假如需求变化,现在还要支持第三方联名卡优惠方案,比如“京东E卡”、“山姆会员卡”之类的东西,那我们是不是还需要再创建一个 CardFactory 类?其实是没必要的,过多的工厂类会使代码变得复杂。第三方联名卡优惠也是优惠的一种,所以我们可以抽象成一个抽象工厂。抽象工厂就是可以让一个工厂负责创建多个不同类型的对象。

      interface Discount
      {
          public static function createCoupon(BaseCouponFactory $couponFactory);
          public static function createCard(BaseCardFactory $cardFactory);
      }
      
      class DiscountsFactory implements Discount
      {
          public static function createCoupon(BaseCouponFactory $couponFactory)
          {
              return $couponFactory->createCoupon();
          }
      
          public static function createCard(BaseCardFactory $cardFactory)
          {
              return $cardFactory->createCard();
          }
      }
      

    建造者模式

    如果我们要创建一个用户,最简单的实现方式是这样的:

    class User
    {
        public $username;
        public $password;
        public $createTime;
    
        public function save()
        {
        }
    }
    
    $user = new User();
    $user->username = '74percent';
    $user->password = md5(111111);
    $user->createTime = time();
    $user->save();
    

    以上代码在 PHP 中是非常常见的,不过有一个很明显的问题,就是所有的属性都是公有的,谁都可以访问,谁都可以修改,看起来好像不是那么安全。那我们可以用建造者模式改造一下。

    interface IBuilder
    {
        // start build
        public static function builder();
        // end build
        public static function build();
    }
    
    class BBuilder implements IBuilder
    {
        public static $model;
        public static $arr = [];
    
        public function __call($name, $arguments)
        {
            self::$arr[$name] = $arguments[0];
            return $this;
        }
    
        public static function builder()
        {
            $childClassName = get_called_class();
            self::$model = new $childClassName;
            return new self;
        }
    
        public static function build()
        {
            foreach (self::$arr as $k => $v) {
                $method = 'set' . ucfirst($k);
                call_user_func([self::$model, $method], $v);
            }
            self::$arr = [];
            return self::$model;
        }
    }
    
    class User extends BBuilder
    {
        private $username;
        private $password;
        private $createTime;
    
        public function setUsername($username)
        {
            $this->username = $username;
        }
    
        public function setPassword($password)
        {
            $this->password = $password;
        }
    
        public function setCreateTime($createTime)
        {
            $this->createTime = $createTime;
        }
    
        public function save()
        {
        }
    }
    
    $user = User::builder()
        ->username('74percent')
        ->password(md5(111111))
        ->createTime(time())
        ->build();
    
    $user->save();
    

    2. 结构型模式

    代理模式

    在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。

    class ReceiveController
    {
        public function handle()
        {
        }
    }
    
    class ReceiveControllerProxy extends ReceiveController
    {
        public function before()
        {
        }
    
        public function after()
        {
        }
    
        public function handle()
        {
            $this->before();
            parent::handle();
            $this->after();
        }
    }
    
    // before
    $receive = new ReceiveController();
    $receive->handle();
    
    // now
    $receive = new ReceiveControllerProxy();
    $receive->handle();
    
    # 我觉得代理模式非常容易理解,不再过多阐述。Spring AOP 底层的实现原理就是基于动态代理。
    

    桥接模式

    桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”

    一句话就是,桥接就是面向接口编程的集大成者。面向接口编程只是说在系统的某一个功能上将接口和实现解藕,而桥接是详细的分析系统功能,将各个独立的纬度都抽象出来,使用时按需组合。

    以一个报警的通知为例,报警有三个等级,SEVERE(严重)、NORMAL(普通)、TRIVIAL(无关紧要)。通知有两种方式,邮件、短信。

    interface MsgSender
    {
        public function send($msg);
    }
    
    class EmailMsgSender implements MsgSender
    {
        private $people = [
            '110@163.com',
            '120@163.com'
        ];
    
        public function send($msg)
        {
            foreach ($this->people as $p) {
                // send
            }
        }
    }
    
    class TelMsgSender implements MsgSender
    {
        private $people = [
            '110',
            '120'
        ];
    
        public function send($msg)
        {
            foreach ($this->people as $p) {
                // send
            }
        }
    }
    
    class Notify
    {
        const LEVEL_LOW = 'TRIVIAL';
        const LEVEL_MIDDLE = 'NORMAL';
        const LEVEL_HIGH = 'SEVERE';
    
        public function sendMsg($level, $msg)
        {
            if ($level === 'SEVERE') {
                $sender = new TelMsgSender();
            } elseif ($level === 'NORMAL') {
                $sender = new EmailMsgSender();
            } else {
                $sender = new EmailMsgSender();
            }
            $this->send($sender, $msg);
        }
    
        private function send(MsgSender $sender, $msg)
        {
            return $sender->send($msg);
        }
    }
    

    乍看起来,桥接模式和策略模式有点像,但其实是有所区别的。桥接模式是结构型模式,策略模式是行为型模式,可以说桥接模式中包含策略模式。桥接模式是多个纬度独立变化的组合,比如报警等级和通知方式,两两组合一共有6种实现。而策略模式没有组合,侧重于单个变化维度的解耦。

    装饰者模式

    装饰者模式和代理模式的代码实现基本上是一样的,不过在定义上还是有所区别的。装饰器模式关注于在一个对象上动态的添加方法,这个对象对用户来说是可以感知的。而代理模式对用户隐藏了源对象的具体信息,用户只需要感知到代理类就行。代理和真实对象之间的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。

    代码示例:

    interface Receive
    {
        public function handle();
    }
    
    class ReceiveController implements Receive
    {
        public function handle()
        {
        }
    }
    
    class ReceiveControllerDecorator implements Receive
    {
        private $receive;
    
        public function __construct(Receive $receive)
        {
            $this->receive = $receive;
        }
    
        public function before()
        {
        }
    
        public function after()
        {
        }
    
        public function handle()
        {
            $this->before();
            $this->receive->handle();
            $this->after();
        }
    }
    

    适配器模式

    适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。

    # 类适配器:基于继承
    interface ITarget
    {
        public function f1();
        public function f2();
        public function fc();
    }
    
    class Adaptee
    {
        public function fa()
        {
        }
    
        public function fb()
        {
        }
    
        public function fc()
        {
        }
    }
    
    class Adaptor extends Adaptee implements ITarget
    {
        public function f1()
        {
            parent::fa();
        }
    
        public function f2()
        {
        }
    }
    
    # 对象适配器:基于组合
    interface ITarget
    {
        public function f1();
        public function f2();
        public function fc();
    }
    
    class Adaptee
    {
        public function fa()
        {
        }
    
        public function fb()
        {
        }
    
        public function fc()
        {
        }
    }
    
    class Adaptor implements ITarget
    {
        private $adaptee;
    
        public function __construct(Adaptee $adaptee)
        {
            $this->adaptee = $adaptee;
        }
    
        public function f1()
        {
            $this->adaptee->fa();
        }
    
        public function f2()
        {
        }
    
        public function fc()
        {
        }
    }
    

    针对这两种实现方式,在实际的开发中,到底该如何选择使用哪一种呢?判断的标准主要有两个,一个是 Adaptee 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。

    • 如果 Adaptee 接口并不多,那两种实现方式都可以。
    • 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
    • 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。

    3. 行为型模式

    观察者模式

    观察者模式其实就是发布订阅模式,是一个非常常用的设计模式。它是这样定义的:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

    我们以用户下单为例,下单这个动作涉及到很多操作,比如减库存、给主播算销售额(未支付的订单还不能算有效订单,已支付的订单其实也不能算有效订单,因为可能会涉及到退款,只有已支付无退款并且关闭了的订单才能算有效订单)、写日志等。下单这个行为就很适合用观察者模式。

    class BaseSubject implements SplSubject
    {
        private $observers = [];
    
        public function attach(SplObserver $observer)
        {
            return array_push($this->observers, $observer);
        }
    
        public function detach(SplObserver $observer)
        {
            $key = array_search($observer, $this->observers, true);
            if ($key !== false) {
                unset($this->observers[$key]);
                return true;
            }
            return false;
        }
    
        public function notify()
        {
            if (!empty($this->observers)) {
                foreach ($this->observers as $observer) {
                    $observer->update($this);
                }
            }
            return true;
        }
    }
    
    class OrderSubject extends BaseSubject
    {
        private $order;
    
        public function __construct(Order $order)
        {
            $this->order = $order;
        }
    
        public function placeOrder()
        {
            // ...
            $this->notify();
        }
    }
    
    class GoodsObserver implements SplObserver
    {
        public function update(SplSubject $subject)
        {
        }
    }
    
    class AnchorObserver implements SplObserver
    {
        public function update(SplSubject $subject)
        {
        }
    }
    
    class LogObserver implements SplObserver
    {
        public function update(SplSubject $subject)
        {
        }
    }
    
    class OrderController
    {
        public function placeOrder()
        {
            $order = new Order();
            $orderSubject = new OrderSubject($order);
            $orderSubject->attach(new GoodsObserver());
            $orderSubject->attach(new AnchorObserver());
            $orderSubject->attach(new LogObserver());
            $orderSubject->placeOrder();
        }
    }
    

    模板模式

    模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

    代码示例:

    abstract class  RequestTemplate
    {
        abstract protected function doGet($params);
        abstract protected function doPost($params);
    
        final public function getResponse($params)
        {
            if ($_SERVER['REQUEST_METHOD'] === 'GET') {
                $res = $this->doGet($params);
            } elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
                $res = $this->doPost($params);
            } else {
                throw new Exception('Invalid request method.');
            }
            return $res;
        }
    }
    
    class Request extends RequestTemplate
    {
        protected function doGet($params)
        {
            throw new Exception('Get request is invalid.');
        }
    
        protected function doPost($params)
        {
            return 'Post request is valid.';
        }
    }
    
    $request = new Request();
    $res = $request->getResponse($_REQUEST);
    
    # 这里我定义了一个 Request 类继承 RequestTemplate 模板类,重写 doGet() 和 doPost() 方法,来处理不同的请求逻辑。Java 中 Servlet 就是这么实现的。
    

    策略模式

    策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

    策略模式非常常见,使用也相当广泛,其实我在上面讲工厂模式、桥接模式的时候,就已经用到策略模式了。在业务中,使用优惠券、支付、发短信都是可以使用策略模式的。

    abstract class PayStrategy
    {
        abstract public function pay();
    }
    
    abstract class ExternalPay extends PayStrategy
    {
        abstract public function callback();
    }
    
    abstract class InteriorPay extends PayStrategy
    {
    }
    
    class WechatPay extends ExternalPay
    {
        public function pay()
        {
            echo 'wechat pay' . "
    ";
        }
    
        public function callback()
        {
        }
    }
    
    class WalletPay extends InteriorPay
    {
        public function pay()
        {
            echo 'wallet pay' . "
    ";
        }
    }
    
    class PayController
    {
        public function pay($payType)
        {
            if ($payType === 1) { // 微信支付
                $payStrategy = new WechatPay();
            } elseif ($payType === 2) { // 钱包支付
                $payStrategy = new WalletPay();
            }
            // ...
            $this->_pay($payStrategy);
        }
    
        private function _pay(PayStrategy $payStrategy)
        {
            return $payStrategy->pay();
        }
    }
    

    职责链模式

    职责链模式的英文翻译是 Chain Of Responsibility Design Pattern。它是这么定义的:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

    这里举一个游戏直播发弹幕敏感词汇过滤的例子,针对游戏公司,有自己的敏感词汇库,不过不全,还需要引用第三方的敏感词汇过滤接口。

    interface SensitiveWordFilter
    {
        public function filter($content);
    }
    
    class SelfWordFilter implements SensitiveWordFilter
    {
        public function filter($content)
        {
            $legal = true;
            // ...
            return $legal;
        }
    }
    
    class BaiduWordFilter implements SensitiveWordFilter
    {
        public function filter($content)
        {
            $legal = true;
            // ...
            return $legal;
        }
    }
    
    class SensitiveWordFilterChain
    {
        private $filters = [];
    
        public function addFilter(SensitiveWordFilter $filter)
        {
            array_push($this->filters, $filter);
        }
    
        public function filter($content)
        {
            foreach ($this->filters as $filter) {
                if (!$filter->filter($content)) {
                    return false;
                }
            }
            return true;
        }
    }
    
    $chain = new SensitiveWordFilterChain();
    $chain->addFilter(new SelfWordFilter());
    $chain->addFilter(new BaiduWordFilter());
    var_dump($chain->filter('包子'));
    

    上述职责链模式是用数组实现的,下面我再用链表实现一个。

    abstract class SensitiveWordFilter
    {
        protected $next = null;
    
        public function setNext(?SensitiveWordFilter $filter)
        {
            $this->next = $filter;
        }
    
        public abstract function filter($content);
    }
    
    class SelfWordFilter extends SensitiveWordFilter
    {
        public function filter($content)
        {
            $legal = true;
            // ...
            if ($legal && $this->next) {
                $legal = $this->next->filter($content);
            }
            return $legal;
        }
    }
    
    class BaiduWordFilter extends SensitiveWordFilter
    {
        public function filter($content)
        {
            $legal = true;
            // ...
            if ($legal && $this->next) {
                $legal = $this->next->filter($content);
            }
            return $legal;
        }
    }
    
    class SensitiveWordFilterChain
    {
        private $head = null;
        private $tail = null;
    
        public function addFilter(SensitiveWordFilter $filter)
        {
            $filter->setNext(null);
            if ($this->head == null) {
                $this->head = $filter;
                $this->tail = $filter;
                return;
            }
            $this->tail->setNext($filter);
            $this->tail = $filter;
        }
    
        public function filter($content)
        {
            if ($this->head != null) {
                return $this->head->filter($content);
            }
        }
    }
    
    $chain = new SensitiveWordFilterChain();
    $chain->addFilter(new SelfWordFilter());
    $chain->addFilter(new BaiduWordFilter());
    var_dump($chain->filter('包子'));
    

    职责链模式还有一个变体,就是必须让链上所有的对象都处理一遍请求,不存在中途终止的情况。这个也很好理解,这里不再阐述。

    迭代器模式

    迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。迭代器模式用来遍历集合对象。这里说的“集合对象”也可以叫“容器”、“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。

    代码示例:

    # PHP 中有现成的迭代器
    $names = ['Dick', 'Erick', 'Alicia', 'Tom', 'Jack'];
    $arrIterator = new ArrayIterator($names);
    while ($arrIterator->valid()) {
        echo $arrIterator->current() . "
    ";
        $arrIterator->next();
    }
    
    # 实现原理
    interface Iterato
    {
        public function valid();
        public function current();
        public function next();
    }
    
    class ArrIterator implements Iterato
    {
        private $index = 0;
        private $arr = [];
    
        public function __construct(array $arr)
        {
            $this->arr = $arr;
        }
    
        public function valid()
        {
            return $this->index < count($this->arr);
        }
    
        public function current()
        {
            return $this->arr[$this->index];
        }
    
        public function next()
        {
            $this->index++;
        }
    }
    
    $names = ['Dick', 'Erick', 'Alicia', 'Tom', 'Jack'];
    $arrIterator = new ArrIterator($names);
    while ($arrIterator->valid()) {
        echo $arrIterator->current() . "
    ";
        $arrIterator->next();
    }
    

    状态模式

    状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。下面我以电商修改订单状态为例:

    /**
     * 订单状态
     */
    class OrderState
    {
        const OBLIGATION = 0;   // 待付款
        const CANCELED = 1;     // 已取消
        const WAITDELIVER = 2;  // 待发货
        const WAITRECEIPT = 3;  // 待收货
        const RECEIPED = 4;     // 已完成
    }
    
    /**
     * 售后状态
     */
    class AfterSaleState
    {
        const DEFAULT = 0;      // 未提交售后
        const SUBMITTED = 1;    // 已提交售后
    }
    
    /**
     * 退款状态
     */
    class RefundState
    {
        const DEFALUT = 0;      // 未退款
        const REFUNDED = 1;     // 已退款
    }
    
    interface IOrder
    {
        public function getOrderState();
        public function cancelOrder(OrderStateMachine $orderStateMachine);  // 取消订单
        public function paidOrder(OrderStateMachine $orderStateMachine);    // 支付订单
        public function receiptOrder(OrderStateMachine $orderStateMachine); // 确认收货
        public function afterOrder(OrderStateMachine $orderStateMachine);   // 提交售后
    }
    
    /**
     * 待付款订单
     */
    class ObligationOrder implements IOrder
    {
        public function getOrderState()
        {
            return OrderState::OBLIGATION;
        }
    
        public function cancelOrder(OrderStateMachine $orderStateMachine)
        {
            $orderStateMachine->setCurrentState(new CanceledOrder());
        }
    
        public function paidOrder(OrderStateMachine $orderStateMachine)
        {
            $orderStateMachine->setCurrentState(new WaitDeliverOrder());
        }
    
        public function receiptOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    
        public function afterOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    }
    
    /**
     * 已取消订单
     */
    class CanceledOrder implements IOrder
    {
        public function getOrderState()
        {
            return OrderState::CANCELED;
        }
    
        public function cancelOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    
        public function paidOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    
        public function receiptOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    
        public function afterOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    }
    
    /**
     * 待发货订单
     */
    class WaitDeliverOrder implements IOrder
    {
        public function getOrderState()
        {
            return OrderState::WAITDELIVER;
        }
    
        public function cancelOrder(OrderStateMachine $orderStateMachine)
        {
            $orderStateMachine->setCurrentState(new CanceledOrder());
            $orderStateMachine->setRefundState(RefundState::REFUNDED);
        }
    
        public function paidOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    
        public function receiptOrder(OrderStateMachine $orderStateMachine)
        {
            // can not do this
        }
    
        public function afterOrder(OrderStateMachine $orderStateMachine)
        {
            $orderStateMachine->setAfterState(AfterSaleState::SUBMITTED);
        }
    }
    
    // 省略 WaitReceiptOrder、ReceipedOrder 类
    
    class OrderStateMachine
    {
        private IOrder $currentOrderState;
        private $currentAfterSaleState;
        private $currentRefundState;
    
        public function __construct()
        {
            $this->currentOrderState = new ObligationOrder();
            $this->currentAfterSaleState = AfterSaleState::DEFAULT;
            $this->currentRefundState = RefundState::DEFALUT;
        }
    
        public function setCurrentState(IOrder $currentState)
        {
            $this->currentOrderState = $currentState;
        }
    
        public function getCurrentState()
        {
            $this->currentOrderState->getOrderState();
        }
    
        public function setAfterState($state)
        {
            $this->currentAfterSaleState = $state;
        }
    
        public function setRefundState($state)
        {
            $this->currentRefundState = $state;
        }
    
        public function cancel()
        {
            $this->currentOrderState->cancelOrder($this);
        }
    
        public function paid()
        {
            $this->currentOrderState->paidOrder($this);
        }
    
        public function receipt()
        {
            $this->currentOrderState->receiptOrder($this);
        }
    
        public function after()
        {
            $this->currentOrderState->afterOrder($this);
        }
    }
    

    参考资料:《设计模式之美》

  • 相关阅读:
    转载--详解tomcat配置
    MongoDB@入门一
    面试@单例模式
    单点登录系统(一)
    SublimeText3 初探(工欲善其事,必先利其器)
    UEFI+GPT 修复 win10启动
    悟空模式-java-建造者模式
    悟空模式-java-原型模式
    悟空模式-java-单例模式
    悟空模式-java-抽象工厂模式
  • 原文地址:https://www.cnblogs.com/74percent/p/14785350.html
Copyright © 2020-2023  润新知