PHP 创建型模式


PHP 创建型模式


正文

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

简单工厂模式(Simple Factory)

  • 模式定义

简单工厂的作用是实例化对象,而不需要客户了解这个对象属于哪个具体的子类。 简单工厂实例化的类具有相同的接口或者基类,在子类比较固定并不需要扩展时,可以使用简单工厂。

  • UML类图

  • 实例代码

ConcreteFactory.php

<?php
namespace DesignPatterns\Creational\SimpleFactory;

/**
 * ConcreteFactory类
 */
class ConcreteFactory
{
    /**
     * @var array
     */
    protected $typeList;

    /**
     * 你可以在这里注入自己的车子类型
     */
    public function __construct()
    {
        $this->typeList = array(
            'bicycle' => __NAMESPACE__ . '\Bicycle',
            'other' => __NAMESPACE__ . '\Scooter'
        );
    }

    /**
     * 创建车子
     *
     * @param string $type a known type key
     *
     * @return VehicleInterface a new instance of VehicleInterface
     * @throws \InvalidArgumentException
     */
    public function createVehicle($type)
    {
        if (!array_key_exists($type, $this->typeList)) {
            throw new \InvalidArgumentException("$type is not valid vehicle");
        }
        $className = $this->typeList[$type];

        return new $className();
    }
}

VehicleInterface.php

<?php
namespace DesignPatterns\Creational\SimpleFactory;

/**
 * VehicleInterface 是车子接口
 */
interface VehicleInterface
{
    /**
     * @param mixed $destination
     *
     * @return mixed
     */
    public function driveTo($destination);
}

Bicycle.php

<?php
namespace DesignPatterns\Creational\SimpleFactory;

/**
 * 自行车类
 */
class Bicycle implements VehicleInterface
{
    /**
     * @param mixed $destination
     *
     * @return mixed|void
     */
    public function driveTo($destination)
    {
    }
}

Scooter.php

<?php
namespace DesignPatterns\Creational\SimpleFactory;

/**
 * 摩托车类
 */
class Scooter implements VehicleInterface
{
    /**
     * @param mixed $destination
     */
    public function driveTo($destination)
    {
    }
}
  • 测试代码

Tests/SimpleFactoryTest.php

<?php

namespace DesignPatterns\Creational\SimpleFactory\Tests;

use DesignPatterns\Creational\SimpleFactory\ConcreteFactory;

/**
 * SimpleFactoryTest 用于测试简单工厂模式
 */
class SimpleFactoryTest extends \PHPUnit_Framework_TestCase
{
    protected $factory;

    protected function setUp()
    {
        $this->factory = new ConcreteFactory();
    }

    public function getType()
    {
        return array(
            array('bicycle'),
            array('other')
        );
    }

    /**
     * @dataProvider getType
     */
    public function testCreation($type)
    {
        $obj = $this->factory->createVehicle($type);
        $this->assertInstanceOf('DesignPatterns\Creational\SimpleFactory\VehicleInterface', $obj);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testBadType()
    {
        $this->factory->createVehicle('car');
    }
}
  • 总结

采用简单工厂的优点是可以使用户根据参数获得对应的类实例,避免了直接实例化类,降低了耦合性; 缺点是可实例化的类型在编译期间已经被确定,如果增加新类型,则需要修改工厂,不符合OCP(开闭原则)的原则。 简单工厂需要知道所有要生成的类型,当子类过多或者子类层次过多时不适合使用。

  • 拓展

四种工厂的比较:

  • 简单工厂模式 :一个工厂用来生产同一等级结构中的任意产品。(对于增加新的产品,无能为力,因为新增了产品就需要修改旧工厂)
  • 静态工厂模式 :和简单工厂相似,只是实例获取方法换成了静态方法来获取。
  • 工厂方法模式 :每个工厂用来生产同一等级结构中的固定产品。(支持增加任意产品,新增产品时也新建该产品的生产工厂)
  • 抽象工厂模式 :每个工厂用来生产不同产品族的全部产品。(对于增加新的产品,无能为力,因为要修改已成型的旧工厂;支持增加产品族,可以组合不同产品组建新工厂)

简单工厂(Simple Factory),官方解释为:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。

案例:

  1. 支付宝、微信、银联的连接方式(connectMode),支付方式(payMode)。使用工厂模式,“客户”就不需要知道具体的连接方式和支付方式了,只需要调用connectMode 和payMode即可。
  2. MySQL、SQL Server、Oracle等数据库的连接方式(connectMode)、查询方式(selectMode)等操作可以使用工厂模式进行封装。下面的例子会讲到。

我们以数据库类创建的案例来说:

产品类:

/** 
 * 数据库系列 
 * 
 */  
abstract Class DataBase
{  
    abstract function getOne($sql); //获取一条数据的方法
}  

Class SqlServer extends DataBase
{  
    function __construct() { 
        $connect = "SqlServer 连接方法操作 (腾讯云服务器)";
        return $connect;
    }

  function getOne($sql){
        return "查询后返回数据结果";
    }
}  

Class MySql extends DataBase
{  
   function __construct(){  
       $connect = "MySql 连接方法操作 (阿里云服务器)";
       return $connect;
   }

    function getOne($sql){
        return "查询后返回数据结果";
    }
}

工厂类:

/** 
 *  
 * 创建数据库的工厂类 
 */  
class Factory 
{  
    public function  createDataBase($type) 
    {  
        switch ($type) 
        {  
            case SqlServer:  
                return new SqlServer();  
            case MySql:  
                return new MySql();  
            //....  
        }
    }  

}  

客户类:

/** 
 *  
 * 客户通过工厂获取数据 
 */  
class Customer 
{  
    private $database;  
    
    function getDataBase($type) 
    {  
        return $this->database =  (new Factory())->createDataBase($type);  
    } 
}

$custome = new Customer;
$db = $custome->getDataBase("SqlServer");  // 我要获取阿里云的SQL Server数据库的数据。
$data = $db->getOne($sql);

通过以上案例可以得知一般情况下工厂模式由以下几个部分组成:

  1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。如例子中的Factory类。
  2. 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。由接口或者抽象类来实现。如例中的DataBase接口。
  3. 具体产品角色:工厂类所创建的对象就是此角色的实例。由一个具体类实现,如例子中的MySql和SqlServer类。

简单工厂的作用是实例化对象,而不需要客户了解这个对象属于哪个具体的子类。简单工厂实例化的类具有相同的接口或者基类, 在子类比较固定并不需要扩展时,可以使用简单工厂,一定程度上可以很好的降低耦合度。

使用工厂设计模式时必须先归类你的产品(需求)找到共同点和特征,然后根据共同的地方创建各自的产品类, 这时候如果没有无法通过客户类去调用每一个产品类,那么耦合度会大大增高(在需求变动的时候), 这时候创建一个工厂类统一管理产品类, 再通过客户类调用。 那么可以很好的管理代码并一定程度上的解耦。

静态工厂模式(Static Factory)

  • 模式定义

与简单工厂类似,该模式用于创建一组相关或依赖的对象,不同之处在于静态工厂模式使用一个静态方法来创建所有类型的对象, 该静态方法通常是 factory 或 build。

  • UML类图

