设计模式简介及PHP的35种设计模式(上)

什么是模式??

        有经验的00开发者(以及其他的软件开发者)建立了既有通用原则又有惯用方案的指令系统来指导他们编制软件。如果以结构化形式对这些问题、解决方案和命名进行描述使其系统化,那么这些原则和习惯用法就可以称为模式。例如,下面是一个模式样例:
        模式名称:        信息专家(Information Expert)
        问题:               给对象分配职责的基本原则是什么?
        解决方案:        给类分配一种职责,使其具有完成该职责所需要的信息。
        在OO设计中,模式是对问题和解决方案的已命名描述,它可以用于新的语境。理想情况下,
模式为在变化环境中如何运用和权衡其解决方案给出建议。对于特定问题,可以应用许多模式
为对象分配职责。
        简单地讲,好的模式是成对的问题/解决方案,并且具有广为人知的名称,它能用于新
的语境中,同时对新情况下的应用、权衡、实现、变化等给出了建议。

模式具有名称一重要!

        软件开发是一个年轻领域。年轻领域中的原则缺乏大家广泛认可的名称,这为沟通和培训带来了困难。模式具有名称,例如信息专家和抽象工厂。对模式、设计思想或原则命名具有以下好处:
        ·它支持将概念条理化地组织为我们的理解和记忆。
        ·它便于沟通。
        模式被命名并且广泛发布后(我们都同意使用这个名字),我们就可以在讨论复杂设计思想时使用简语(或简图),这可以发挥抽象的优势。看看下面两个软件开发者之间使用模式名称的讨论:
        Jill:“嗨!Jack,对于这个持久性子系统,让我截图时论一下外观(Fagade)的服务。我们将对Mappers使用抽象工厂,对延迟具体化使用代理(Proxy)。”

 Jack:“你刚才究竟说了什么呀!
Jill:“喂!看这儿……”

‘新模式”是一种矛盾修饰法


        新模式如果描述的是新思想,则应当被认为是一种矛盾修饰法。术语“模式”的真实含义是长期重复的事物。设计模式的要点并不是要表达新的设计思想。恰恰相反,优秀模式的意图是将已有的经过验证的知识、惯用法和原则汇编起来;磨砺的越多、越悠久、使用得越广泛,这样的模式就越优秀。
        因此,GRASP模式陈述的并不是新思想,它们只是将为广泛使用的基本原则命名并其汇总
起来。对于OO设计专家而言,GRASP模式(其思想而非名称)应作为其基础和熟练掌握的原则。
这是最关键的! 

GOF关于设计模式的著作

        Kent Beck(也因极限编程而闻名)在20世纪80年代中期首先提出了软件命名模式的思想。然而,在模式、00设计和软件设计书籍的历史上,1994年是一个重要的里程碑。极为畅销并产生巨大影响Design Patterns一书[GHJV95]就是在这一年出版的,它的作者是Gamma、Helm、Johnson和Vlissides。这本书被认为是设计模式的“圣经”,它描述了23个00设计模式,并且命名为策略(Strategy)、适配器(Adaptor)等。因此,这四个人提出的模式被称为GOF(或“四人帮”)设计模式。

        然而,Design Patterns一书不是入门类书籍,读者要有一定的OO设计和编程知识,而且书
中的大部分代码是用C++编写的。

GRASP是一组模式或原则吗


        GRASP定义了9个基本O0设计原则或基本设计构件。有些人会问,“难道GRASP描述的是原则而不是模式吗?”《设计模式》一书的序言给出了答案:
                某人的模式是其他人的原始构造块。
        本书并不注重模式的标识和描述,而更关注模式的实用价值,即模式是一种优秀的学习工具,可以用来命名、表示和记忆那些基本和经典的设计思想。

 
一些GRASP原则是对其他设计模式的归纳

        上述适配器模式的使用可以视为某些GRASP构造模块的特化:
        适配器支持防止变异,因为它通过应用了接口和多态的间接对象,改变了外部接口或第三方软件包。


问题是什么?模式过多!


        Pattern Almanac 2000[Rising00]列出了大约500种设计模式,并且此后又发布了数百种模式。如此之多的模式,使求知欲望强烈的程序员都没有时间去实际编程了。

解决方案:找到根本原则

        是的,对于有经验的设计者来说,详细了解和记住50种以上最重要的设计模式非常重要,但是很少有人能够学习或记住1000个模式,因此需要对这些过量的模式进行有效分类。
        但是,现在有好消息了:大多数设计模式可以被视为少数几个GRASP基本原则的特化。这样除了能够有助于加速对详细设计模式的学习之外,而且发现其根本的基本主题(防止变异、多态、间接性等)更为有效,它能够帮助我们透过大量细节发现应用设计技术的本质。


