[精选] 大佬们都喜欢看设计模式,你知道PHP设计模式有哪些呢?

设计模式,是每个程序员必须知道,必须学习的知识,虽然不算是基础,但是你必须要懂得。

我这里所了解的大概有 23 种设计模式,PHP 常见的大概有 10 几种吧。

总体来说设计模式分为三大类:

创建型模式共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

总原则:开闭原则(Open Close Principle)#

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。

所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

1、单一职责原则#

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

2、里氏替换原则(Liskov Substitution Principle)#

里氏代换原则 (Liskov Substitution Principle LSP) 面向对象设计的基本原则之一。

里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

里氏代换原则是对 “开 - 闭” 原则的补充。实现 “开 - 闭” 原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科

里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

3、依赖倒转原则(Dependence Inversion Principle)#

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

4、接口隔离原则(Interface Segregation Principle)#

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

5、迪米特法则(最少知道原则)(Demeter Principle)#

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过 public 方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

6、合成复用原则(Composite Reuse Principle)#

原则是尽量首先使用合成 / 聚合的方式,而不是使用继承。

单例模式

单例模式,顾名思义就是只有一个实例。单例模式确保某一个类只有一个实例,不能重复实例,只能它自己实例化,而且向整个系统提供这个实例。

三私一公:私有化静态属性,私有化构造方法,私有化克隆方法,公有化静态方法。

单例模式:即一个类只被实例化一次,当其他人对其再次实例化时,返回第一次实例化的对象,可以避免大量的 new 操作,减少资源的消耗,典型应用于数据库类的实例化。

以实例化一个 Mysql 数据库类为例:

要实现一个类只实例化一次,就必须堵住其他实例化的入口。

1. 实例化类时,会自动调用类的构造方法,因为将构造方法设置为 private 属性,限制为只能在类内部实例化

// 私有构造方法,防止在类的外部实例化

private function __construct() {
    # code...
}

2. 定义一个静态方法,在类内部实例化对象。实例化前先判断该类是否已被实例化了,若已被实例化,就返回该实例化对象;若没被实例化,便实例化一个对象并将该对象保存在类的静态属性中

// 私有静态属性,存放该类的实例
private static $instance = null;

// 公共的静态方法,实例化该类本身,只实例化一次

public static function getInstance() {

    if (!self::$instance instanceof self) {

       self::$instance = new self;
    }

    return self::$instance;

}

3. 禁止克隆,对象之间进行克隆可生成不同的对象。而克隆时会自动调用类的__conle 方法,因此将克隆方法设置为 private 属性

// 私有克隆方法,防止克隆

private function __clone(){
    # code...
}

综上,三私一公(三个私有属性和一个公共方法)单例模式的代码如下:

class DB
{
    private static $instance = null; //私有静态属性,存放该类的实例

    private function __construct() //私有构造方法,防止在类的外部实例化
    {
        # code...
    }

    private function __clone() //私有克隆方法,防止克隆
    {
        # code
    }

    public static function getInstance() //公共的静态方法,实例化该类本身,只实例化一次
    {
        if (!self::$instance instanceof self) {
            self::$instance = new self;
        }
        return self::$instance;
    }
}

工厂模式

工厂模式 (Factory Design Pattern) 作为一种创建型设计模式,遵循了开放 - 封闭原则,对修改封闭,对扩展开放。工厂方法 (Factory Method) 模式就是要创建” 某种东西”.

对于工厂方法模式,要创建的” 东西” 是一个产品,这个产品与创建它的类之间不存在绑定。实际上,为了保持这种松耦合,客户会通过一个工厂发出请求。再由工厂创建所请求的产品。也可以换种方式考虑,利用工厂方法模式,请求者只发出请求,而不具体创建产品.

工厂的工作

先建立一个工厂的接口

Factory.php

<?php
abstract class Factory
{
 //抽象的创建对象的方法
 protected abstract function createProduct();
 //该方法调用createProduct方法返回一个产品对象.
 public function start()
 {
   return $this->createProduct();
 }
}

start 方法返回一个产品,该方法调用 createProduct 方法完成产生产品的操作。所以 createProduct 的具体实现要构建并返回一个按 Product 接口实现的产品对象.

比如产品都有一个共同的方法 getProperties(), 以下是对应 Product 接口

Product.php

<?php
//产品接口
interface Product
{
 public function getProperties();
}

接着,我们要建立两个工厂,文本工厂 TextFactory 和图像工厂 phptoFactory

TextFactory.php

<?php
include_once('Factory.php');
include_once('TextProduct.php');
class TextFactory extends Factory
{
 protected function createProduct()
 {
  $product = new TextProduct();
  return $product->getProperties();
 }
}