  • 示例代码

StaticFactory.php

<?php
namespace DesignPatterns\Creational\StaticFactory;

class StaticFactory
{
    /**
     * 通过传入参数创建相应对象实例
     *
     * @param string $type
     *
     * @static
     *
     * @throws \InvalidArgumentException
     * @return FormatterInterface
     */
    public static function factory($type)
    {
        $className = __NAMESPACE__ . '\Format' . ucfirst($type);

        if (!class_exists($className)) {
            throw new \InvalidArgumentException('Missing format class.');
        }

        return new $className();
    }
}

FormatterInterface.php

<?php
namespace DesignPatterns\Creational\StaticFactory;

/**
 * FormatterInterface接口
 */
interface FormatterInterface
{
}

FormatNumber.php

<?php
namespace DesignPatterns\Creational\StaticFactory;

/**
 * FormatNumber类
 */
class FormatNumber implements FormatterInterface
{
}

FormatString.php

<?php
namespace DesignPatterns\Creational\StaticFactory;

/**
 * FormatString类
 */
class FormatString implements FormatterInterface
{
}
  • 测试代码

Tests/StaticFactoryTest.php

<?php
namespace DesignPatterns\Creational\StaticFactory\Tests;

use DesignPatterns\Creational\StaticFactory\StaticFactory;

/**
 * 测试静态工厂模式
 *
 */
class StaticFactoryTest extends \PHPUnit_Framework_TestCase
{
    public function getTypeList()
    {
        return array(
            array('string'),
            array('number')
        );
    }

    /**
     * @dataProvider getTypeList
     */
    public function testCreation($type)
    {
        $obj = StaticFactory::factory($type);
        $this->assertInstanceOf('DesignPatterns\Creational\StaticFactory\FormatterInterface', $obj);
    }

    /**
     * @expectedException InvalidArgumentException
     */
    public function testException()
    {
        StaticFactory::factory("");
    }
}

工厂方法模式(Factory Method)

  • 模式定义

定义一个创建对象的接口,但是让子类去实例化具体类。工厂方法模式让类的实例化延迟到子类中。

  • 问题引出

框架需要为多个应用提供标准化的架构模型,同时也要允许独立应用定义自己的域对象并对其进行实例化。

  • 解决办法

工厂方法以模板方法的方式创建对象来解决上述问题。父类定义所有标准通用行为,然后将创建细节放到子类中实现并输出给客户端。 人们通常使用工厂模式作为创建对象的标准方式,但是在这些情况下不必使用工厂方法: 实例化的类永远不会改变;或者实例化发生在子类可以轻易覆盖的操作中(比如初始化)。

  • UML类图

  • 示例代码

FactoryMethod.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * 工厂方法抽象类
 */
abstract class FactoryMethod
{

    const CHEAP = 1;
    const FAST = 2;

    /**
     * 子类必须实现该方法
     *
     * @param string $type a generic type
     *
     * @return VehicleInterface a new vehicle
     */
    abstract protected function createVehicle($type);

    /**
     * 创建新的车辆
     *
     * @param int $type
     *
     * @return VehicleInterface a new vehicle
     */
    public function create($type)
    {
        $obj = $this->createVehicle($type);
        $obj->setColor("#f00");

        return $obj;
    }
}

ItalianFactory.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * ItalianFactory是意大利的造车厂
 */
class ItalianFactory extends FactoryMethod
{
    /**
     * {@inheritdoc}
     */
    protected function createVehicle($type)
    {
        switch ($type) {
            case parent::CHEAP:
                return new Bicycle();
                break;
            case parent::FAST:
                return new Ferrari();
                break;
            default:
                throw new \InvalidArgumentException("$type is not a valid vehicle");
        }
    }
}

GermanFactory.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * GermanFactory是德国的造车厂
 */
class GermanFactory extends FactoryMethod
{
    /**
     * {@inheritdoc}
     */
    protected function createVehicle($type)
    {
        switch ($type) {
            case parent::CHEAP:
                return new Bicycle();
                break;
            case parent::FAST:
                $obj = new Porsche();
                //因为我们已经知道是什么对象所以可以调用具体方法
                $obj->addTuningAMG();

                return $obj;
                break;
            default:
                throw new \InvalidArgumentException("$type is not a valid vehicle");
        }
    }
}

VehicleInterface.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * VehicleInterface是车辆接口
 */
interface VehicleInterface
{
    /**
     * 设置车的颜色
     *
     * @param string $rgb
     */
    public function setColor($rgb);
}

Porsche.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * Porsche(保时捷)
 */
class Porsche implements VehicleInterface
{
    /**
     * @var string
     */
    protected $color;

    /**
     * @param string $rgb
     */
    public function setColor($rgb)
    {
        $this->color = $rgb;
    }

    /**
     * 尽管只有奔驰汽车挂有AMG品牌,这里我们提供一个空方法仅作代码示例
     */
    public function addTuningAMG()
    {
    }
}

Bicycle.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * Bicycle(自行车)
 */
class Bicycle implements VehicleInterface
{
    /**
     * @var string
     */
    protected $color;

    /**
     * 设置自行车的颜色
     *
     * @param string $rgb
     */
    public function setColor($rgb)
    {
        $this->color = $rgb;
    }
}

Ferrari.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;

/**
 * Ferrari(法拉利)
 */
class Ferrari implements VehicleInterface
{
    /**
     * @var string
     */
    protected $color;

    /**
     * @param string $rgb
     */
    public function setColor($rgb)
    {
        $this->color = $rgb;
    }
}
  • 测试代码

Tests/FactoryMethodTest.php

<?php
namespace DesignPatterns\Creational\FactoryMethod\Tests;

use DesignPatterns\Creational\FactoryMethod\FactoryMethod;
use DesignPatterns\Creational\FactoryMethod\GermanFactory;
use DesignPatterns\Creational\FactoryMethod\ItalianFactory;

/**
 * FactoryMethodTest用于测试工厂方法模式
 */
class FactoryMethodTest extends \PHPUnit_Framework_TestCase
{

    protected $type = array(
        FactoryMethod::CHEAP,
        FactoryMethod::FAST
    );

    public function getShop()
    {
        return array(
            array(new GermanFactory()),
            array(new ItalianFactory())
        );
    }

    /**
     * @dataProvider getShop
     */
    public function testCreation(FactoryMethod $shop)
    {
        // 该方法扮演客户端角色,我们不关心什么工厂,我们只知道可以可以用它来造车
        foreach ($this->type as $oneType) {
            $vehicle = $shop->create($oneType);
            $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\VehicleInterface', $vehicle);
        }
    }

    /**
     * @dataProvider getShop
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage spaceship is not a valid vehicle
     */
    public function testUnknownType(FactoryMethod $shop)
    {
        $shop->create('spaceship');
    }
}
  • 总结

工厂方法模式和抽象工厂模式有点类似,但也有不同。 工厂方法针对每一种产品提供一个工厂类,通过不同的工厂实例来创建不同的产品实例, 在同一等级结构中,支持增加任意产品。 抽象工厂是应对产品族概念的,比如说,每个汽车公司可能要同时生产轿车,货车,客车,那么每一个工厂都要有创建轿车,货车和客车的方法。 应对产品族概念而生,增加新的产品线很容易,但是无法增加新的产品。

  • 拓展

简单工厂模式实现了产品类的代码跟客户端代码分离,但会有一个问题,优秀的代码是符合“开闭原则”如果你要加一个C类产品, 你就要修改工厂类里面的代码,也就是说要增加条件语句如:switch—case。对于这个问题,接下来的工厂方法模式可以解决这个问题。

工厂方法就是为配一个产品提供一个独立的工厂类,通过不同的工厂实例来创建不同的产品实例。

使用场景:

  1. 支付宝、微信、银联的连接方式(connectMode),支付方式(payMode)。 使用工厂模式,“客户”就不需要知道具体的连接方式和支付方式了, 只需要调用connectMode 和 payMode即可。
  2. MySQL、SQL Server、Oracle等数据库的连接方式(connectMode)、查询方式(selectMode)等操作可以使用工厂模式进行封装。

看一下案例:

产品类:

//抽象产品类
abstract class DataBase
{
    abstract function connect();
    abstract function getOne();
}

//具体产品类
class MySql extends DataBase
{
    function connect()
    {
        return "MySQL连接对象返回";
    }

    function getOne()
    {
        return "MySQL返回查询结果";
    }
}

//具体产品类
class SqlServer extends DataBase
{
    function connect()
    {
        return "SQL Server连接对象返回";
    }

    function getOne()
    {
        return "SQL Server返回查询结果";
    }
}

工厂类:

//抽象工厂类
abstract class FactoryDataBase{
    function createDataBase(){}
}

//具体工厂类
class FactoryMySql extends FactoryDataBase
{
    public function createDataBase()
    {
        return new MySql();
    }
}

//具体工厂类
class FactorySqlServer extends FactoryDataBase
{
    public function createDataBase()
    {
        return new SqlServer();
    }
}

客户:

$mysql = new FactoryMySql();
$db1 = $mysql->createDataBase();
$db1->connect($config);
$data = $db1->getOne($sql);

工厂方法模式的组成:

  1. 抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。
  2. 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
  3. 抽象产品角色:它是具体产品继承的父类或者是实现的接口。
  4. 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。

工厂方法模式的优点:

  1. 拥有良好的封装性,代码结构清晰。对于每一个对象的创建都是有条件约束的。如:调用一个具体的产品对象, 只需要知道这个产品的类名和约束参数就可以了,不用知道创建对象自身的复杂过程。降低模块之间的耦合度。
  2. 拥有良好的扩展性,新增一个产品类,只需要适当的增加工厂类或者扩展一个工厂类,如下面的例子中,当需要增加一个数据库Oracle的操作, 则只需要增加一个Oracle类,工厂类不用修改任务就可完成系统扩展。
  3. 屏蔽产品类。这一特点非常重要,产品类的实现如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。

工厂方法模式仿佛已经把对象的创建进行了很完美的包装,使得客户程序中仅仅处理抽象产品角色提供的接口。那我们是否一定要在代码中遍布工厂呢? 大可不必。也许在下面情况下你可以考虑使用工厂方法模式:

  • 当客户程序不需要知道要使用对象的创建过程。
  • 客户程序使用的对象存在变动的可能,或者根本就不知道使用哪一个具体的对象。

抽象工厂模式(Abstract Factory)

  • 模式概述

抽象工厂模式为一组相关或相互依赖的对象创建提供接口,而无需指定其具体实现类。抽象工厂的客户端不关心如何创建这些对象,只关心如何将它们组合到一起。

  • 问题引出

举个例子,如果某个应用是可移植的,那么它需要封装平台依赖,这些平台可能包括窗口系统、操作系统、数据库等等。 这种封装如果未经设计,通常代码会包含多个 if 条件语句以及对应平台的操作。这种硬编码不仅可读性差,而且扩展性也不好。

  • 解决方案

提供一个间接的层(即“抽象工厂”)抽象一组相关或依赖对象的创建而不是直接指定具体实现类。该“工厂”对象的职责是为不同平台提供创建服务。 客户端不需要直接创建平台对象,而是让工厂去做这件事。

这种机制让替换平台变得简单,因为抽象工厂的具体实现类只有在实例化的时候才出现,如果要替换的话只需要在实例化的时候指定具体实现类即可。

  • UML类图

抽象工厂为每个产品(具体实现)定义了工厂方法,每个工厂方法封装了new操作符和具体类(指定平台的产品类),每个“平台”都是抽象工厂的派生类。

  • 示例代码

AbstractFactory.php

<?php
namespace DesignPatterns\Creational\AbstractFactory;

/**
 * 抽象工厂类
 *
 * 该设计模式实现了设计模式的依赖倒置原则,因为最终由具体子类创建具体组件
 *
 * 在本例中,抽象工厂为创建 Web 组件(产品)提供了接口,这里有两个组件:文本和图片,有两种渲染方式:HTML
 * 和 JSON,对应四个具体实现类。
 *
 * 尽管有四个具体类,但是客户端只需要知道这个接口可以用于构建正确的 HTTP 响应即可,无需关心其具体实现。
 */
abstract class AbstractFactory
{
    /**
     * 创建本文组件
     *
     * @param string $content
     *
     * @return Text
     */
    abstract public function createText($content);

    /**
     * 创建图片组件
     *
     * @param string $path
     * @param string $name
     *
     * @return Picture
     */
    abstract public function createPicture($path, $name = '');
}

JsonFactory.php

<?php
namespace DesignPatterns\Creational\AbstractFactory;

/**
 * JsonFactory类
 *
 * JsonFactory 是用于创建 JSON 组件的工厂
 */
class JsonFactory extends AbstractFactory
{

    /**
     * 创建图片组件
     *
     * @param string $path
     * @param string $name
     *
     * @return Json\Picture|Picture
     */
    public function createPicture($path, $name = '')
    {
        return new Json\Picture($path, $name);
    }

    /**
     * 创建文本组件
     *
     * @param string $content
     *
     * @return Json\Text|Text
     */
    public function createText($content)
    {
        return new Json\Text($content);
    }
}

HtmlFactory.php

<?php
namespace DesignPatterns\Creational\AbstractFactory;

/**
 * HtmlFactory类
 *
 * HtmlFactory 是用于创建 HTML 组件的工厂
 */
class HtmlFactory extends AbstractFactory
{
    /**
     * 创建图片组件
     *
     * @param string $path
     * @param string $name
     *
     * @return Html\Picture|Picture
     */
    public function createPicture($path, $name = '')
    {
        return new Html\Picture($path, $name);
    }

    /**
     * 创建文本组件
     *
     * @param string $content
     *
     * @return Html\Text|Text
     */
    public function createText($content)
    {  
        return new Html\Text($content);
    }
}

MediaInterface.php

<?php
namespace DesignPatterns\Creational\AbstractFactory;

/**
 * MediaInterface接口
 *
 * 该接口不是抽象工厂设计模式的一部分, 一般情况下, 每个组件都是不相干的
 */
interface MediaInterface
{

    /**
     * JSON 或 HTML(取决于具体类)输出的未经处理的渲染
     *
     * @return string
     */
    public function render();
}

Picture.php

<?php
namespace DesignPatterns\Creational\AbstractFactory;

/**
 * Picture类
 */
abstract class Picture implements MediaInterface
{

    /**
     * @var string
     */
    protected $path;

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

