PHP 常用设计模式


PHP 常用设计模式


正文

设计模式(Design pattern)是什么:设计模式是一套被反复使用、多数人知晓、经过分类编目的代码设计的经验总结。 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

为什么会有设计模式:在软件开发过程中,一个功能的实现方式多种多样,不同方法的可扩展性、可维护性以及复用性都是不一样的。 随着一个人对自己项目代码的要求增加,他会逐渐思考和实践出自己的一套方法或者思想,这种方法或思想决定了他设计出的架构或者编写出的代码的质量优劣。 设计模式就属于这样一种经验的积累,是由大量优秀的工程师或者架构师总结和提炼的精华,学习好设计模式等于让我们站在了巨人的肩膀上, 从一个高的起点出发,可以避免走很多弯路。

设计模式六大原则

单一职责原则:

对象不应承担太多功能,正如一心不能而用,比如太多的工作(种类)会使人崩溃。唯有专注才能保证对象的高内聚;唯有唯一,才能保证对象的细粒度。

接口隔离原则:

1、客户端不应依赖它不需要的接口;
2、类间的依赖关系应该建立在最小的接口上。 

依赖倒置原则:  

1、高层模块不应该依赖底层模块,二者都应该依赖抽象。
2、抽象不应该依赖细节,细节应该依赖抽象。
3、依赖倒置的中心思想是面向接口编程。
4、依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础搭建的架构要稳定的多。
5、使用接口或抽象类的目的是指定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类来完成。

里氏替换原则:

子类应当可以替换父类并出现在父类能够出现的地方。

迪米特法则(LOD):

也叫最少知识原则。迪米特法则的定义是只与你的直接朋友交谈,不与"陌生人"说话。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该应用。
其目的是降低类之间的耦合度,提高模块的相对独立性。

开闭原则(OCP):

软件对象(类、模块、方法等)应该对于扩展是开放的,对修改是关闭的。

设计模式的分类

一般情况下,我们把设计模式分成了三大类:

创建型模式(Creational patterns)

在软件工程中,创建型设计模式用于处理对象的实例化。

创建型模式是为了解决创建对象时候遇到的问题。因为基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度, 创建型设计模式有两个主导思想:一是将系统使用的具体类封装起来,二是隐藏这些具体类的实例创建和结合方式。

  • 简单工厂模式(Simple Factory)
  • 静态工厂模式(Static Factory)
  • 工厂方法模式(Factory method)
  • 抽象工厂模式(Abstract factory)
  • 建造者模式(Builder)
  • 单例模式(Singleton)
  • 多例模式(Multiton)
  • 原型模式(Prototype)
  • 对象池模式(Pool)

GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。 将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

创建型模式解说地址

结构型模式(Structural pattern)

结构型设计模式用于处理类和对象的组合。

结构型模式是通过定义一个简单方法来实现和了解实体间关系,从而简化设计。

  • 适配器模式(Adapter)
  • 桥梁模式(Bridge)
  • 组合模式(Composite)
  • 数据映射模式(Data Mapper)
  • 装饰模式(Decorator)
  • 依赖注入模式(Dependency Injection)
  • 门面模式(Facade)
  • 流接口模式(Fluent Interface)
  • 代理模式(Proxy)
  • 注册模式(Registry)
  • 享元模式(Flyweight)

结构型模式解说地址

行为型模式(Behavioral pattern)

行为型设计模式用于处理类的对象间通信。

行为型模式是用来识别对象之间的常用交流模式并加以实现,使得交流变得更加灵活。

  • 责任链模式(Chain Of Responsibilities)
  • 命令行模式(Command)
  • 迭代器模式(Iterator)
  • 中介者模式(Mediator)
  • 备忘录模式(Memento)
  • 空对象模式(Null Object)
  • 观察者模式(Observer)
  • 规格模式(Specification)
  • 状态模式(State)
  • 策略模式(Strategy)
  • 模板方法模式(Template Method)
  • 访问者模式(Visitor)
  • 解释器模式(Interpreter)

行为型模式解说地址

其它

  • 委托模式(Delegation)
  • 资源库模式(Repository)
  • 服务定位器模式(Service Locator)
  • 事件模式(Event)
  • 行为模式(Behavior)

各个设计模式之间的关系

委托模式(Delegation)

  • 模式定义

委托是对一个类的功能进行扩展和复用的方法。它的做法是:写一个附加的类提供附加的功能,并使用原来的类的实例提供原有的功能。 假设我们有一个 TeamLead 类,将其既定任务委托给一个关联辅助对象 JuniorDeveloper 来完成: 本来 TeamLead 处理 writeCode 方法,Usage 调用 TeamLead 的该方法, 但现在 TeamLead 将 writeCode 的实现委托给 JuniorDeveloper 的 writeBadCode 来实现, 但 Usage 并没有感知在执行 writeBadCode 方法。

  • UML类图

  • 示例代码