示例:适配器和GRASP


        图说明了我的观点,可以用GRASP原则的基本列表来分析详细的设计模式。UML的泛化关系可以用来指出概念上的连接。目前,这种思想可能过于理论化。但这是必要的,当你花费了数年应用那些大量的设计模式后,你会越来越体会到本质主题的重要性,而极为细节化的适配器或策略等任何模式都将变得次要。

 

 适配器与某些GRASP核心原则的关系

PHP的35种设计模式

模式

三个大类。

1. 创建型

在软件工程中,创建型设计模式是处理对象创建机制的设计模式,试图以适当的方式来创建对象。对象创建的基本形式可能会带来设计问题,亦或增加了设计的复杂度。创建型设计模式通过控制这个对象的创建方式来解决此问题。

2. 结构型

在软件工程中,结构型设计模式是通过识别实体之间关系来简化设计的设计模式。

3. 行为型

在软件工程中,行为设计模式是识别对象之间的通用通信模式并实现这些模式的设计模式。 通过这样做,这些模式增加了执行此通信的灵活性。

创建模式

1、抽象工厂模式(Abstract Factory)

目的

在不指定具体类的情况下创建一系列相关或依赖对象。 通常创建的类都实现相同的接口。 抽象工厂的客户并不关心这些对象是如何创建的,它只是知道它们是如何一起运行的。

UML图

代码

Product.php

<?php

namespace DesignPatterns\Creational\AbstractFactory;
interface Product
{
    public function calculatePrice(): int;
}

ShippableProduct.php

<?php

namespace DesignPatterns\Creational\AbstractFactory;
class ShippableProduct implements Product
{
    /**
     * @var float
     */
    private $productPrice;
    /**
     * @var float
     */
    private $shippingCosts;
    public function __construct(int $productPrice, int $shippingCosts)
    {
        $this->productPrice = $productPrice;
        $this->shippingCosts = $shippingCosts;
    }
    public function calculatePrice(): int
    {
        return $this->productPrice + $this->shippingCosts;
    }
}

DigitalProduct.php

<?php

namespace DesignPatterns\Creational\AbstractFactory;
class DigitalProduct implements Product
{
    /**
     * @var int
     */
    private $price;
    public function __construct(int $price)
    {
        $this->price = $price;
    }
    public function calculatePrice(): int
    {
        return $this->price;
    }
}

ProductFactory.php

<?php

namespace DesignPatterns\Creational\AbstractFactory;
class ProductFactory
{
    const SHIPPING_COSTS = 50;
    public function createShippableProduct(int $price): Product
    {
        return new ShippableProduct($price, self::SHIPPING_COSTS);
    }
    public function createDigitalProduct(int $price): Product
    {
        return new DigitalProduct($price);
    }
}
Test

Tests/AbstractFactoryTest.php

<?php

namespace DesignPatterns\Creational\AbstractFactory\Tests;
use DesignPatterns\Creational\AbstractFactory\DigitalProduct;
use DesignPatterns\Creational\AbstractFactory\ProductFactory;
use DesignPatterns\Creational\AbstractFactory\ShippableProduct;
use PHPUnit\Framework\TestCase;
class AbstractFactoryTest extends TestCase
{
    public function testCanCreateDigitalProduct()
    {
        $factory = new ProductFactory();
        $product = $factory->createDigitalProduct(150);
        $this->assertInstanceOf(DigitalProduct::class, $product);
    }
    public function testCanCreateShippableProduct()
    {
        $factory = new ProductFactory();
        $product = $factory->createShippableProduct(150);
        $this->assertInstanceOf(ShippableProduct::class, $product);
    }
    public function testCanCalculatePriceForDigitalProduct()
    {
        $factory = new ProductFactory();
        $product = $factory->createDigitalProduct(150);
        $this->assertEquals(150, $product->calculatePrice());
    }
    public function testCanCalculatePriceForShippableProduct()
    {
        $factory = new ProductFactory();
        $product = $factory->createShippableProduct(150);
        $this->assertEquals(200, $product->calculatePrice());
    }
}

2、建造者模式(Builder)

目的

建造者是创建一个复杂对象的一部分接口。

有时候,如果建造者对他所创建的东西拥有较好的知识储备,这个接口就可能成为一个有默认方法的抽象类(又称为适配器)。