    /**
     * @param string $path
     * @param string $name
     */
    public function __construct($path, $name = '')
    {
        $this->name = (string) $name;
        $this->path = (string) $path;
    }
}

Text.php

<?php
namespace DesignPatterns\Creational\AbstractFactory;

/**
 * Text类
 */
abstract class Text implements MediaInterface
{
    /**
     * @var string
     */
    protected $text;

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

Json/Picture.php

<?php
namespace DesignPatterns\Creational\AbstractFactory\Json;

use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;

/**
 * Picture类
 *
 * 该类是以 JSON 格式输出的具体图片组件类
 */
class Picture extends BasePicture
{
    /**
     * JSON 格式输出
     *
     * @return string
     */
    public function render()
    {
        return json_encode(array('title' => $this->name, 'path' => $this->path));
    }
}

Json/Text.php

<?php
namespace DesignPatterns\Creational\AbstractFactory\Json;

use DesignPatterns\Creational\AbstractFactory\Text as BaseText;

/**
 * Class Text
 *
 * 该类是以 JSON 格式输出的具体文本组件类
 */
class Text extends BaseText
{
    /**
     * 以 JSON 格式输出的渲染
     *
     * @return string
     */
    public function render()
    {
        return json_encode(array('content' => $this->text));
    }
}

Html/Picture.php

<?php
namespace DesignPatterns\Creational\AbstractFactory\Html;

use DesignPatterns\Creational\AbstractFactory\Picture as BasePicture;

/**
 * Picture 类
 *
 * 该类是以 HTML 格式渲染的具体图片类
 */
class Picture extends BasePicture
{
    /**
     * HTML 格式输出的图片
     *
     * @return string
     */
    public function render()
    {
        return sprintf('<img src="%s" title="%s"/>', $this->path, $this->name);
    }
}

Html/Text.php

<?php
namespace DesignPatterns\Creational\AbstractFactory\Html;

use DesignPatterns\Creational\AbstractFactory\Text as BaseText;

/**
 * Text 类
 *
 * 该类是以 HTML 渲染的具体文本组件类
 */
class Text extends BaseText
{
    /**
     * HTML 格式输出的文本
     *
     * @return string
     */
    public function render()
    {
        return '<div>' . htmlspecialchars($this->text) . '</div>';
    }
}
  • 测试代码

Tests/AbstractFactoryTest.php

<?php
namespace DesignPatterns\Creational\AbstractFactory\Tests;

use DesignPatterns\Creational\AbstractFactory\AbstractFactory;
use DesignPatterns\Creational\AbstractFactory\HtmlFactory;
use DesignPatterns\Creational\AbstractFactory\JsonFactory;

/**
 * AbstractFactoryTest 用于测试具体的工厂
 */
class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
{
    public function getFactories()
    {
        return array(
            array(new JsonFactory()),
            array(new HtmlFactory())
        );
    }

    /**
     * 这里是工厂的客户端,我们无需关心传递过来的是什么工厂类,
     * 只需以我们想要的方式渲染任意想要的组件即可。
     *
     * @dataProvider getFactories
     */
    public function testComponentCreation(AbstractFactory $factory)
    {
        $article = array(
            $factory->createText('php学院'),
            $factory->createPicture('/image.jpg', 'php-academy'),
            $factory->createText('phpAcademy.org')
        );

        $this->assertContainsOnly('DesignPatterns\Creational\AbstractFactory\MediaInterface', $article);
    }
}
  • 总结

最后我们以工厂生产产品为例,所谓抽象工厂模式就是我们的抽象工厂约定了可以生产的产品,这些产品都包含多种规格, 然后我们可以从抽象工厂为每一种规格派生出具体工厂类,然后让这些具体工厂类生产具体的产品。 以上示例中AbstractFactory是抽象工厂,JsonFactoryHtmlFactory是具体工厂, Html\PictureHtml\TextJson\PictureJson\Text都是具体产品, 客户端需要HTML格式的Text,调用HtmlFactorycreateText方法即可,而不必关心其实现逻辑。

  • 拓展

抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足以下条件:

  1. 系统中有多个产品族,而系统一次只可能消费其中一族产品。
  2. 同属于同一个产品族的产品可以使用。

产品族:位于不同产品等级结构中,功能相关联的产品组成的家族。下面例子的 汽车和空调就是两个产品树, 奔驰C200+格力某型号空调就是一个产品族, 同理, 奥迪A4+海尔某型号空调也是一个产品族。

看案例学习:

产品类:

// 汽车(抽象产品接口)
interface AutoProduct
{
    public function dirve();
}


//奥迪A4(具体产品类)
class AudiA4Product implements AutoProduct
{
    //获取汽车名称
    public function dirve()
    {
        echo "开奥迪A4"."<br>";
    }
}

//奔驰C200(具体产品类)
class BenzC200Product implements AutoProduct
{
    //获取汽车名称
    public function dirve()
    {
        echo "开奔驰C200"."<br>";
    }
}
//空调(抽象产品接口)
interface AirCondition
{
    public function blow();
}

//格力空调某型号(具体产品类)
class GreeAirCondition implements AirCondition
{
    public function blow()
    {
        echo "吹格力空调某型号"."<br>";
    }
}

//海尔空调某型号(具体产品类)
class HaierAirCondition implements AirCondition
{
    public function blow()
    {
        echo "吹海尔空调某型号"."<br>";
    }
}

工厂类:

//工厂接口
interface Factory
{
    public function getAuto();
    public function getAirCondition();
}


//工厂A = 奥迪A4 + 海尔空调某型号
class AFactory implements Factory
{
    //汽车
    public function getAuto()
    {
        return new AudiA4Product();
    }

    //空调
    public function getAirCondition()
    {
        return new HaierAirCondition();
    }
}

//工厂B = 奔驰C200 + 格力空调某型号
class BFactory implements Factory
{
    //汽车
    public function getAuto()
    {
        return new BenzC200Product();
    }

    //空调
    public function getAirCondition()
    {
        return new GreeAirCondition();
    }
}

客户端类:

//客户端测试代码
$factoryA = new AFactory();
$factoryB = new BFactory();

//A工厂制作车
$auto_carA = $factoryA->getAuto();
$auto_airA = $factoryA->getAirCondition();

//B工厂制作车
$auto_carB = $factoryB->getAuto();
$auto_airB = $factoryB->getAirCondition();

//开奥迪车+吹海尔空调
$auto_carA->dirve();
$auto_airA->blow(); //热的时候可以吹吹空调

//开奔驰车+吹格力空调;
$auto_carB->dirve();
$auto_airB->blow(); //热的时候可以吹吹空调

类图:

抽象工厂模式的组成:

  1. 抽象工厂(AbstractFactory):确定工厂的业务范围。
  2. 具体工厂(ConcreteFactory):每个具体工厂对应一个产品族。具体工厂决定生产哪个具体产品对象。
  3. 抽象产品(AbstractProduct):同一产品等级结构的抽象类。
  4. 具体产品(ConcreteProduct):可供生产的具体产品。

工厂方法模式:

  1. 一个抽象产品类,可以派生出多个具体产品类。
  2. 一个抽象工厂类,可以派生出多个具体工厂类。
  3. 每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:

  1. 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
  2. 一个抽象工厂类,可以派生出多个具体工厂类。
  3. 每个具体工厂类可以创建多个具体产品类的实例。

建造者模式(Builder)

  • 模式定义

建造者模式将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  • 问题引出

假设我们有个生产车的工厂,可以制造各种车,比如自行车、汽车、卡车等等,如果每辆车都是从头到尾按部就班地造,必然效率低下。

  • 解决办法

我们可以试着将车的组装和零部件生产分离开来:让一个类似“导演”的角色负责车子组装, 而具体造什么样的车需要什么样的零部件让具体的“构造者”去实现,“导演”知道什么样的车怎么造, 需要的零部件则让“构造者”去建造,何时完成由“导演”来控制并最终返回给客户端。