Usage.php

<?php
namespace DesignPatterns\More\Delegation;

// 初始化 TeamLead 并委托辅助者 JuniorDeveloper
$teamLead = new TeamLead(new JuniorDeveloper());

// TeamLead 将编写代码的任务委托给 JuniorDeveloper
echo $teamLead->writeCode();

TeamLead.php

<?php
namespace DesignPatterns\More\Delegation;

/**
 * TeamLead类
 * @package DesignPatterns\Delegation
 * `TeamLead` 类将工作委托给 `JuniorDeveloper`
 */
class TeamLead
{
    /** @var JuniorDeveloper */
    protected $slave;

    /**
     * 在构造函数中注入初级开发者JuniorDeveloper
     * @param JuniorDeveloper $junior
     */
    public function __construct(JuniorDeveloper $junior)
    {
        $this->slave = $junior;
    }

    /**
     * TeamLead 喝咖啡, JuniorDeveloper 工作
     * @return mixed
     */
    public function writeCode()
    {
        return $this->slave->writeBadCode();
    }
}

JuniorDeveloper.php

<?php
namespace DesignPatterns\More\Delegation;

/**
 * JuniorDeveloper 类
 * @package DesignPatterns\Delegation
 */
class JuniorDeveloper
{
    public function writeBadCode()
    {
        return "Some junior developer generated code...";
    }
}
  • 测试代码

Tests/DelegationTest.php

<?php
namespace DesignPatterns\More\Delegation\Tests;

use DesignPatterns\More\Delegation;

/**
 * DelegationTest 用于测试委托模式
 */
class DelegationTest extends \PHPUnit_Framework_TestCase
{
    public function testHowTeamLeadWriteCode()
    {
        $junior = new Delegation\JuniorDeveloper();
        $teamLead = new Delegation\TeamLead($junior);
        $this->assertEquals($junior->writeBadCode(), $teamLead->writeCode());
    }
}
  • 总结

与类似接口的区别:

  1. 委托模式 —— 被委托类和委托类无继承关系,在委托类中依赖注入被委托类,访问者与委托类进行交互,被委托类和委托类方法不一样。
  2. 代理模式 —— 代理类继承自被代理类,对被代理类进行控制和拓展,访问者与代理类进行交互。
  3. 适配器模式 —— 适配器模式为它所适配的对象提供了一个不同的接口,目的是将已存在的东西(接口)转换成适合我们所利用的东西。
  4. 装饰器模式 —— 装饰器通过依赖注入,为注入对象进行后续装饰,装饰器提供了与被装饰类相同的方法。

资源库模式(Repository)

  • 模式定义

Repository 是一个独立的层,介于领域层与数据映射层(数据访问层)之间。 它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。 Repository 是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。 Repository 模式是架构模式,在设计架构时,才有参考价值。 应用 Repository 模式所带来的好处,远高于实现这个模式所增加的代码。只要项目分层,都应当使用这个模式。

  • UML类图

  • 示例代码

Post.php

<?php
namespace DesignPatterns\More\Repository;

/**
 * Post 类
 * @package DesignPatterns\Repository
 */
class Post
{
    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $title;

    /**
     * @var string
     */
    private $text;

    /**
     * @var string
     */
    private $author;

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @param int $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param string $author
     */
    public function setAuthor($author)
    {
        $this->author = $author;
    }

    /**
     * @return string
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * @param \DateTime $created
     */
    public function setCreated($created)
    {
        $this->created = $created;
    }

    /**
     * @return \DateTime
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * @param string $text
     */
    public function setText($text)
    {
        $this->text = $text;
    }

    /**
     * @return string
     */
    public function getText()
    {
        return $this->text;
    }

    /**
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
}

PostRepository.php

<?php
namespace DesignPatterns\More\Repository;

use DesignPatterns\More\Repository\Storage;

/**
 * Post 对应的 Repository
 * 该类介于数据实体层(Post) 和访问对象层(Storage)之间
 *
 * Repository 封装了持久化对象到数据存储器以及在展示层显示面向对象的视图操作
 *
 * Repository 还实现了领域层和数据映射层的分离和单向依赖
 *
 * PostRepository 类
 * @package DesignPatterns\Repository
 */
class PostRepository
{
    private $persistence;

    public function __construct(Storage $persistence)
    {
        $this->persistence = $persistence;
    }

    /**
     * 通过指定id返回Post对象
     *
     * @param int $id
     * @return Post|null
     */
    public function getById($id)
    {
        $arrayData = $this->persistence->retrieve($id);
        if (is_null($arrayData)) {
            return null;
        }

        $post = new Post();
        $post->setId($arrayData['id']);
        $post->setAuthor($arrayData['author']);
        $post->setCreated($arrayData['created']);
        $post->setText($arrayData['text']);
        $post->setTitle($arrayData['title']);

        return $post;
    }