如果对象有复杂的继承树,那么对于建造者来说,有一个复杂继承树也是符合逻辑的。

注意:建造者通常有一个「流式接口」,例如 PHPUnit 模拟生成器。

UML

代码

Director.php

<?php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

/**
 * Director 类是建造者模式的一部分。 它可以实现建造者模式的接口
 * 并在构建器的帮助下构建一个复杂的对象
 *
 * 您也可以注入许多构建器而不是构建更复杂的对象
 */
class Director
{
    public function build(BuilderInterface $builder): Vehicle
    {
        $builder->createVehicle();
        $builder->addDoors();
        $builder->addEngine();
        $builder->addWheel();

        return $builder->getVehicle();
    }
}

BuilderInterface.php

<?php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

interface BuilderInterface
{
    public function createVehicle();

    public function addWheel();

    public function addEngine();

    public function addDoors();

    public function getVehicle(): Vehicle;
}

TruckBuilder.php

<?php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

class TruckBuilder implements BuilderInterface
{
    /**
    * @var Parts\Truck
    */
    private $truck;

    public function addDoors()
    {
        $this->truck->setPart('rightDoor', new Parts\Door());
        $this->truck->setPart('leftDoor', new Parts\Door());
    }

    public function addEngine()
    {
        $this->truck->setPart('truckEngine', new Parts\Engine());
    }

    public function addWheel()
    {
        $this->truck->setPart('wheel1', new Parts\Wheel());
        $this->truck->setPart('wheel2', new Parts\Wheel());
        $this->truck->setPart('wheel3', new Parts\Wheel());
        $this->truck->setPart('wheel4', new Parts\Wheel());
        $this->truck->setPart('wheel5', new Parts\Wheel());
        $this->truck->setPart('wheel6', new Parts\Wheel());
    }

    public function createVehicle()
    {
        $this->truck = new Parts\Truck();
    }

    public function getVehicle(): Vehicle
    {
        return $this->truck;
    }
}

 CarBuilder.php

<?php

namespace DesignPatterns\Creational\Builder;

use DesignPatterns\Creational\Builder\Parts\Vehicle;

class CarBuilder implements BuilderInterface
{
    /**
    * @var Parts\Car
    */
    private $car;

    public function addDoors()
    {
        $this->car->setPart('rightDoor', new Parts\Door());
        $this->car->setPart('leftDoor', new Parts\Door());
        $this->car->setPart('trunkLid', new Parts\Door());
    }

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

    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());
    }

    public function createVehicle()
    {
        $this->car = new Parts\Car();
    }

    public function getVehicle(): Vehicle
    {
        return $this->car;
    }
}

Parts/Vehicle.php

<?php

namespace DesignPatterns\Creational\Builder\Parts;

abstract class Vehicle
{
    /**
    * @var object[]
    */
    private $data = [];

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

Parts/Truck.php

<?php

namespace DesignPatterns\Creational\Builder\Parts;

class Truck extends Vehicle
{
}

Parts/Car.php

<?php

namespace DesignPatterns\Creational\Builder\Parts;

class Car extends Vehicle
{
}

Parts/Engine.php

<?php

namespace DesignPatterns\Creational\Builder\Parts;

class Engine
{
}

Parts/Wheel.php

<?php

namespace DesignPatterns\Creational\Builder\Parts;

class Wheel
{
}

Parts/Door.php

<?php

namespace DesignPatterns\Creational\Builder\Parts;

class Door
{
}
测试

Tests/DirectorTest.php

<?php

namespace DesignPatterns\Creational\Builder\Tests;

use DesignPatterns\Creational\Builder\Parts\Car;
use DesignPatterns\Creational\Builder\Parts\Truck;
use DesignPatterns\Creational\Builder\TruckBuilder;
use DesignPatterns\Creational\Builder\CarBuilder;
use DesignPatterns\Creational\Builder\Director;
use PHPUnit\Framework\TestCase;

class DirectorTest extends TestCase
{
    public function testCanBuildTruck()
    {
        $truckBuilder = new TruckBuilder();
        $newVehicle = (new Director())->build($truckBuilder);

        $this->assertInstanceOf(Truck::class, $newVehicle);
    }