  • UML类图

  • 示例代码

Director.php

<?php
namespace DesignPatterns\Creational\Builder;

/**
 * Director 是建造者模式的一部分,它知道建造者接口并通过建造者构建复杂对象。
 *
 * 可以通过依赖注入建造者的方式构造任何复杂对象
 */
class Director
{

    /**
     * “导演”并不知道具体实现细节
     *
     * @param BuilderInterface $builder
     *
     * @return Parts\Vehicle
     */
    public function build(BuilderInterface $builder)
    {
        $builder->createVehicle();
        $builder->addDoors();
        $builder->addEngine();
        $builder->addWheel();

        return $builder->getVehicle();
    }
}

BuilderInterface.php

<?php
namespace DesignPatterns\Creational\Builder;

/**
 * 建造者接口
 */
interface BuilderInterface
{
    /**
     * @return mixed
     */
    public function createVehicle();

    /**
     * @return mixed
     */
    public function addWheel();

    /**
     * @return mixed
     */
    public function addEngine();

    /**
     * @return mixed
     */
    public function addDoors();

    /**
    * @return mixed
    */
    public function getVehicle();
}

BikeBuilder.php

<?php
namespace DesignPatterns\Creational\Builder;

/**
 * BikeBuilder用于建造自行车
 */
class BikeBuilder implements BuilderInterface
{
    /**
     * @var Parts\Bike
     */
    protected $bike;

    /**
     * {@inheritdoc}
     */
    public function addDoors()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function addEngine()
    {
        $this->bike->setPart('engine', new Parts\Engine());
    }

    /**
     * {@inheritdoc}
     */
    public function addWheel()
    {
        $this->bike->setPart('forwardWheel', new Parts\Wheel());
        $this->bike->setPart('rearWheel', new Parts\Wheel());
    }

    /**
     * {@inheritdoc}
     */
    public function createVehicle()
    {
        $this->bike = new Parts\Bike();
    }

    /**
     * {@inheritdoc}
     */
    public function getVehicle()
    {
        return $this->bike;
    }
}

CarBuilder.php

<?php
namespace DesignPatterns\Creational\Builder;

/**
 * CarBuilder用于建造汽车
 */
class CarBuilder implements BuilderInterface
{
    /**
     * @var Parts\Car
     */
    protected $car;

    /**
     * @return void
     */
    public function addDoors()
    {
        $this->car->setPart('rightdoor', new Parts\Door());
        $this->car->setPart('leftDoor', new Parts\Door());
    }

    /**
     * @return void
     */
    public function addEngine()
    {
        $this->car->setPart('engine', new Parts\Engine());
    }

    /**
     * @return void
     */
    public function addWheel()
    {
        $this->car->setPart('wheelLF', new Parts\Wheel());
        $this->car->setPart('wheelRF', new Parts\Wheel());
        $this->car->setPart('wheelLR', new Parts\Wheel());
        $this->car->setPart('wheelRR', new Parts\Wheel());
    }

    /**
     * @return void
     */
    public function createVehicle()
    {
        $this->car = new Parts\Car();
    }

    /**
     * @return Parts\Car
     */
    public function getVehicle()
    {
        return $this->car;
    }
}

Parts/Vehicle.php

<?php
namespace DesignPatterns\Creational\Builder\Parts;

/**
 * VehicleInterface是车辆接口
 */
abstract class Vehicle
{
    /**
     * @var array
     */
    protected $data;

    /**
     * @param string $key
     * @param mixed $value
     */
    public function setPart($key, $value)
    {  
        $this->data[$key] = $value;
    }
}

Parts/Bike.php

<?php
namespace DesignPatterns\Creational\Builder\Parts;

/**
 * Bike
 */
class Bike extends Vehicle
{
}

Parts/Car.php

<?php
namespace DesignPatterns\Creational\Builder\Parts;

/**
 * Car
 */
class Car extends Vehicle
{
}

Parts/Engine.php

<?php
namespace DesignPatterns\Creational\Builder\Parts;

/**
 * Engine类
 */
class Engine
{
}

Parts/Wheel.php

<?php
namespace DesignPatterns\Creational\Builder\Parts;

/**
 * Wheel类
 */
class Wheel
{
}

Parts/Door.php

<?php
namespace DesignPatterns\Creational\Builder\Parts;

/**
 * Door类
 */
class Door
{
}
  • 测试代码

Tests/DirectorTest.php

<?php
namespace DesignPatterns\Creational\Builder\Tests;

use DesignPatterns\Creational\Builder\Director;
use DesignPatterns\Creational\Builder\CarBuilder;
use DesignPatterns\Creational\Builder\BikeBuilder;
use DesignPatterns\Creational\Builder\BuilderInterface;

/**
 * DirectorTest 用于测试建造者模式
 */
class DirectorTest extends \PHPUnit_Framework_TestCase
{

    protected $director;

    protected function setUp()
    {
        $this->director = new Director();
    }

    public function getBuilder()
    {
        return array(
            array(new CarBuilder()),
            array(new BikeBuilder())
        );
    }

   /**
    * 这里我们测试建造过程,客户端并不知道具体的建造者。
    *
    * @dataProvider getBuilder
    */
    public function testBuild(BuilderInterface $builder)
    {
        $newVehicle = $this->director->build($builder);
        $this->assertInstanceOf('DesignPatterns\Creational\Builder\Parts\Vehicle', $newVehicle);
    }
}
  • 总结

建造者模式和抽象工厂模式很像,总体上,建造者模式仅仅只比抽象工厂模式多了一个“导演类”的角色。 与抽象工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂, 因此将对象的创建过程独立出来组成一个新的类 —— 导演类。 也就是说,抽像工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品; 而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将完整建造过程交付给导演类。 由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。

  • 拓展

建造者模式:将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示的设计模式。

设计场景:

  1. 有一个用户的UserInfo类,创建这个类,需要创建用户的姓名,年龄,爱好等信息,才能获得用户具体的信息结果。
  2. 创建一个UserBuilder 用户建造者类,这个类,将UserInfo复杂的创建姓名,年龄,爱好等操作封装起来,简化用户类的创建过程

这是一个用户类:

class UserInfo
{
    protected $_userName;
    protected $_userAge;
    protected $_userHobby;

    public function setUserName($userName)
    {
        $this->_userName = $userName;
    }

    public function setUserAge($userAge)
    {
        $this->_userAge = $userAge;
    }

    public function setUserHobby($userHobby)
    {
        $this->_userHobby = $userHobby;
    }

    public function getPeopleInfo()
    {
        echo  "<br>这个人的名字是:" . $this->_userName . "<br>年龄为:" . $this->_userAge . "<br>爱好:" . $this->_userHobby;
    }
}

这时候我们要获取一个用户的信息,过程是这样的:

$modelUser = new UserInfo();
$modelUser->setUserName('松涛');
$modelUser->setUserAge('23');
$modelUser->setUserHobby('推理小说');
$modelUser->getPeopleInfo();

得到的结果是:

这个人的名字是:松涛
年龄为:23
爱好:推理小说

这时候创建一个用户建造者类(这个建造者类兼容了导演类,这里没有依赖注入):

class UserBuilder
{
    protected $_obj;

    public function __construct()
    {
        $this->_obj = new UserInfo();
    }