    /**
     * 保存指定对象并返回
     *
     * @param Post $post
     * @return Post
     */
    public function save(Post $post)
    {
        $id = $this->persistence->persist(array(
            'author' => $post->getAuthor(),
            'created' => $post->getCreated(),
            'text' => $post->getText(),
            'title' => $post->getTitle()
        ));

        $post->setId($id);
        return $post;
    }

    /**
     * 删除指定的 Post 对象
     *
     * @param Post $post
     * @return bool
     */
    public function delete(Post $post)
    {
        return $this->persistence->delete($post->getId());
    }
}

Storage.php

<?php
namespace DesignPatterns\More\Repository;

/**
 * Storage接口
 *
 * 该接口定义了访问数据存储器的方法
 * 具体的实现可以是多样化的,比如内存、关系型数据库、NoSQL数据库等等
 *
 * @package DesignPatterns\Repository
 */
interface Storage
{
    /**
     * 持久化数据方法
     * 返回新创建的对象ID
     *
     * @param array() $data
     * @return int
     */
    public function persist($data);

    /**
     * 通过指定id返回数据
     * 如果为空返回null
     *
     * @param int $id
     * @return array|null
     */
    public function retrieve($id);

    /**
     * 通过指定id删除数据
     * 如果数据不存在返回false,否则如果删除成功返回true
     *
     * @param int $id
     * @return bool
     */
    public function delete($id);
}

MemoryStorage.php

<?php
namespace DesignPatterns\More\Repository;

use DesignPatterns\More\Repository\Storage;

/**
 * MemoryStorage类
 * @package DesignPatterns\Repository
 */
class MemoryStorage implements Storage
{

    private $data;
    private $lastId;