    public function testCanBuildCar()
    {
        $carBuilder = new CarBuilder();
        $newVehicle = (new Director())->build($carBuilder);

        $this->assertInstanceOf(Car::class, $newVehicle);
    }
}

3、工厂方法模式(Factory Method)

目的

对比简单工厂模式的优点是,您可以将其子类用不同的方法来创建一个对象。

举一个简单的例子,这个抽象类可能只是一个接口。

这种模式是「真正」的设计模式, 因为他实现了 S.O.L.I.D 原则中「D」的 「依赖倒置」。

这意味着工厂方法模式取决于抽象类,而不是具体的类。 这是与简单工厂模式和静态工厂模式相比的优势。

UML 图

代码

Logger.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;
interface Logger
{
    public function log(string $message);
}

StdoutLogger.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;
class StdoutLogger implements Logger
{
    public function log(string $message)
    {
        echo $message;
    }
}

FileLogger.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;
class FileLogger implements Logger
{
    /**
     * @var string
     */
    private $filePath;
    public function __construct(string $filePath)
    {
        $this->filePath = $filePath;
    }
    public function log(string $message)
    {
        file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
    }
}

LoggerFactory.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;
interface LoggerFactory
{
    public function createLogger(): Logger;
}

StdoutLoggerFactory.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;
class StdoutLoggerFactory implements LoggerFactory
{
    public function createLogger(): Logger
    {
        return new StdoutLogger();
    }
}

FileLoggerFactory.php

<?php
namespace DesignPatterns\Creational\FactoryMethod;
class FileLoggerFactory implements LoggerFactory
{
    /**
     * @var string
     */
    private $filePath;
    public function __construct(string $filePath)
    {
        $this->filePath = $filePath;
    }
    public function createLogger(): Logger
    {
        return new FileLogger($this->filePath);
    }
}
测试

Tests/FactoryMethodTest.php

<?php
namespace DesignPatterns\Creational\FactoryMethod\Tests;
use DesignPatterns\Creational\FactoryMethod\FileLogger;
use DesignPatterns\Creational\FactoryMethod\FileLoggerFactory;
use DesignPatterns\Creational\FactoryMethod\StdoutLogger;
use DesignPatterns\Creational\FactoryMethod\StdoutLoggerFactory;
use PHPUnit\Framework\TestCase;
class FactoryMethodTest extends TestCase
{
    public function testCanCreateStdoutLogging()
    {
        $loggerFactory = new StdoutLoggerFactory();
        $logger = $loggerFactory->createLogger();
        $this->assertInstanceOf(StdoutLogger::class, $logger);
    }
    public function testCanCreateFileLogging()
    {
        $loggerFactory = new FileLoggerFactory(sys_get_temp_dir());
        $logger = $loggerFactory->createLogger();
        $this->assertInstanceOf(FileLogger::class, $logger);
    }
}

4、多例模式(Multiton)

目的

多例模式是指存在一个类有多个相同实例,而且该实例都是该类本身。这个类叫做多例类。 多例模式的特点是:

  1. 多例类可以有多个实例。
  2. 多例类必须自己创建、管理自己的实例,并向外界提供自己的实例。

多例模式实际上就是单例模式的推广。

举例
  • 2 个数据库连接器,比如一个是 MySQL ,另一个是 SQLite
  • 多个记录器(一个用于记录调试消息,一个用于记录错误)
UML 图 

 代码

 Multiton.php

<?php

namespace DesignPatterns\Creational\Multiton;

final class Multiton
{
    const INSTANCE_1 = '1';
    const INSTANCE_2 = '2';

    /**
     * @var 实例数组
     */
    private static $instances = [];

    /**
     * 这里私有方法阻止用户随意的创建该对象实例
     */
    private function __construct()
    {
    }

    public static function getInstance(string $instanceName): Multiton
    {
        if (!isset(self::$instances[$instanceName])) {
            self::$instances[$instanceName] = new self();
        }

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

    /**
     * 该私有对象阻止实例被克隆
     */
    private function __clone()
    {
    }

    /**
     * 该私有方法阻止实例被序列化
     */
    private function __wakeup()
    {
    }
}

5、对象池模式(Pool)

目的

对象池模式是一种提前准备1个存放 初始化待用对象 “池”的创建型设计模式;而不是一次性创建并使用,完成之后销毁。对象池的客户端会向对象池中请求一个对象,然后使用这个返回的对象执行相关操作。当客户端使用完毕,它将把这个特定类型的工厂对象返回给对象池,而不是销毁掉这个对象。

在初始化实例成本高,实例化率高,可用实例较少的情况下,对象池可以极大地提升性能。在创建对象(尤其是通过网络)时间花销不确定的情况下,通过对象池在可期时间内就可以获得所需的对象。

总之,对象池模式在需要耗时创建对象方面,例如创建数据库连接,套接字连接,线程和大型图形对象(比方字体或位图等),使用起来会为你节省宝贵的时间。在特定情况下,简单的对象池(没有请求外部的资源,仅仅将自身保存在内存中)或许并不会提升效率和性能,甚至会有损性能。

UML

代码

WorkerPool.php

<?php

namespace DesignPatterns\Creational\Pool;

class WorkerPool implements \Countable
{
    /**
    * @var StringReverseWorker[]
    */
    private $occupiedWorkers = [];