    public function builderPeople($userInfo)
    {
        $this->_obj->setUserName($userInfo['userName']);
        $this->_obj->setUserAge($userInfo['userAge']);
        $this->_obj->setUserHobby($userInfo['userHobby']);
    }

    public function getBuliderPeopleInfo()
    {
        $this->_obj->getPeopleInfo();
    }
}

这个是将复杂的创建过程封装在了builderPeople这个方法里面。 接下来是创建对象:

$userArr = array(
    'userName' => '松涛',
    'userAge' => '23',
    'userHobby' => '推理小说'
);

$modelUserBuilder = new UserBuilder();
$modelUserBuilder->builderPeople($userArr);
$modelUserBuilder->getBuliderPeopleInfo();

优点:建造者模式可以很好的将一个对象的实现与相关的“业务”逻辑分离开来, 从而可以在不改变事件逻辑的前提下,使增加(或改变)实现变得非常容易。

缺点:建造者接口的修改会导致所有执行类的修改。

以下情况应当使用建造者模式:

  1. 需要生成的产品对象有复杂的内部结构。
  2. 需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
  3. 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。

根据以上例子,我们可以得到建造者模式的效果:

  1. 建造者模式的使用使得产品的内部表象可以独立的变化。使用建造者模式可以使客户端不必知道产品内部组成的细节。
  2. 每一个Builder都相对独立,而与其它的Builder(独立控制逻辑)无关。
  3. 模式所建造的最终产品更易于控制。

建造者模式与工厂模式的区别:

我们可以看到,建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个“导演类”的角色。在建造者模式的类图中, 假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。

与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。 也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中, 建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品, 然后将组建好的产品交付给客户端。

单例模式(Singleton)

  • 模式定义

简单说来,单例模式的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个, 同时这个类还必须提供一个访问该类的全局访问点。 常见使用实例:数据库连接器;日志记录器(如果有多种用途使用多例模式);锁定文件。

  • UML类图

  • 示例代码

Singleton.php

<?php
namespace DesignPatterns\Creational\Singleton;

/**
 * Singleton类
 */
class Singleton
{
    /**
     * @var Singleton reference to singleton instance
     */
    private static $instance;
    
    /**
     * 通过延迟加载(用到时才加载)获取实例
     *
     * @return self
     */
    public static function getInstance()
    {
        if (null === static::$instance) {
            static::$instance = new static;
        }

        return static::$instance;
    }

    /**
     * 构造函数私有,不允许在外部实例化
     *
     */
    private function __construct()
    {
    }

    /**
     * 防止对象实例被克隆
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * 防止被反序列化
     *
     * @return void
     */
    private function __wakeup()
    {
    }
}
  • 测试代码

Tests/SingletonTest.php

<?php
namespace DesignPatterns\Creational\Singleton\Tests;

use DesignPatterns\Creational\Singleton\Singleton;

/**
 * SingletonTest用于测试单例模式
 */
class SingletonTest extends \PHPUnit_Framework_TestCase
{

    public function testUniqueness()
    {
        $firstCall = Singleton::getInstance();
        $this->assertInstanceOf('DesignPatterns\Creational\Singleton\Singleton', $firstCall);
        $secondCall = Singleton::getInstance();
        $this->assertSame($firstCall, $secondCall);
    }

    public function testNoConstructor()
    {
        $obj = Singleton::getInstance();

        $refl = new \ReflectionObject($obj);
        $meth = $refl->getMethod('__construct');
        $this->assertTrue($meth->isPrivate());
    }
}
  • 拓展

单例模式(Singleton Pattern 单件模式或单元素模式)

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式是一种常见的设计模式,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、数据库操作、显卡的驱动程序常被设计成单例。

单例模式有以下3个特点:

  1. 只能有一个实例。
  2. 必须自行创建这个实例。
  3. 必须给其他对象提供这一实例。

单例模式的技巧:

  1. 利用$_instance私有变量来保存类的唯一实例化对象;
  2. 设计一个getInstance对外公开的函数,可以获取类唯一实例;
  3. 防止用户用new实例化,和克隆,构造两个__construct、__clone私有函数;

内在表现为:

  • $_instance 必须声明为静态的私有变量
  • 构造函数和克隆函数必须声明为私有的,这是为了防止外部程序 new 类从而失去单例模式的意义
  • getInstance()方法必须声明为公有的,必须调用此方法以返回唯一实例的一个引用
  • ::操作符只能访问静态变量或静态函数
  • PHP的单例模式是相对而言的,因为PHP的解释运行机制使得每个PHP页面被解释执行后,所有的相关资源都会被回收。

那么为什么要使用PHP单例模式?

单例模式一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为, 使用单例模式可以避免大量的new操作。因为每一次new操作都会消耗系统和内存的资源。使用单例模式生成一个对象后,该对象可以被其它众多对象所使用。

在以往的项目开发中,没使用单例模式前的情况如下:

//初始化一个数据库句柄
$db = new DB(...);
//比如有个应用场景是添加一条用户信息
$db->addUserInfo();
......
//然而我们要在另一地方使用这个用户信息,这时要用到数据库句柄资源,可能会这么做,就会导致新建数据库句柄连接数据库,造成重复
......
function test() {
    $db = new DB(...);
    $db->getUserInfo();
}

// 有些朋友也许会说,可以直接使用global关键字!
global $db;

的确global可以解决问题,也起到单例模式的作用,但在OOP中,我们拒绝这种编码。因为global存在安全隐患(全局变量不受保护的本质)。

全局变量是面向对象程序员遇到的引发BUG的主要原因之一。这是因为全局变量将类捆绑于特定的环境,破坏了封装。 如果新的应用程序无法保证一开始就定义了相同的全局变量,那么一个依赖于全局变量的类就无法从一个应用程序中提取出来并应用到新应用程序中。

确切的讲,单例模式恰恰是对全局变量的一种改进,避免那些存储唯一实例的全局变量污染命名空间。你无法用错误类型的数据覆写一个单例。 这种保护在不支持命名空间的PHP版本里尤其重要。因为在PHP中命名冲突会在编译时被捕获,并使脚本停止运行。

单例模式的优缺点:

优点:

  1. 改进系统的设计

  2. 是对全局变量的一种改进

缺点:

  1. 难于调试

  2. 隐藏的依赖关系

  3. 无法用错误类型的数据覆写一个单例

看一个单例示例:

class single
{
    // 私有静态属性用以保存对象
    private static $_instance =null;
    
    private $config;
    
    // 私有属性的构造方法 防止被 new
    private function __construct($config)
    {
        $this->config = $config;
    }
    
    // 私有属性的克隆方法 防止被克隆
    private function __clone(){}
    
    // 静态方法 用以实例化调用
    public static function getInstance($config)
    {
        if (!self::$_instance instanceof self) {
            self::$_instance= new self($config);
        }
        
        return self::$_instance;   
    }
    
    public function getConfig()
    {
        return $this->config;
    }
}

$single_case = single::getInstance('sam');
echo $single_case->getConfig();  // 输出sam
$single_case2 = single::getInstance('jack');
echo $single_case2->getConfig();  // 输出sam

可以发现第二次赋值时没有成功,这就是单例的作用,只有一个实例。

单例模式分3种:懒汉式单例、饿汉式单例、登记式单例。不过PHP不支持饿汉式单例。

懒汉式单例:默认加载的时候不着急实例化,在需要用这个实例的时候才实例化,延时加载。

上面那个就是懒汉式单例,下面再看一个:

class User 
{
    // 静态变量保存全局实例
    private static $_instance = null;
    