    public function __construct()
    {
        $this->data = array();
        $this->lastId = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function persist($data)
    {
        $this->data[++$this->lastId] = $data;
        return $this->lastId;
    }

    /**
     * {@inheritdoc}
     */
    public function retrieve($id)
    {
        return isset($this->data[$id]) ? $this->data[$id] : null;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($id)
    {
        if (!isset($this->data[$id])) {
            return false;
        }

        $this->data[$id] = null;
        unset($this->data[$id]);

        return true;
    }
}
  • 测试代码

  • 总结

资源库 Repository 是仓库管理员,仓库中可以有多种资源的数据映射, 而数据映射是持久化数据存储层(通常是关系型数据库)和驻于内存的数据表现层之间的数据访问层, 领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要关心东西实际放在哪、怎么加工处理。 这也可以说是资源库模式(Repository)与数据映射模式(Data Mapper)的区别。 这是一种架构模式,可以用于软件架构中。

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

使用类似集合的接口在域和数据映射层之间进行中介,以访问域对象。 Repository封装了数据存储中持久化的对象集以及对这些对象执行的操作, 提供了持久化层更面向对象的视图。 存储库还支持在域和数据映射层之间实现干净分离和单向依赖的目标。

与类似接口的区别:

  1. 资源库模式 —— 是仓库管理员,仓库中可以有多种资源的数据映射。
  2. 数据映射模式 —— 是持久化数据存储层(通常是关系型数据库)和驻于内存的数据表现层之间的数据访问层。

EAV模式

  • 模式定义

The Entity–attribute–value (EAV) model is a data model to describe entities where the number of attributes (properties, parameters) that can be used to describe them is potentially vast, but the number that will actually apply to a given entity is relatively modest.

实体-属性-值(Entity-attribute-value,EAV)模型是一种数据模型,用于描述实体,其中可用于描述它们的属性(属性、参数)的数量可能很大, 但实际应用于给定实体的数量相对较少。

  • UML类图

  • 示例代码

Entity.php

<?php 
namespace DesignPatterns\More\EAV;

use SplObjectStorage;

class Entity
{
    /**
     * @var SplObjectStorage<Value,Value>
     */
    private $values;

    /**
     * @var string
     */
    private $name;

    /**
     * @param string $name
     * @param Value[] $values
     */
    public function __construct(string $name, $values)
    {
        /** @var SplObjectStorage<Value,Value> values */
        $this->values = new SplObjectStorage();
        $this->name = $name;

        foreach ($values as $value) {
            $this->values->attach($value);
        }
    }

    public function __toString(): string
    {
        $text = [$this->name];

        foreach ($this->values as $value) {
            $text[] = (string) $value;
        }

        return join(', ', $text);
    }
}

Attribute.php

<?php 
namespace DesignPatterns\More\EAV;

use SplObjectStorage;

class Attribute
{
    /**
     * @var SplObjectStorage
     */
    private $values;

    /**
     * @var string
     */
    private $name;

    public function __construct(string $name)
    {
        $this->values = new SplObjectStorage();
        $this->name = $name;
    }

    public function addValue(Value $value)
    {
        $this->values->attach($value);
    }

    public function getValues(): SplObjectStorage
    {
        return $this->values;
    }

    public function __toString(): string
    {
        return $this->name;
    }
}

Value.php

<?php
namespace DesignPatterns\More\EAV;

class Value
{
    /**
     * @var Attribute
     */
    private $attribute;

    /**
     * @var string
     */
    private $name;

    public function __construct(Attribute $attribute, string $name)
    {
        $this->name = $name;
        $this->attribute = $attribute;

        $attribute->addValue($this);
    }

    public function __toString(): string
    {
        return sprintf('%s: %s', (string) $this->attribute, $this->name);
    }
}
  • 测试代码

EAVTest.php

<?php
namespace DesignPatterns\More\EAV\Tests;

use DesignPatterns\More\EAV\Attribute;
use DesignPatterns\More\EAV\Entity;
use DesignPatterns\More\EAV\Value;
use PHPUnit\Framework\TestCase;

class EAVTest extends TestCase
{
    public function testCanAddAttributeToEntity()
    {
        $colorAttribute = new Attribute('color');
        $colorSilver = new Value($colorAttribute, 'silver');
        $colorBlack = new Value($colorAttribute, 'black');

        $memoryAttribute = new Attribute('memory');
        $memory8Gb = new Value($memoryAttribute, '8GB');

        $entity = new Entity('MacBook Pro', [$colorSilver, $colorBlack, $memory8Gb]);

        $this->assertEquals('MacBook Pro, color: silver, color: black, memory: 8GB', (string) $entity);
    }
}

服务定位器模式(Service Locator)

  • 模式定义

当系统中的组件需要调用某一服务来完成特定的任务时,通常最简单的做法是使用 new 关键字来创建该服务的实例, 或者通过工厂模式来解耦该组件与服务的具体实现部分,以便通过配置信息等更为灵活的方式获得该服务的实例。然而,这些做法都有着各自的弊端:

  1. 在组件中直接维护对服务实例的引用,会造成组件与服务之间的关联依赖,当需要替换服务的具体实现时, 不得不修改组件中调用服务的部分并重新编译解决方案;即使采用工厂模式来根据配置信息动态地获得服务的实例, 也无法针对不同的服务类型向组件提供一个管理服务实例的中心位置;
  2. 由于组件与服务之间的这种关联依赖,使得项目的开发过程受到约束。在实际项目中,开发过程往往是并行的,但又不是完全同步的, 比如组件的开发跟其所需要的服务的开发同时进行,但很有可能当组件需要调用服务时,服务却还没完成开发和单体测试。 遇到这种问题时,通常会将组件调用服务的部分暂时空缺,待到服务完成开发和单体测试之后,将其集成到组件的代码中。 但这种做法不仅费时,而且增大了出错的风险;
  3. 针对组件的单体测试变得复杂。每当对组件进行单体测试时,不得不为其配置并运行所需要的服务, 而无法使用Service Stub来解决组件与服务之间的依赖;
  4. 在组件中可能存在多个地方需要引用服务的实例,在这种情况下,直接创建服务实例的代码会散布到整个程序中, 造成一段程序存在多个副本,大大增加维护和排错成本;
  5. 当组件需要调用多个服务时,不同服务初始化各自实例的方式又可能存在差异。 开发人员不得不了解所有服务初始化的API,以便在程序中能够正确地使用这些服务;
  6. 某些服务的初始化过程需要耗费大量资源,因此多次重复地初始化服务会大大增加系统的资源占用和性能损耗。 程序中需要有一个管理服务初始化过程的机制,在统一初始化接口的同时,还需要为程序提供部分缓存功能。

要解决以上问题,我们可以在应用程序中引入服务定位器(Service Locator)模式。 服务定位器(Service Locator)模式是一种企业级应用程序体系结构模式,它能够为应用程序中服务的创建和初始化提供一个中心位置, 并解决了上文中所提到的各种设计和开发问题。 服务定位器模式和依赖注入模式都是控制反转(IoC)模式的实现。 我们在服务定位器中注册给定接口的服务实例,然后通过接口获取服务并在应用代码中使用而不需要关心其具体实现。 我们可以在启动时配置并注入服务提供者。 如果你了解 Laravel 框架,你对这一流程会很熟悉,没错,这就是 Laravel 框架的核心机制, 我们在服务提供者中绑定接口及其实现,将服务实例注册到服务容器中,然后在使用时可以通过依赖注入或者通过服务接口/别名获取服务实例的方式调用服务。

  • UML类图

  • 示例代码

ServiceLocatorInterface.php

<?php
namespace DesignPatterns\More\ServiceLocator;

interface ServiceLocatorInterface
{
    /**
     * Checks if a service is registered.
     *
     * @param string $interface
     *
     * @return bool
     */
    public function has($interface);

    /**
     * Gets the service registered for the interface.
     *
     * @param string $interface
     *
     * @return mixed
     */
    public function get($interface);
}

ServiceLocator.php

<?php
namespace DesignPatterns\More\ServiceLocator;

class ServiceLocator implements ServiceLocatorInterface
{
    /**
     * All services.
     *
     * @var array
     */
    private $services;

    /**
     * The services which have an instance.
     *
     * @var array
     */
    private $instantiated;

    /**
     * True if a service can be shared.
     *
     * @var array
     */
    private $shared;

    public function __construct()
    {
        $this->services     = array();
        $this->instantiated = array();
        $this->shared       = array();
    }

    /**
     * Registers a service with specific interface.
     *
     * @param string $interface
     * @param string|object $service
     * @param bool $share
     */
    public function add($interface, $service, $share = true)
    {
        /**
         * When you add a service, you should register it
         * with its interface or with a string that you can use
         * in the future even if you will change the service implementation.
         */

        if (is_object($service) && $share) {
            $this->instantiated[$interface] = $service;
        }
        $this->services[$interface] = (is_object($service) ? get_class($service) : $service);
        $this->shared[$interface]   = $share;
    }

    /**
     * Checks if a service is registered.
     *
     * @param string $interface
     *
     * @return bool
     */
    public function has($interface)
    {
        return (isset($this->services[$interface]) || isset($this->instantiated[$interface]));
    }

    /**
     * Gets the service registered for the interface.
     *
     * @param string $interface
     *
     * @return mixed
     */
    public function get($interface)
    {
        // Retrieves the instance if it exists and it is shared
        if (isset($this->instantiated[$interface]) && $this->shared[$interface]) {
            return $this->instantiated[$interface];
        }

        // otherwise gets the service registered.
        $service = $this->services[$interface];

        // You should check if the service class exists and
        // the class is instantiable.

        // This example is a simple implementation, but
        // when you create a service, you can decide
        // if $service is a factory or a class.
        // By registering a factory you can create your services
        // using the DependencyInjection pattern.

        // ...

        // Creates the service object
        $object = new $service();

        // and saves it if the service must be shared.
        if ($this->shared[$interface]) {
            $this->instantiated[$interface] = $object;
        }
        return $object;
    }
}

LogServiceInterface.php

<?php
namespace DesignPatterns\More\ServiceLocator;

interface LogServiceInterface
{
}

LogService.php

<?php
namespace DesignPatterns\More\ServiceLocator;

class LogService implements LogServiceInterface
{
}

DatabaseServiceInterface.php

<?php
namespace DesignPatterns\More\ServiceLocator;

interface DatabaseServiceInterface
{
}

DatabaseService.php

<?php
namespace DesignPatterns\More\ServiceLocator;

class DatabaseService implements DatabaseServiceInterface
{
}
  • 测试代码

Tests/ServiceLocatorTest.php

<?php
namespace DesignPatterns\More\ServiceLocator\Tests;

use DesignPatterns\More\ServiceLocator\DatabaseService;
use DesignPatterns\More\ServiceLocator\LogService;
use DesignPatterns\More\ServiceLocator\ServiceLocator;
use \PHPUnit_Framework_TestCase as TestCase;

class ServiceLocatorTest extends TestCase
{
    /**
     * @var LogService
     */
    private $logService;

    /**
     * @var DatabaseService
     */
    private $databaseService;

    /**
     * @var ServiceLocator
     */
    private $serviceLocator;

    public function setUp()
    {
        $this->serviceLocator  = new ServiceLocator();
        $this->logService      = new LogService();
        $this->databaseService = new DatabaseService();
    }

    public function testHasServices()
    {
        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\LogServiceInterface',
            $this->logService
        );

        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
            $this->databaseService
        );

        $this->assertTrue($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\LogServiceInterface'));
        $this->assertTrue($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface'));

        $this->assertFalse($this->serviceLocator->has('DesignPatterns\More\ServiceLocator\FakeServiceInterface'));
    }

    public function testServicesWithObject()
    {
        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\LogServiceInterface',
            $this->logService
        );

        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
            $this->databaseService
        );

        $this->assertSame(
            $this->logService,
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
        );

        $this->assertSame(
            $this->databaseService,
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
        );
    }