    /**
    * @var StringReverseWorker[]
    */
    private $freeWorkers = [];

    public function get(): StringReverseWorker
    {
        if (count($this->freeWorkers) == 0) {
            $worker = new StringReverseWorker();
        } else {
            $worker = array_pop($this->freeWorkers);
        }

        $this->occupiedWorkers[spl_object_hash($worker)] = $worker;

        return $worker;
    }

    public function dispose(StringReverseWorker $worker)
    {
        $key = spl_object_hash($worker);

        if (isset($this->occupiedWorkers[$key])) {
            unset($this->occupiedWorkers[$key]);
            $this->freeWorkers[$key] = $worker;
        }
    }

    public function count(): int
    {
        return count($this->occupiedWorkers) + count($this->freeWorkers);
    }
}

StringReverseWorker.php

<?php

namespace DesignPatterns\Creational\Pool;

class StringReverseWorker
{
    /**
    * @var \DateTime
    */
    private $createdAt;

    public function __construct()
    {
        $this->createdAt = new \DateTime();
    }

    public function run(string $text)
    {
        return strrev($text);
    }
}
测试

Tests/PoolTest.php

<?php

namespace DesignPatterns\Creational\Pool\Tests;

use DesignPatterns\Creational\Pool\WorkerPool;
use PHPUnit\Framework\TestCase;

class PoolTest extends TestCase
{
    public function testCanGetNewInstancesWithGet()
    {
        $pool = new WorkerPool();
        $worker1 = $pool->get();
        $worker2 = $pool->get();

        $this->assertCount(2, $pool);
        $this->assertNotSame($worker1, $worker2);
    }

    public function testCanGetSameInstanceTwiceWhenDisposingItFirst()
    {
        $pool = new WorkerPool();
        $worker1 = $pool->get();
        $pool->dispose($worker1);
        $worker2 = $pool->get();

        $this->assertCount(1, $pool);
        $this->assertSame($worker1, $worker2);
    }
}

6、原型模式(Prototype)

目的

通过创建一个原型对象,然后复制原型对象来避免通过标准的方式创建大量的对象产生的开销(new Foo())。相比正常创建一个对象 会更节省开销。 

示例

大数据量 (例如:通过 ORM 模型一次性往数据库插入 1,000,000 条数据) 。

UML 图

代码

BookPrototype.php

<?php

namespace DesignPatterns\Creational\Prototype;

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

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

    abstract public function __clone();

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }
}

BarBookPrototype.php

<?php

namespace DesignPatterns\Creational\Prototype;

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

    public function __clone()
    {
    }
}

FooBookPrototype.php

<?php

namespace DesignPatterns\Creational\Prototype;

class FooBookPrototype extends BookPrototype
{
    /**
    * @var string
    */
    protected $category = 'Foo';

    public function __clone()
    {
    }
}
测试

Tests/PrototypeTest.php

<?php

namespace DesignPatterns\Creational\Prototype\Tests;

use DesignPatterns\Creational\Prototype\BarBookPrototype;
use DesignPatterns\Creational\Prototype\FooBookPrototype;
use PHPUnit\Framework\TestCase;

class PrototypeTest extends TestCase
{
    public function testCanGetFooBook()
    {
        $fooPrototype = new FooBookPrototype();
        $barPrototype = new BarBookPrototype();

        for ($i = 0; $i < 10; $i++) {
            $book = clone $fooPrototype;
            $book->setTitle('Foo Book No ' . $i);
            $this->assertInstanceOf(FooBookPrototype::class, $book);
        }

        for ($i = 0; $i < 5; $i++) {
            $book = clone $barPrototype;
            $book->setTitle('Bar Book No ' . $i);
            $this->assertInstanceOf(BarBookPrototype::class, $book);
        }
    }
}

 7、简单工厂模式(Simple Factory)

目的 

简单工厂模式是一个精简版的工厂模式。