    // 私有构造函数,防止外界实例化对象
    private function __construct() {
    }
    
    // 私有克隆函数,防止外办克隆对象
    private function __clone() {
    }
    
    // 静态方法,单例统一访问入口
    public static function getInstance() 
    {
        if (!self::$_instance instanceof self) {
            self::$_instance = new self ();
        }
        
        return self::$_instance;
    }
    
    public function getName() 
    {
        echo 'hello world!';
    }
}

饿汉式:在实例使用之前,不管你用不用,我都先new出来再说,避免了线程安全问题。 java语言支持。

// 下面代码会报错  Fatal error: Constant expression contains invalid operations
// 属性声明是由关键字 public,protected 或者 private 开头,然后跟一个普通的变量声明来组成。属性中的变量可以初始化,
// 但是初始化的值必须是常数,这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。


// 错误代码就不写了,以免误导!(就是在声明private static $_instance的时候就new self()实例化,PHP不支持这种写法,切记!)
    


// 它是在类加载的时候就立即初始化,并且创建单例对象

// 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好

// 缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间,浪费了内存

// 绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题

再看一个,数据库设计,我们发送一次请求,可能会需要访问不同的表,那么如果每次访问都 new 一个实例, 那必然会造成资源的浪费,所以使用单例模式,可以很好的节省资源。

class DataBase
{
    /**
     * 静态成品变量,保存全局实例
     */
    private static $_instance = null;

    /**
     *  测试变量,存储日志信息
     */
    private static $_msg = null;

    /**
     * 私有构造方法,防止外界实例化对象
     */
    private function __construct()
    {
        $connect = "连接数据库操作";
    }

    /**
     * 私有化克隆方法,防止外键克隆对象
     */
    private function __clone()
    {
    }

    /**
     * 静态方法,外界获取实例的唯一接口
     * @return Object 返回对象唯一实例
     */
    public static function getInstance()
    {
        if (!self::$_instance){
            self::$_instance = new DataBase();
            self::$_msg = "这是一个新对象" . "<br>";
        }else{
            self::$_msg = "这个是一个旧的对象" . "<br>";
        }

        return self::$_instance;
    }

    public function log()
    {
        echo self::$_msg;
    }
}

// 客户端测试代码
$dbA = DataBase::getInstance();
$dbA->log();  // 输出 这是一个新对象

$dbB = DataBase::getInstance();
$dbB->log();  // 输出 这个是一个旧的对象

$dbC = DataBase::getInstance();
$dbC->log();  // 输出 这个是一个旧的对象
  • 总结
  1. 单例模式 —— 单例模式的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。
  2. 多例模式 —— 除了单例模式的作用外,容器提供多个单例。
  3. 服务定位器模式 —— 把服务在服务定位器中注册好,使用时可以由服务定位器或返回新的实例、或返回实例化好的实例。
  4. 注册模式 —— 把实例化好的类放在一个注册的容器中,使用时从容器中获取。
  5. 享元模式 —— 没有注册这一步,使用时直接从Factory类获取,Factory类决定怎么创建对象和返回。
  6. 对象池模式 —— 对象池是一组已经初始化过且可以直接使用的对象集合,使用对象时可以从对象池中获取对象并进行操作,在不需要时归还给对象池。

多例模式(Multiton)

  • 模式定义

多例模式和单例模式类似,但可以返回多个实例。 比如我们有多个数据库连接,MySQL、SQLite、Postgres,又或者我们有多个日志记录器,分别用于记录调试信息和错误信息,这些都可以使用多例模式实现。

  • UML类图

  • 示例代码

Multiton.php

<?php
namespace DesignPatterns\Creational\Multiton;

/**
 * Multiton类
 */
class Multiton
{
    /**
     *
     * 第一个实例
     */
    const INSTANCE_1 = '1';

    /**
     *
     * 第二个实例
     */
    const INSTANCE_2 = '2';

    /**
     * 实例数组
     *
     * @var array
     */
    private static $instances = array();

    /**
     * 构造函数是私有的,不能从外部进行实例化
     *
     */
    private function __construct()
    {
    }

    /**
     * 通过指定名称返回实例(使用到该实例的时候才会实例化)
     *
     * @param string $instanceName
     *
     * @return Multiton
     */
    public static function getInstance($instanceName)
    {
        if (!array_key_exists($instanceName, self::$instances)) {
            self::$instances[$instanceName] = new self();
        }

        return self::$instances[$instanceName];
    }

    /**
     * 防止实例从外部被克隆
     *
     * @return void
     */
    private function __clone()
    {
    }

    /**
     * 防止实例从外部反序列化
     *
     * @return void
     */
    private function __wakeup()
    {
    }
}
  • 使用示例
$instance_one = Multiton::getInstance(Multiton::INSTANCE_1);
  • 总结
  1. 多例模式 —— 除了单例模式的作用外,容器提供多个单例。
  2. 单例模式 —— 单例模式的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。

原型模式(Prototype)

  • 模式定义

通过创建原型使用克隆方法实现对象创建而不是使用标准的 new 方式。

  • UML类图

  • 示例代码

BookPrototype.php

<?php
namespace DesignPatterns\Creational\Prototype;

/**
 * BookPrototype类
 */
abstract class BookPrototype
{
    /**
     * @var string
     */
    protected $title;

    /**
     * @var string
     */
    protected $category;

    /**
     * @abstract
     * @return void
     */
    abstract public function __clone();

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

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

BarBookPrototype.php

<?php
namespace DesignPatterns\Creational\Prototype;

/**
 * BarBookPrototype类
 */
class BarBookPrototype extends BookPrototype
{
    /**
     * @var string
     */
    protected $category = 'Bar';

    /**
     * empty clone
     */
    public function __clone()
    {
    }
}

FooBookPrototype.php

<?php
namespace DesignPatterns\Creational\Prototype;

/**
 * FooBookPrototype类
 */
class FooBookPrototype extends BookPrototype
{
    protected $category = 'Foo';

    /**
     * empty clone
     */
    public function __clone()
    {
    }
}
  • 测试代码

Tests/PrototypeTest.php

<?php
namespace DesignPatterns\Creational\Prototype\Tests;

use DesignPatterns\Creational\Prototype\BookPrototype;
use DesignPatterns\Creational\Prototype\FooBookPrototype;
use DesignPatterns\Creational\Prototype\BarBookPrototype;

/**
 * PrototypeTest tests the prototype pattern
 */
class PrototypeTest extends \PHPUnit_Framework_TestCase
{

     public function getPrototype(){
         return array(
             array(new FooBookPrototype()),
             array(new BarBookPrototype())
         );
     }

     /**
      * @dataProvider getPrototype
      */
     public function testCreation(BookPrototype $prototype)
     {
         $book = clone $prototype;
         $book->setTitle($book->getCategory().' Book');
         $this->assertInstanceOf('DesignPatterns\Creational\Prototype\BookPrototype', $book);
     }
}
  • 总结

原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是用对象内部提供的克隆方法, 通过该方法返回一个对象的副本,这种创建对象的方式,相比我们之前说的几类创建型模式还是有区别的, 之前的讲述的工厂方法模式与抽象工厂都是通过工厂封装具体的 new 操作的过程,返回一个新的对象, 有的时候我们通过这样的创建工厂创建对象不值得,特别是以下的几个场景,可能使用原型模式更简单、效率更高:

  1. 如果说我们的对象类型不是刚开始就能确定,而是在运行时确定的话,那么我们通过这个类型的对象克隆出一个新的类型更容易。
  2. 有的时候我们可能在实际的项目中需要一个对象在某个状态下的副本,这个前提很重要,这点怎么理解呢, 例如有的时候我们需要对比一个对象经过处理后的状态和处理前的状态是否发生过改变,可能我们就需要在执行某段处理之前, 克隆这个对象此时状态的副本,然后等执行后的状态进行相应的对比,这样的应用在项目中也是经常会出现的。
  3. 当我们处理的对象比较简单,并且对象之间的区别很小,可能只是很固定的几个属性不同的时候,使用原型模式更合适。
  • 拓展

原型设计模式: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型设计模式简单的来说,就是不去创建新的对象进而保留原型的一种设计模式。

interface Prototype {
    public function copy();
}

原型类:

class PrototypeDemo implements Prototype
{
    private $_name;

    public function __construct($name)
    {
        // 这里可能是复杂的逻辑
        $this->_name = $name;
    }

    public function getMul()
    {
        return $this->_name * $this->_name;
    }

    public function copy()
    {
        // 克隆后的逻辑
        $this->_name ++;
        return clone $this;
    }
}

客户类:  // 输入10

// 客户类
class Client
{
    public function main()
    {
        $pro1 = new PrototypeDemo('10');
        echo $pro1->getMul();

        echo "<br>";

        $pro2 = $pro1->copy();
        echo $pro2->getMul();
    }
}

调用客户端:

$obj = new Client();
$obj->main();

输出结果:

100
121

显示传入10 然后 getMul 方法做 乘方运算 得到 10*10 = 100

然后克隆对象, 原型类的 copy 方法 被执行时, $_name 自增 1(克隆的逻辑) 后再去 做乘法运算,得到11*11 = 121

缺点:

原型设计模式最主要的缺点就是这个克隆方法需要对类的功能进行检测,这对于全新的类来说较容易,但对已有的类进行改造时将不是件容易的事情;

对象池模式(Object Pool)

  • 模式定义

对象池(也称为资源池)被用来管理对象缓存。对象池是一组已经初始化过且可以直接使用的对象集合,用户在使用对象时可以从对象池中获取对象, 对其进行操作处理,并在不需要时归还给对象池而非销毁它。 若对象初始化、实例化的代价高,且需要经常实例化,但每次实例化的数量较少的情况下,使用对象池可以获得显著的性能提升。 常见的使用对象池模式的技术包括线程池、数据库连接池、任务队列池、图片资源对象池等。 当然,如果要实例化的对象较小,不需要多少资源开销,就没有必要使用对象池模式了,这非但不会提升性能,反而浪费内存空间,甚至降低性能。

  • UML类图

  • 示例代码

Pool.php

<?php
namespace DesignPatterns\Creational\Pool;

class Pool
{
    private $instances = array();
    private $class;

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

    public function get()
    {
        if (count($this->instances) > 0) {
            return array_pop($this->instances);
        }

        return new $this->class();
    }

    public function dispose($instance)
    {
        $this->instances[] = $instance;
    }
}

Processor.php

<?php
namespace DesignPatterns\Creational\Pool;

class Processor
{
    private $pool;
    private $processing = 0;
    private $maxProcesses = 3;
    private $waitingQueue = [];

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    public function process($image)
    {
        if ($this->processing++ < $this->maxProcesses) {
            $this->createWorker($image);
        } else {
            $this->pushToWaitingQueue($image);
        }
    }

    private function createWorker($image)
    {
        $worker = $this->pool->get();
        $worker->run($image, array($this, 'processDone'));
    }

    public function processDone($worker)
    {
        $this->processing--;
        $this->pool->dispose($worker);

        if (count($this->waitingQueue) > 0) {
            $this->createWorker($this->popFromWaitingQueue());
        }
    }

    private function pushToWaitingQueue($image)
    {
        $this->waitingQueue[] = $image;
    }

    private function popFromWaitingQueue()
    {
        return array_pop($this->waitingQueue);
    }
}

Worker.php

<?php
namespace DesignPatterns\Creational\Pool;

class Worker
{
    public function __construct()
    {
        // let's say that constuctor does really expensive work...
        // for example creates "thread"
    }

    public function run($image, array $callback)
    {
        // do something with $image...
        // and when it's done, execute callback
        call_user_func($callback, $this);
    }
}
  • 测试代码

Tests/TestWorker.php

<?php
namespace DesignPatterns\Creational\Pool\Tests;

class TestWorker
{
    public $id = 1;
}

Tests/PoolTest.php

<?php
namespace DesignPatterns\Creational\Pool\Tests;

use DesignPatterns\Creational\Pool\Pool;

class PoolTest extends \PHPUnit_Framework_TestCase
{
    public function testPool()
    {
        $pool = new Pool('DesignPatterns\Creational\Pool\Tests\TestWorker');
        $worker = $pool->get();

        $this->assertEquals(1, $worker->id);

        $worker->id = 5;
        $pool->dispose($worker);

        $this->assertEquals(5, $pool->get()->id);
        $this->assertEquals(1, $pool->get()->id);
    }
}

第2个测试:

Tests/ObjectPoolTest.php

<?php
namespace DesignPatterns\Creational\Pool\Tests;

use DesignPatterns\Creational\Pool\Pool;
use DesignPatterns\Creational\Pool\Processor;

class PoolTest extends \PHPUnit_Framework_TestCase
{
    public function testPool()
    {
        $pool = new Pool('DesignPatterns\Creational\Pool\Worker');
        $client = new Processor($pool);
        
        $image = new \PDO("mysql:host=127.0.0.1;port=3306;dbname=db_test", "test", "123456");
        $client->process($image);
        
        $image2 = new \PDO("mysql:host=127.0.0.2;port=3306;dbname=db_test", "test", "123456");
        $client->process($image2);
    }
}
  • 总结

与类似接口的区别:

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






参考资料

PHP 常用设计模式 https://ibaiyang.github.io/blog/php/2019/07/30/PHP-常用设计模式.html

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

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

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

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设计模式(一)简单工厂模式 (Simple Factory For PHP) https://segmentfault.com/a/1190000016635395

PHP设计模式(二)工厂方法模式(Factory Method) https://segmentfault.com/a/1190000016646401

PHP设计模式(三)抽象工厂模式(Abstract Factory) https://segmentfault.com/a/1190000016659904

PHP设计模式(四)单例模式(Singleton) https://segmentfault.com/a/1190000016670292

PHP设计模式(五)建造者模式(Builder For PHP) https://www.cnblogs.com/wilburxu/p/6179363.html

PHP设计模式(六)原型模式(Prototype For PHP) https://www.cnblogs.com/wilburxu/p/6188437.html

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

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

工厂方法模式(Factory Method) https://learnku.com/docs/php-design-patterns/2018/FactoryMethod/1489

抽象工厂模式(Abstract Factory) https://www.jianshu.com/p/7deb64f902db

PHP设计模式-单例模式 https://www.cnblogs.com/yangjinjin/archive/2013/01/31/2887492.html

PHP中“简单工厂模式”实例讲解 https://www.cnblogs.com/hongfei/archive/2012/07/07/2580776.html


返回