    public function testServicesWithClass()
    {
        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\LogServiceInterface',
            get_class($this->logService)
        );

        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
            get_class($this->databaseService)
        );

        $this->assertNotSame(
            $this->logService,
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
        );

        $this->assertInstanceOf(
            'DesignPatterns\More\ServiceLocator\LogServiceInterface',
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
        );

        $this->assertNotSame(
            $this->databaseService,
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
        );

        $this->assertInstanceOf(
            'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
        );
    }

    public function testServicesNotShared()
    {
        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\LogServiceInterface',
            $this->logService,
            false
        );

        $this->serviceLocator->add(
            'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
            $this->databaseService,
            false
        );

        $this->assertNotSame(
            $this->logService,
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
        );

        $this->assertInstanceOf(
            'DesignPatterns\More\ServiceLocator\LogServiceInterface',
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\LogServiceInterface')
        );

        $this->assertNotSame(
            $this->databaseService,
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
        );

        $this->assertInstanceOf(
            'DesignPatterns\More\ServiceLocator\DatabaseServiceInterface',
            $this->serviceLocator->get('DesignPatterns\More\ServiceLocator\DatabaseServiceInterface')
        );
    }
}
  • 总结

与类似接口的区别:

  1. 服务定位器模式 —— 把服务在服务定位器中注册好,使用时可以由服务定位器或返回新的实例、或返回实例化好的实例。
  2. 注册模式 —— 把实例化好的类放在一个注册的容器中,使用时从容器中获取。
  3. 享元模式 —— 没有注册这一步,使用时直接从Factory类获取,Factory类决定怎么创建对象和返回。
  4. 单例模式 —— 单例模式的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。
  5. 对象池模式 —— 对象池是一组已经初始化过且可以直接使用的对象集合,使用对象时可以从对象池中获取对象并进行操作,在不需要时归还给对象池。
  • 拓展