它与静态工厂模式最大的区别是它不是『静态』的。因为非静态,所以你可以拥有多个不同参数的工厂,你可以为其创建子类。甚至可以模拟(Mock)它,这对编写可测试的代码来讲至关重要。 这也是它比静态工厂模式受欢迎的原因!

UML 

代码

SimpleFactory.php

<?php

namespace DesignPatterns\Creational\SimpleFactory;

class SimpleFactory
{
public function createBicycle(): Bicycle
{
return new Bicycle();
}
}

Bicycle.php

<?php

namespace DesignPatterns\Creational\SimpleFactory;

class Bicycle
{
    public function driveTo(string $destination)
    {
        return $destination;
    }
}
用法
$factory = new SimpleFactory();
 $bicycle = $factory->createBicycle();
 $bicycle->driveTo('Paris');
测试

Tests/SimpleFactoryTest.php

<?php

namespace DesignPatterns\Creational\SimpleFactory\Tests;

use DesignPatterns\Creational\SimpleFactory\Bicycle;
use DesignPatterns\Creational\SimpleFactory\SimpleFactory;
use PHPUnit\Framework\TestCase;

class SimpleFactoryTest extends TestCase
{
    public function testCanCreateBicycle()
    {
        $bicycle = (new SimpleFactory())->createBicycle();
        $this->assertInstanceOf(Bicycle::class, $bicycle);
    }
}

8、单例模式(Singleton)

单例模式被公认为是 反面模式,为了获得更好的可测试性和可维护性,请使用『依赖注入模式』。

在软件工程中,一个反面模式(Anti-pattern 或 Antipattern)指的是在实践中明显出现但又低效或是有待优化的设计模式,是用来解决问题的带有共同性的不良方法。它们已经经过研究并分类,以防止日后重蹈覆辙,并能在研发尚未投产的系统时辨认出来。 Andrew Koenig 在 1995 年造了 Anti-pattern 这个词,灵感来自于 GoF 的《设计模式》一书。而这本书则在软件领域引入了 “设计模式”(Design Pattern)的概念。三年后 Antipattern 因《AntiPatterns》这本书而获得普及,而它的使用也从软件设计领域扩展到了日常的社会互动中。按《AntiPatterns》作者的说法,可以用至少两个关键因素来把反面模式和不良习惯、错误的实践或糟糕的想法区分开来: 行动、过程和结构中的一些重复出现的乍一看是有益的,但最终得不偿失的模式 在实践中证明且可重复的清晰记录的重构方案 很多反面模式只相当于是错误、咆哮、不可解的问题、或是可能可以避免的糟糕的实践,它们的名字通常都是一些用反话构成的词语。有些时候陷阱(Pitfalls)或黑色模式(Dark Patterns)这些不正式的说法会被用来指代各类反复出现的糟糕的解决方法。因此,一些有争议的候选的反面模式不会被正式承认。

目的  

在应用程序调用的时候,只能获得一个对象实例。

例子
  • 数据库连接
  • 日志 (多种不同用途的日志也可能会成为多例模式)
  • 在应用中锁定文件 (系统中只存在一个 ...)
UML

代码 

 Singleton.php

<?php

namespace DesignPatterns\Creational\Singleton;

final class Singleton
{
    /**
    * @var Singleton
    */
    private static $instance;

    /**
    * 通过懒加载获得实例(在第一次使用的时候创建)
    */
    public static function getInstance(): Singleton
    {
        if (null === static::$instance) {
            static::$instance = new static();
        }

        return static::$instance;
    }

    /**
    * 不允许从外部调用以防止创建多个实例
    * 要使用单例,必须通过 Singleton::getInstance() 方法获取实例
    */
    private function __construct()
    {
    }

    /**
    * 防止实例被克隆(这会创建实例的副本)
    */
    private function __clone()
    {
    }

    /**
    * 防止反序列化(这将创建它的副本)
    */
    private function __wakeup()
    {
    }
}
 测试

 Tests/SingletonTest.php

<?php

namespace DesignPatterns\Creational\Singleton\Tests;

use DesignPatterns\Creational\Singleton\Singleton;
use PHPUnit\Framework\TestCase;

class SingletonTest extends TestCase
{
    public function testUniqueness()
    {
        $firstCall = Singleton::getInstance();
        $secondCall = Singleton::getInstance();

        $this->assertInstanceOf(Singleton::class, $firstCall);
        $this->assertSame($firstCall, $secondCall);
    }
}

9、静态工厂模式(Static Factory)

目的