PhotoFactory.php

<?php
include_once('Factory.php');
include_once('PhotoProduct.php');
class PhotoFactory extends Factory
{
 protected function createProduct()
 {
  $product = new PhotoProduct();
  return $product->getProperties();
 }
}

可以看到,在工厂方法的实现中,getProperties 方法引入了多态 (polymorphism), 将用这个方法返回” 文本” 或” 图像”. 同一个 getProperties() 有多个 (poly) 不同的形态 (morphs), 这就是多态。在这种情况下,其中一种形式返回文本,而另一种返回图像.

可以在 properties 这个实现中放入你想要的任何东西,工厂方法设计将会创建这个对象,并把他返回给 Client 使用.

下面的是两个产品的实现

TextProduct.php

<?php
include_once('Product.php');
class TextProduct implements Product
{
 public function getProperties()
 {
  return "这里是文本产品";
 }
}

PhotoProduct.php

<?php
include_once('Product.php');
class PhotoProduct implements Product
{
 //这是产品具有的方法
 public function getProperties()
 {
  return "这里是图像产品";
 }
}

这两个产品实现了 Product 接口中的抽象方法 getProperties(),

客户 (Client)

我们并不希望客户直接做出产品请求。实际上,我们希望客户通过 Factory 工厂接口做出请求。这样一来,如果以后我们增加了产品或者工厂,客户可以做同样的请求来得到更多类型的产品,而不会破坏这个应用:

Client.php

<?php
include_once('PhotoFactory.php');
include_once('TextFactory.php');
class Client
{
 public function __construct()
 {
  $this->somePhotoObject = new PhotoFactory();
  echo $this->somePhotoObject->start() . '<br />';
  $this->someTextObject = new TextFactory();
  echo $this->someTextObject->start() . '<br />';
 }
}
$worker = new Client();

运行 Client.php, 得到下面的结果

这里是图像产品
这里是文本产品

* 注意: *Client 对象并没有向产品直接做出请求,而是通过工厂来请求。重要的是,客户并不实现产品特性,而留给产品实现来体现.

调整产品

设计模式的真正价值并不是提高操作的速度,而是加快开发的速度.

如果现在需求变化了,需要对图像产品做出修改,只需要修改相应的产品 PhotoProduct 的 getProperties 方法即可

对象的改变看起来很简单 不过 Product 的 getProperties() 方法仍保持相同的接口,请求工厂返回一个属性对象

增加新产品和参数化请求

问题来了,如果要增加更多的图像和文本说明,有没有必要每次增加一个新的区域就增加一个新的具体的工厂类?这意味着要为每个新区域增加一个新工厂和产品。于是,我们引进了参数化工厂设计模式

参数化工厂设计模式和一般的工厂设计模式的主要区别之一是客户包含工厂和产品的引用。在参数化请求中,Client 类必须指定产品,而不是产品工厂. createProduct() 操作中的参数是由客户传入一个产品;所以客户必须指出它想要的具体产品。不过,这个请求仍然是通过工厂接口 Factory 发出的。所以,尽管客户包含一个产品引用,但通过 Factory, 客户仍然与产品分离.

一个工厂多个产品 (参数化工厂方法)

对于大多数请求,参数化工厂方法更为简单,因为客户只需要处理一个具体工厂。工厂方法操作有一个参数,指示需要创建的产品。而在原来的设计中,每个产品都有自己的工厂,不需要另个传递参数;产品实现依赖于各个产品特定的工厂.

新工厂接口

Factory.php

<?php
abstract class Factory
{
 //抽象的创建对象的方法
 protected abstract function createProduct(Product $product);
 //该方法由factoryMethod方法返回一个产品对象.
 public function start($product)
 {
   return $this->createProduct($product);
 }
}

在这个新的 Factory 接口中可以看到,create()start() 都需要一个参数,指定一个 Product 对象,而不是 Product 接口的一个特定实现,所以可以接受任何 Product 的具体实例.

工厂具体实现

具体的创建者类 CommonFactory 实现了 createProduct(), 如下

CommonFactory.php

<?php
include_once('Factory.php');
include_once('Product.php');
class CommonFactory extends Factory
{
 protected function createProduct(Product $product)
 {
  return $product->getProperties();
 }
}

这个类调用 Product 的方法 getProperties 将产品返回给客户.

新产品

具体产品的变化并不会改变原来的 Product 接口,还是原来的代码

<?php
//产品接口
interface Product
{
 public function getProperties();
}

例如,现在有一个钢笔产品 PenProduct

PenProduct.php