服务定位器(Service Locator)是IoC的另一种实现方式, 其核心是把所有可能用到的依赖单元交由Service Locator进行实例化和创建、配置, 把类对依赖单元的依赖,转换成类对Service Locator的依赖。 DI 与 Service Locator并不冲突,两者可以结合使用。 目前,Yii2.0把这DI和Service Locator这两个东西结合起来使用,或者说通过DI容器,实现了Service Locator。

具体实例,可以看一下 深入理解Yii2.0 服务定位器

事件模式(Event)

使用事件,可以在特定的时点,触发执行预先设定的一段代码,事件既是代码解耦的一种方式,也是设计业务流程的一种模式。

具体实例,可以看一下 深入理解Yii2.0 事件

行为模式(Behavior)

使用行为(behavior)可以在不修改现有类的情况下,对类的功能进行扩充。 通过将行为绑定到一个类,可以使类具有行为本身所定义的属性和方法, 就好像类本来就有这些属性和方法一样。 而且不需要写一个新的类去继承或包含现有类。

具体实例,可以看一下 深入理解Yii2.0 行为

通俗解释

23 种设计模式的通俗解释:

01 工厂方法(Factory method)

追 MM 少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是 MM 爱吃的东西,虽然口味有所不同,但不管你带 MM 去麦当劳或肯德基, 只管向服务员说「来四个鸡翅」就行了。麦当劳和肯德基就是生产鸡翅的 Factory 工厂模式:客户类和工厂类分开。

消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。 如:如何创建及如何向客户端提供。

02 建造者模式(Builder)

MM 最爱听的就是「我爱你」这句话了,见到不同地方的 MM,要能够用她们的方言跟她说这句话哦,我有一个多种语言翻译机,上面每种语言都有一个按键, 见到 MM 我只要按对应的键,它就能够用相应的语言说出「我爱你」这句话了,国外的 MM 也可以轻松搞掂,这就是我的「我爱你」builder。

建造模式:将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化, 客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。

03 抽象工厂(Abstract factory)

请 MM 去麦当劳吃汉堡,不同的 MM 有不同的口味,要每个都记住是一件烦人的事情,我一般采用 Factory Method 模式, 带着 MM 到服务员那儿,说「要一个汉堡」,具体要什么样的汉堡呢,让 MM 直接跟服务员说就行了。

工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色, 仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

04 原型模式(Prototype)

跟 MM 用 QQ 聊天,一定要说些深情的话语了,我搜集了好多肉麻的情话,需要时只要 copy 出来放到 QQ 里面就行了,这就是我的情话 prototype 了。(100 块钱一份,你要不要)

原始模型模式:通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的方法创建出更多同类型的对象。 原始模型模式允许动态的增加或减少产品类,产品类不需要非得有任何事先确定的等级结构,原始模型模式适用于任何的等级结构。 缺点是每一个类都必须配备一个克隆方法。

05 单态模式(Singleton)

俺有 6 个漂亮的老婆,她们的老公都是我,我就是我们家里的老公 Sigleton,她们只要说道「老公」,都是指的同一个人,那就是我 (刚才做了个梦啦,哪有这么好的事)

单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的 “单一实例” 的需求时才可使用。