与抽象工厂模式类似,此模式用于创建一系列相关或相互依赖的对象。 『静态工厂模式』与『抽象工厂模式』的区别在于,只使用一个静态方法来创建所有类型对象, 此方法通常被命名为 factory 或 build 。

例子 

Zend Framework:Zend_Cache_Backend 或 _Frontend 使用工厂方法创建缓存后端或前端

UML 

 代码

StaticFactory.php

<?php

namespace DesignPatterns\Creational\StaticFactory;

/**
 * 注意点1: 记住,静态意味着全局状态,因为它不能被模拟进行测试,所以它是有弊端的
 * 注意点2: 不能被分类或模拟或有多个不同的实例。
 */
final class StaticFactory
{
    /**
    * @param string $type
    *
    * @return FormatterInterface
    */
    public static function factory(string $type): FormatterInterface
    {
        if ($type == 'number') {
            return new FormatNumber();
        }

        if ($type == 'string') {
            return new FormatString();
        }

        throw new \InvalidArgumentException('Unknown format given');
    }
}

FormatterInterface.php

<?php

namespace DesignPatterns\Creational\StaticFactory;

interface FormatterInterface
{
}

FormatString.php

<?php

namespace DesignPatterns\Creational\StaticFactory;

class FormatString implements FormatterInterface
{
}

FormatNumber.php 

<?php

namespace DesignPatterns\Creational\StaticFactory;

class FormatNumber implements FormatterInterface
{
}
测试

Tests/StaticFactoryTest.php

<?php

namespace DesignPatterns\Creational\StaticFactory\Tests;

use DesignPatterns\Creational\StaticFactory\StaticFactory;
use PHPUnit\Framework\TestCase;

class StaticFactoryTest extends TestCase
{
    public function testCanCreateNumberFormatter()
    {
        $this->assertInstanceOf(
            'DesignPatterns\Creational\StaticFactory\FormatNumber',
            StaticFactory::factory('number')
        );
    }

    public function testCanCreateStringFormatter()
    {
        $this->assertInstanceOf(
            'DesignPatterns\Creational\StaticFactory\FormatString',
            StaticFactory::factory('string')
        );
    }

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

 架构模式

10、适配器模式(Adapter)

目的

将某个类的接口转换成另一个接口以兼容,适配器使得原来因为接口不同而无法一起使用的类可以一起工作。 适配器通过将原始接口进行转换,给用户提供一个兼容接口。

例子

 客户端数据库适配器

使用多个不同的网络服务和适配器来规范数据使得出结果是相同的

UML 

代码

BookInterface.php

<?php

namespace DesignPatterns\Structural\Adapter;

interface BookInterface
{
    public function turnPage();

    public function open();

    public function getPage(): int;
}
 Book.php
<?php

namespace DesignPatterns\Structural\Adapter;

class Book implements BookInterface
{
    /**
    * @var int
    */
    private $page;

    public function open()
    {
        $this->page = 1;
    }

    public function turnPage()
    {
        $this->page++;
    }

    public function getPage(): int
    {
        return $this->page;
    }
}

EBookAdapter.php

<?php

namespace DesignPatterns\Structural\Adapter;

/**
* 这里是一个适配器. 注意他实现了 BookInterface,
* 因此你不必去更改客户端代码当使用 Book
*/
class EBookAdapter implements BookInterface
{
    /**
    * @var EBookInterface
    */
    protected $eBook;

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

    /**
    * 这个类使接口进行适当的转换.
    */
    public function open()
    {
        $this->eBook->unlock();
    }

    public function turnPage()
    {
        $this->eBook->pressNext();
    }

    /**
    * 注意这里适配器的行为: EBookInterface::getPage() 将返回两个整型,除了 BookInterface
    * 仅支持获得当前页,所以我们这里适配这个行为
    *
    * @return int
    */
    public function getPage(): int
    {
        return $this->eBook->getPage()[0];
    }
}

EBookInterface.php

<?php

namespace DesignPatterns\Structural\Adapter;

interface EBookInterface
{
    public function unlock();

    public function pressNext();

    /**
    * 返回当前页和总页数,像 [10, 100] 是总页数100中的第10页。
    *
    * @return int[]
    */
    public function getPage(): array;
}

Kindle.php

<?php

namespace DesignPatterns\Structural\Adapter;

/**
* 这里是适配过的类. 在生产代码中, 这可能是来自另一个包的类,一些供应商提供的代码。
* 注意它使用了另一种命名方案并用另一种方式实现了类似的操作
*/
class Kindle implements EBookInterface
{
    /**
    * @var int
    */
    private $page = 1;