<?php
include_once('Product.php');
class PenProduct implements Product
{
 public function getProperties()
 {
  return "钢笔产品";
 }
}

客户 Clent (有参数)

<?php
include_once('CommonFactory.php');
include_once('PenProduct.php');
class Client
{
 public function __construct()
 {
  $commonFactory = new CommonFactory();
  echo $commonFactory->start(new PenProduct());
 }
}
$worker = new Client();

运行后输出

钢笔产品

以后如果开发出了新的产品,只需要创建对应的产品类,然后客户指定想要的新产品,即可返回客户需要的产品.

总结:

产品改变:接口不变

使用设计模式的一大好处就是可以很容易地对类做出改变,而不会破坏更大的程序。之所以能够容易地做出改变,秘诀在于保持接口不变,而只改变内容.

适配器模式

什么是适配器模式

将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原来由于接口不兼容而不能一起工作的那此类可以一起工作

UML 图

适配器模式中主要角色

目标 (Target) 角色:定义客户端使用的与特定领域相关的接口,这也就是我们所期待得到的。
源 (Adaptee) 角色:需要进行适配的接口。
适配器 (Adapter) 角色:对 Adaptee 的接口与 Target 接口进行适配;适配器是本模式的核心,适配器把源接口转换成目标接口,此角色为具体类。

适配器模式适用场景

1、你想使用一个已经存在的类,而它的接口不符合你的需求。
2、你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。
3、你想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口(仅限于对象适配器)。

类适配器模式与对象适配器

类适配器:Adapter 与 Adaptee 是继承关系
1、用一个具体的 Adapter 类和 Target 进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类 Adapter 将不能胜任工作。
2、使得 Adapter 可以重定义 Adaptee 的部分行为,因为 Adapter 是 Adaptee 的一个子集。
3、仅仅引入一个对象,并不需要额外的指针以间接取得 adaptee。

对象适配器:Adapter 与 Adaptee 是委托关系
1、允许一个 Adapter 与多个 Adaptee 同时工作。Adapter 也可以一次给所有的 Adaptee 添加功能。
2、使用重定义 Adaptee 的行为比较困难。

适配器模式与其它模式
桥梁模式 (bridge 模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口。

装饰器模式 (decorator 模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。

类适配器模式 PHP 示例

类适配器使用的是继承

<?php
/**
 * 目标角色
 */
interface Target {

  /**
   * 源类也有的方法1
   */
  public function sampleMethod1();

  /**
   * 源类没有的方法2
   */
  public function sampleMethod2();
}

/**
 * 源角色
 */
class Adaptee {

  /**
   * 源类含有的方法
   */
  public function sampleMethod1() {
    echo 'Adaptee sampleMethod1 <br />';
  }
}

/**
 * 类适配器角色
 */
class Adapter extends Adaptee implements Target {

  /**
   * 源类中没有sampleMethod2方法,在此补充
   */
  public function sampleMethod2() {
    echo 'Adapter sampleMethod2 <br />';
  }

}

class Client {

  /**
   * Main program.
   */
  public static function main() {
    $adapter = new Adapter();
    $adapter->sampleMethod1();
    $adapter->sampleMethod2();

  }

}

Client::main();
?>

对象适配器模式 PHP 示例
对象适配器使用的是委派

<?php
/**
 * 目标角色
 */
interface Target {

  /**
   * 源类也有的方法1
   */
  public function sampleMethod1();

  /**
   * 源类没有的方法2
   */
  public function sampleMethod2();
}

/**
 * 源角色
 */
class Adaptee {

  /**
   * 源类含有的方法
   */
  public function sampleMethod1() {
    echo 'Adaptee sampleMethod1 <br />';
  }
}

/**
 * 类适配器角色
 */
class Adapter implements Target {

  private $_adaptee;

  public function __construct(Adaptee $adaptee) {
    $this->_adaptee = $adaptee;
  }

  /**
   * 委派调用Adaptee的sampleMethod1方法
   */
  public function sampleMethod1() {
    $this->_adaptee->sampleMethod1();
  }

  /**
   * 源类中没有sampleMethod2方法,在此补充
   */
  public function sampleMethod2() {
    echo 'Adapter sampleMethod2 <br />';
  }

}

class Client {

  /**
   * Main program.
   */
  public static function main() {
    $adaptee = new Adaptee();
    $adapter = new Adapter($adaptee);
    $adapter->sampleMethod1();
    $adapter->sampleMethod2();

  }

}

Client::main();
?>
本作品采用《CC 协议》,转载必须注明作者和本文链接
最美的不是下雨天,而是和你一起躲过的屋檐!
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。