06 适配器模式(Adapter)

在朋友聚会上碰到了一个美女 Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友 kent 了, 他作为我和 Sarah 之间的 Adapter,让我和 Sarah 可以相互交谈了 (也不知道他会不会耍我)

适配器(变压器)模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。 适配类可以根据参数返还一个合适的实例给客户端。

07 桥梁模式(Bridge)

早上碰到 MM,要说早上好,晚上碰到 MM,要说晚上好;碰到 MM 穿了件新衣服,要说你的衣服好漂亮哦,碰到 MM 新做的发型,要说你的头发好漂亮哦。 不要问我 “早上碰到 MM 新做了个发型怎么说” 这种问题,自己用 BRIDGE 组合一下不就行了

桥梁模式:将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联, 也就是指在一个软件系统的抽象化和实现化之间使用组合 / 聚合关系而不是继承关系,从而使两者可以独立的变化。

08 合成模式(Composite)

Mary 今天过生日。“我过生日,你要送我一件礼物。”“嗯,好吧,去商店,你自己挑。”“这件 T 恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。” “喂,买了三件了呀,我只答应送一件礼物的哦。”“什么呀,T 恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。” “……”,MM 都会用 Composite 模式了,你会了没有?

合成模式:合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。 合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。

09 装饰模式(Decorator)

Mary 过完轮到 Sarly 过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片, 在背面写上 “最好的的礼物,就是爱你的 Fita”,再到街上礼品店买了个像框(卖礼品的 MM 也很漂亮哦), 再找隔壁搞美术设计的 Mike 设计了一个漂亮的盒子装起来……,我们都是 Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?

装饰模式:装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能, 这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。

10 门面模式(Facade)

我有一个专业的 Nikon 相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但 MM 可不懂这些,教了半天也不会。 幸好相机有 Facade 设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样 MM 也可以用这个相机给我拍张照片了。 门面模式:外部与一个子系统的通信必须通过一个统一的门面对象进行。

门面模式提供一个高层次的接口,使得子系统更易于使用。每一个子系统只有一个门面类,而且此门面类只有一个实例,也就是说它是一个单例模式。但整个系统可以有多个门面类。

11 享元模式(Flyweight)

每天跟 MM 发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来, 在前面加上 MM 的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是 Flyweight,MM 的名字就是提取出来的外部特征, 根据上下文情况使用。享元模式:FLYWEIGHT 在拳击比赛中指最轻量级。

享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。 内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。

将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。 客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。

12 代理模式(Proxy)

跟 MM 在网上聊天,一开头总是 “hi, 你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?” 这些话,真烦人,写个程序做为我的 Proxy 吧, 凡是接收到这些话都设置好了自己的回答,接收到其他的话时再通知我回答,怎么样,酷吧。

代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。 某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。

客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口, 这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

13 责任链模式(Chain Of Responsibilities)

晚上去上英语课,为了好开溜坐到了最后一排,哇,前面坐了好几个漂亮的 MM 哎,找张纸条,写上 “Hi, 可以做我的女朋友吗? 如果不愿意请向前传”,纸条就一个接一个的传上去了,糟糕,传到第一排的 MM 把纸条传给老师了,听说是个老处女呀,快跑!

责任链模式:在责任链模式中,很多对象由每一个对象对其下家的引用而接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。 客户并不知道链上的哪一个对象最终处理这个请求,系统可以在不影响客户端的情况下动态的重新组织链和分配责任。 处理者有两个选择:承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。

14 命令模式(Command)

俺有一个 MM 家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。 这不,她弟弟又传送过来一个 COMMAND,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送 COMMAND,就数你最小气,才请我吃面。”

命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。 命令模式允许请求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收, 以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。

15 解释器模式(Interpreter)

俺有一个《泡 MM 真经》,上面有各种泡 MM 的攻略,比如说去吃西餐的步骤、去看电影的方法等等,跟 MM 约会时,只要做一个 Interpreter,照着上面的脚本执行就可以了。

解释器模式:给定一个语言后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。 解释器模式将描述怎样在有了一个简单的文法后,使用模式设计解释这些语句。

在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构,也就是一系列的组合规则。 每一个命令对象都有一个解释方法,代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。

16 迭代模式(Iterator)

我爱上了 Mary,不顾一切的向她求婚。Mary:“想要我跟你结婚,得答应我的条件” 我:“什么条件我都答应,你说吧” Mary:“我看上了那个一克拉的钻石” 我:“我买,我买,还有吗?” Mary:“我看上了湖边的那栋别墅” 我:“我买,我买,还有吗?” Mary:“我看上那辆法拉利跑车” 我脑袋嗡的一声,坐在椅子上,一咬牙:“我买,我买,还有吗?”