    /**
    * @var int
    */
    private $totalPages = 100;

    public function pressNext()
    {
        $this->page++;
    }

    public function unlock()
    {
    }

    /**
    * 返回当前页和总页数,像 [10, 100] 是总页数100中的第10页。
    *
    * @return int[]
    */
    public function getPage(): array
    {
        return [$this->page, $this->totalPages];
    }
}
 测试

Tests/AdapterTest.php

<?php

namespace DesignPatterns\Structural\Adapter\Tests;

use DesignPatterns\Structural\Adapter\Book;
use DesignPatterns\Structural\Adapter\EBookAdapter;
use DesignPatterns\Structural\Adapter\Kindle;
use PHPUnit\Framework\TestCase;

class AdapterTest extends TestCase
{
    public function testCanTurnPageOnBook()
    {
        $book = new Book();
        $book->open();
        $book->turnPage();

        $this->assertEquals(2, $book->getPage());
    }

    public function testCanTurnPageOnKindleLikeInANormalBook()
    {
        $kindle = new Kindle();
        $book = new EBookAdapter($kindle);

        $book->open();
        $book->turnPage();

        $this->assertEquals(2, $book->getPage());
    }
}

11、桥梁模式(Bridge)

目的

解耦一个对象使抽象与实现分离,这样两者可以独立地变化。

UML

代码

Formatter.php

<?php

namespace DesignPatterns\Structural\Bridge;

/**
* 创建格式化接口。
*/
interface FormatterInterface
{
    public function format(string $text);
}

 PlainTextFormatter.php

<?php

namespace DesignPatterns\Structural\Bridge;

/**
* 创建 PlainTextFormatter 文本格式类实现 FormatterInterface 接口。
*/
class PlainTextFormatter implements FormatterInterface
{

    /**
    * 返回字符串格式。
    */
    public function format(string $text)
    {
        return $text;
    }
}

HtmlFormatter.php

<?php

namespace DesignPatterns\Structural\Bridge;

/**
* 创建 HtmlFormatter HTML 格式类实现 FormatterInterface 接口。
*/
class HtmlFormatter implements FormatterInterface
{

    /**
    * 返回 HTML 格式。
    */
    public function format(string $text)
    {
        return sprintf('<p>%s</p>', $text);
    }
}

Service.php

<?php

namespace DesignPatterns\Structural\Bridge;

/**
* 创建抽象类 Service。
*/
abstract class Service
{
    /**
    * @var FormatterInterface
    * 定义实现属性。
    */
    protected $implementation;

    /**
    * @param FormatterInterface $printer
    * 传入 FormatterInterface 实现类对象。
    */
    public function __construct(FormatterInterface $printer)
    {
        $this->implementation = $printer;
    }

    /**
    * @param FormatterInterface $printer
    * 和构造方法的作用相同。
    */
    public function setImplementation(FormatterInterface $printer)
    {
        $this->implementation = $printer;
    }

    /**
    * 创建抽象方法 get() 。
    */
    abstract public function get();
}

HelloWorldService.php

<?php

namespace DesignPatterns\Structural\Bridge;

/**
* 创建 Service 子类 HelloWorldService 。
*/
class HelloWorldService extends Service
{

    /**
    * 定义抽象方法 get() 。
    * 根据传入的格式类定义来格式化输出 'Hello World' 。
    */
    public function get()
    {
        return $this->implementation->format('Hello World');
    }
}
测试

Tests/BridgeTest.php

<?php

namespace DesignPatterns\Structural\Bridge\Tests;

use DesignPatterns\Structural\Bridge\HelloWorldService;
use DesignPatterns\Structural\Bridge\HtmlFormatter;
use DesignPatterns\Structural\Bridge\PlainTextFormatter;
use PHPUnit\Framework\TestCase;

/**
* 创建自动化测试单元 BridgeTest 。
*/
class BridgeTest extends TestCase
{

    /**
    * 使用 HelloWorldService 分别测试文本格式实现类和 HTML 格式实
    * 现类。
    */
    public function testCanPrintUsingThePlainTextPrinter()
    {
        $service = new HelloWorldService(new PlainTextFormatter());
        $this->assertEquals('Hello World', $service->get());

        // 现在更改实现方法为使用 HTML 格式器。
        $service->setImplementation(new HtmlFormatter());
        $this->assertEquals('<p>Hello World</p>', $service->get());
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值