迭代模式:迭代模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集,聚集对象是能够包容一组对象的容器对象。 迭代子模式将迭代逻辑封装到一个独立的子对象中,从而与聚集本身隔开。

迭代模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。

17 调停者模式(Mediator)

四个 MM 打麻将,相互之间谁应该给谁多少钱算不清楚了,幸亏当时我在旁边,按照各自的筹码数算钱,赚了钱的从我这里拿,赔了钱的也付给我, 一切就 OK 啦,俺得到了四个 MM 的电话。

调停者模式:调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使他们可以松散偶合。

当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。 调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。

18 备忘录模式(Memento)

同时跟几个 MM 聊天时,一定要记清楚刚才跟 MM 说了些什么话,不然 MM 发现了会不高兴的哦, 幸亏我有个备忘录,刚才与哪个 MM 说了什么话我都拷贝一份放到备忘录里面保存,这样可以随时察看以前的记录啦。

备忘录模式:备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下, 将一个对象的状态捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。

19 观察者模式(Observer)

想知道咱们公司最新 MM 情报吗?加入公司的 MM 情报邮件组就行了,tom 负责搜集情报,他发现的新情报不用一个一个通知我们, 直接发布给邮件组,我们作为订阅者(观察者)就可以及时收到情报啦。

观察者模式:观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时, 会通知所有观察者对象,使他们能够自动更新自己。

20 状态模式(State)

跟 MM 交往时,一定要注意她的状态哦,在不同的状态时她的行为会有不同,比如你约她今天晚上去看电影,对你没兴趣的 MM 就会说 “有事情啦”, 对你不讨厌但还没喜欢上的 MM 就会说 “好啊,不过可以带上我同事么?”,已经喜欢上你的 MM 就会说 “几点钟?看完电影再去泡吧怎么样?”, 当然你看电影过程中表现良好的话,也可以把 MM 的状态从不讨厌不喜欢变成喜欢哦。

状态模式:状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。 状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。

状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。 当系统的状态变化时,系统便改变所选的子类。

21 策略模式(Strategy)

跟不同类型的 MM 约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去海边浪漫最合适,单目的都是为了得到 MM 的芳心, 我的追 MM 锦囊中有好多 Strategy 哦。策略模式:策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模把行为和环境分开。环境类负责维持和查询行为类,各种算法在具体的策略类中提供。 由于算法和环境独立开来,算法的增减,修改都不会影响到环境和客户端。

22 模板方法模式(Template Method)

看过《如何说服女生上床》这部经典文章吗?女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤 (Template method), 但每个步骤针对不同的情况,都有不一样的做法,这就要看你随机应变啦 (具体实现);

模板方法模式:模板方法模式准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。 不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

23 访问者模式(Visitor)

情人节到了,要给每个 MM 送一束鲜花和一张卡片,可是每个 MM 送的花都要针对她个人的特点,每张卡片也要根据个人的特点来挑, 我一个人哪搞得清楚,还是找花店老板和礼品店老板做一下 Visitor,让花店老板根据 MM 的特点选一束花, 让礼品店老板也根据每个人特点选一张卡,这样就轻松多了;

访问者模式:访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。 访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。 访问者模式使得增加新的操作变的很容易,就是增加一个新的访问者类。

访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。当使用访问者模式时,要将尽可能多的对象浏览逻辑放在访问者类中, 而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。






参考资料

常见软件架构模式 https://ibaiyang.github.io/blog/php/2019/07/30/常见软件架构模式.html

DesignPatternsPHP https://github.com/domnikl/DesignPatternsPHP

PHP 设计模式系列 https://laravelacademy.org/books/php-design-pattern

PHP 设计模式说明 http://www.yii-china.com/design/php/

设计模式六大原则:单一职责原则 https://www.cnblogs.com/az4215/p/11462818.html

设计模式(Design pattern) https://www.runoob.com/design-pattern/design-pattern-tutorial.html

PHP 设计模式(Design Pattern For PHP) https://www.cnblogs.com/wilburxu/category/910011.html

php-the-right-way 设计模式 http://laravel-china.github.io/php-the-right-way/pages/Design-Patterns.html

DesignPatternsPHP https://designpatternsphp.readthedocs.io/en/latest/

DesignPatternsPHP(PHP设计模式范例) https://designpatternsphp.readthedocs.io/zh_CN/latest/README.html

PHP 设计模式概述 https://segmentfault.com/a/1190000016629282

PHP八大设计模式 https://blog.csdn.net/flitrue/article/details/52614599

php各种设计模式简单实践思考 https://www.cnblogs.com/aksir/p/6777595.html

23 种设计模式的通俗解释 https://mp.weixin.qq.com/s/bAL__qVKMwALjzUP8HdQlg

秒懂 23 种设计模式 https://mp.weixin.qq.com/s/nmhXBiPi1Nu1KRzX8xlXYg


返回