创建一系列互相关联或依赖的对象时,不指定他们具体的类。因为这些创建的类通常都实现了同一个接口。抽象工厂的客户端并不关心对象是如何创建的,而只知道它们是怎样组合的。
在github上查看代码
WriterFactory.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
interface WriterFactory
{
public function createCsvWriter(): CsvWriter;
public function createJsonWriter(): JsonWriter;
}
CsvWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
interface CsvWriter
{
public function write(array $line): string;
}
JsonWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
interface JsonWriter
{
public function write(array $data, bool $formatted): string;
}
UnixCsvWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class UnixCsvWriter implements CsvWriter
{
public function write(array $line): string
{
return join(',', $line) . "\n";
}
}
UnixJsonWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class UnixJsonWriter implements JsonWriter
{
public function write(array $data, bool $formatted): string
{
$options = 0;
if ($formatted) {
$options = JSON_PRETTY_PRINT;
}
return json_encode($data, $options);
}
}
UnixWriterFactory.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class UnixWriterFactory implements WriterFactory
{
public function createCsvWriter(): CsvWriter
{
return new UnixCsvWriter();
}
public function createJsonWriter(): JsonWriter
{
return new UnixJsonWriter();
}
}
WinCsvWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class WinCsvWriter implements CsvWriter
{
public function write(array $line): string
{
return join(',', $line) . "\r\n";
}
}
WinJsonWriter.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class WinJsonWriter implements JsonWriter
{
public function write(array $data, bool $formatted): string
{
return json_encode($data, JSON_PRETTY_PRINT);
}
}
WinWriterFactory.php
<?php
namespace DesignPatterns\Creational\AbstractFactory;
class WinWriterFactory implements WriterFactory
{
public function createCsvWriter(): CsvWriter
{
return new WinCsvWriter();
}
public function createJsonWriter(): JsonWriter
{
return new WinJsonWriter();
}
}
Tests/AbstractFactoryTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\AbstractFactory\Tests;
use DesignPatterns\Creational\AbstractFactory\CsvWriter;
use DesignPatterns\Creational\AbstractFactory\JsonWriter;
use DesignPatterns\Creational\AbstractFactory\UnixWriterFactory;
use DesignPatterns\Creational\AbstractFactory\WinWriterFactory;
use DesignPatterns\Creational\AbstractFactory\WriterFactory;
use PHPUnit\Framework\TestCase;
class AbstractFactoryTest extends TestCase
{
public function provideFactory()
{
return [
[new UnixWriterFactory()],
[new WinWriterFactory()]
];
}
/**
* @dataProvider provideFactory
*/
public function testCanCreateCsvWriterOnUnix(WriterFactory $writerFactory)
{
$this->assertInstanceOf(JsonWriter::class, $writerFactory->createJsonWriter());
$this->assertInstanceOf(CsvWriter::class, $writerFactory->createCsvWriter());
}
}
生成器模式(Builder,或称建造者模式)是一个接口,用于构建复杂对象的各个部分。
在某些情况下,如果生成器对其构建的内容有很好的了解,那么这个接口可以是一个抽象类,并会有一个默认方法(也称适配器)。
如果对象具有复杂的继承结构,那么按照的正常逻辑,生成器也应该有一个复杂的继承结构。
注意:生成器通常有都有一个完善的接口,例如 PHPUnit 的模拟生成器(Mock Builder)。
PHPUnit: 模拟生成器(Mock Builder)
在 github 上找到这些代码
Director.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
/**
* Director is part of the builder pattern. It knows the interface of the builder
* and builds a complex object with the help of the builder
*
* You can also inject many builders instead of one to build more complex objects
*/
class Director
{
public function build(Builder $builder): Vehicle
{
$builder->createVehicle();
$builder->addDoors();
$builder->addEngine();
$builder->addWheel();
return $builder->getVehicle();
}
}
Builder.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
interface Builder
{
public function createVehicle();
public function addWheel();
public function addEngine();
public function addDoors();
public function getVehicle(): Vehicle;
}
TruckBuilder.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Door;
use DesignPatterns\Creational\Builder\Parts\Engine;
use DesignPatterns\Creational\Builder\Parts\Wheel;
use DesignPatterns\Creational\Builder\Parts\Truck;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
class TruckBuilder implements Builder
{
private Truck $truck;
public function addDoors()
{
$this->truck->setPart('rightDoor', new Door());
$this->truck->setPart('leftDoor', new Door());
}
public function addEngine()
{
$this->truck->setPart('truckEngine', new Engine());
}
public function addWheel()
{
$this->truck->setPart('wheel1', new Wheel());
$this->truck->setPart('wheel2', new Wheel());
$this->truck->setPart('wheel3', new Wheel());
$this->truck->setPart('wheel4', new Wheel());
$this->truck->setPart('wheel5', new Wheel());
$this->truck->setPart('wheel6', new Wheel());
}
public function createVehicle()
{
$this->truck = new Truck();
}
public function getVehicle(): Vehicle
{
return $this->truck;
}
}
CarBuilder.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder;
use DesignPatterns\Creational\Builder\Parts\Door;
use DesignPatterns\Creational\Builder\Parts\Engine;
use DesignPatterns\Creational\Builder\Parts\Wheel;
use DesignPatterns\Creational\Builder\Parts\Car;
use DesignPatterns\Creational\Builder\Parts\Vehicle;
class CarBuilder implements Builder
{
private Car $car;
public function addDoors()
{
$this->car->setPart('rightDoor', new Door());
$this->car->setPart('leftDoor', new Door());
$this->car->setPart('trunkLid', new Door());
}
public function addEngine()
{
$this->car->setPart('engine', new Engine());
}
public function addWheel()
{
$this->car->setPart('wheelLF', new Wheel());
$this->car->setPart('wheelRF', new Wheel());
$this->car->setPart('wheelLR', new Wheel());
$this->car->setPart('wheelRR', new Wheel());
}
public function createVehicle()
{
$this->car = new Car();
}
public function getVehicle(): Vehicle
{
return $this->car;
}
}
Parts/Vehicle.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
abstract class Vehicle
{
public function setPart(string $key, object $value)
{
}
}
Parts/Truck.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Truck extends Vehicle
{
}
Parts/Car.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Car extends Vehicle
{
}
Parts/Engine.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Engine
{
}
Parts/Wheel.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Wheel
{
}
Parts/Door.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Builder\Parts;
class Door
{
}
Tests/DirectorTest.php
<?php
declare(strict_types=1);
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);
}
}
相比 简单工厂模式(SimpleFactory)而言,工厂方法模式(Factory Method)可以通过延伸出子类,实现用不同的方法创建对象。
对于比较简单的情况,这个抽象类可能只是一个接口。
这是一个 “真正” 的设计模式,因为它遵循了”依赖反转原则(Dependency Inversion Principle)” 。也就是 SOLID 原则中的”D”。
这意味着工厂方法实现的类依赖于类的抽象,而不是具体的类。这也是 工厂方法模式 与 简单工厂模式 和 静态工厂模式 之间最重要的区别。
在github上找到这些代码
Logger.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
interface Logger
{
public function log(string $message);
}
StdoutLogger.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class StdoutLogger implements Logger
{
public function log(string $message)
{
echo $message;
}
}
FileLogger.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class FileLogger implements Logger
{
public function __construct(private string $filePath)
{
}
public function log(string $message)
{
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}
LoggerFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
interface LoggerFactory
{
public function createLogger(): Logger;
}
StdoutLoggerFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class StdoutLoggerFactory implements LoggerFactory
{
public function createLogger(): Logger
{
return new StdoutLogger();
}
}
FileLoggerFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\FactoryMethod;
class FileLoggerFactory implements LoggerFactory
{
public function __construct(private string $filePath)
{
}
public function createLogger(): Logger
{
return new FileLogger($this->filePath);
}
}
Tests/FactoryMethodTest.php
<?php
declare(strict_types=1);
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);
}
}
对象池设计模式
是一种创建型设计模式。它使用一组提前已准备完成可以使用的对象,而不是根据需要分配或销毁。这种来源被称为:“池”(pool)。池的使用者将从池中请求一个对象,并对返回的对象进行操作。在客户端处理完成后,它将返回一种特定类型的工厂对象。返回给池,而不是销毁。
对象池模式在一些情景下可以带来明显的性能提升。如:类实例初始化成本较高、类的实例化率较高、或类实例同时使用率不高的情况下。当创建新对象(尤其是通过网络)的时间花费不确定时,池对象的获得时间却是可预测的。
这些优点对于时间要求敏感的对象来说,是非常有用的。例如:数据库连接、套接字连接、线程和大型图形对象(如字体或位图)。但在某些情况下,简单的对象池(不包含外部资源,只占用内存)可能效率不高,并且会降低性能。
在github上找到这些代码
WorkerPool.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Pool;
use Countable;
class WorkerPool implements Countable
{
/**
* @var StringReverseWorker[]
*/
private array $occupiedWorkers = [];
/**
* @var StringReverseWorker[]
*/
private array $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
declare(strict_types=1);
namespace DesignPatterns\Creational\Pool;
use DateTime;
class StringReverseWorker
{
public function __construct()
{
}
public function run(string $text): string
{
return strrev($text);
}
}
Tests/PoolTest.php
<?php
declare(strict_types=1);
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);
}
}
避免以标准方式(new Foo())创建对象的成本,而是创建一个原型(prototype)并克隆它。
大量数据(例如:通过 ORM 在数据库中创建百万行数据。)
在github上找到这些代码
BookPrototype.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype;
abstract class BookPrototype
{
protected string $title;
protected string $category;
abstract public function __clone();
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
}
BarBookPrototype.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype;
class BarBookPrototype extends BookPrototype
{
protected string $category = 'Bar';
public function __clone()
{
}
}
FooBookPrototype.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Prototype;
class FooBookPrototype extends BookPrototype
{
protected string $category = 'Foo';
public function __clone()
{
}
}
Tests/PrototypeTest.php
<?php
declare(strict_types=1);
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);
}
}
}
SimpleFactory 是一个简单的工厂模式。
它不同于静态工厂,因为它不是静态的。因此,您可以拥有多个工厂,可以有不同的参数,可以进行子类化,也可以模拟。它总是比静态工厂更受欢迎!
在github上找到这些代码
SimpleFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\SimpleFactory;
class SimpleFactory
{
public function createBicycle(): Bicycle
{
return new Bicycle();
}
}
Bicycle.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\SimpleFactory;
class Bicycle
{
public function driveTo(string $destination)
{
}
}
$factory = new SimpleFactory();
$bicycle = $factory->createBicycle();
$bicycle->driveTo('Paris');
Tests/SimpleFactoryTest.php
<?php
declare(strict_types=1);
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);
}
}
注:单例模式可能被认为是一种“反模式”。为了获得更好的可测试性和可维护性,建议使用依赖注入。
让应用只存在一个对象的实例,处理所有的调用。
在github上找到这些代码
Singleton.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\Singleton;
use Exception;
final class Singleton
{
private static ?Singleton $instance = null;
/**
* gets the instance via lazy initialization (created on first usage)
*/
public static function getInstance(): Singleton
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
/**
* is not allowed to call from outside to prevent from creating multiple instances,
* to use the singleton, you have to obtain the instance from Singleton::getInstance() instead
*/
private function __construct()
{
}
/**
* prevent the instance from being cloned (which would create a second instance of it)
*/
private function __clone()
{
}
/**
* prevent from being unserialized (which would create a second instance of it)
*/
public function __wakeup()
{
throw new Exception("Cannot unserialize singleton");
}
}
Tests/SingletonTest.php
<?php
declare(strict_types=1);
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);
}
}
与 抽象工厂 类似,静态工厂模式用于创建一系列互相关联或依赖的对象。它与抽象工厂模式的区别在于,静态工厂模式仅使用 一个静态方法 来创建所有它可以创建的类型。通常,这个静态方法被命名为factory
或build
。
在github上找到这些代码
StaticFactory.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
use InvalidArgumentException;
/**
* Note1: Remember, static means global state which is evil because it can't be mocked for tests
* Note2: Cannot be subclassed or mock-upped or have multiple different instances.
*/
final class StaticFactory
{
public static function factory(string $type): Formatter
{
if ($type == 'number') {
return new FormatNumber();
} elseif ($type == 'string') {
return new FormatString();
}
throw new InvalidArgumentException('Unknown format given');
}
}
Formatter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
interface Formatter
{
public function format(string $input): string;
}
FormatString.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
class FormatString implements Formatter
{
public function format(string $input): string
{
return $input;
}
}
FormatNumber.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory;
class FormatNumber implements Formatter
{
public function format(string $input): string
{
return number_format((int) $input);
}
}
Tests/StaticFactoryTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Creational\StaticFactory\Tests;
use InvalidArgumentException;
use DesignPatterns\Creational\StaticFactory\FormatNumber;
use DesignPatterns\Creational\StaticFactory\FormatString;
use DesignPatterns\Creational\StaticFactory\StaticFactory;
use PHPUnit\Framework\TestCase;
class StaticFactoryTest extends TestCase
{
public function testCanCreateNumberFormatter()
{
$this->assertInstanceOf(FormatNumber::class, StaticFactory::factory('number'));
}
public function testCanCreateStringFormatter()
{
$this->assertInstanceOf(FormatString::class, StaticFactory::factory('string'));
}
public function testException()
{
$this->expectException(InvalidArgumentException::class);
StaticFactory::factory('object');
}
}
将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。
在github上找到这些代码
Book.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Adapter;
interface Book
{
public function turnPage();
public function open();
public function getPage(): int;
}
PaperBook.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Adapter;
class PaperBook implements Book
{
private int $page;
public function open(): void
{
$this->page = 1;
}
public function turnPage(): void
{
$this->page++;
}
public function getPage(): int
{
return $this->page;
}
}
EBook.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Adapter;
interface EBook
{
public function unlock();
public function pressNext();
/**
* returns current page and total number of pages, like [10, 100] is page 10 of 100
*
* @return int[]
*/
public function getPage(): array;
}
EBookAdapter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Adapter;
/**
* This is the adapter here. Notice it implements Book,
* therefore you don't have to change the code of the client which is using a Book
*/
class EBookAdapter implements Book
{
public function __construct(protected EBook $eBook)
{
}
/**
* This class makes the proper translation from one interface to another.
*/
public function open()
{
$this->eBook->unlock();
}
public function turnPage()
{
$this->eBook->pressNext();
}
/**
* notice the adapted behavior here: EBook::getPage() will return two integers, but Book
* supports only a current page getter, so we adapt the behavior here
*/
public function getPage(): int
{
return $this->eBook->getPage()[0];
}
}
Kindle.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Adapter;
/**
* this is the adapted class. In production code, this could be a class from another package, some vendor code.
* Notice that it uses another naming scheme and the implementation does something similar but in another way
*/
class Kindle implements EBook
{
private int $page = 1;
private int $totalPages = 100;
public function pressNext()
{
$this->page++;
}
public function unlock()
{
}
/**
* returns current page and total number of pages, like [10, 100] is page 10 of 100
*
* @return int[]
*/
public function getPage(): array
{
return [$this->page, $this->totalPages];
}
}
Tests/AdapterTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Adapter\Tests;
use DesignPatterns\Structural\Adapter\PaperBook;
use DesignPatterns\Structural\Adapter\EBookAdapter;
use DesignPatterns\Structural\Adapter\Kindle;
use PHPUnit\Framework\TestCase;
class AdapterTest extends TestCase
{
public function testCanTurnPageOnBook()
{
$book = new PaperBook();
$book->open();
$book->turnPage();
$this->assertSame(2, $book->getPage());
}
public function testCanTurnPageOnKindleLikeInANormalBook()
{
$kindle = new Kindle();
$book = new EBookAdapter($kindle);
$book->open();
$book->turnPage();
$this->assertSame(2, $book->getPage());
}
}
解耦一个对象的实现与抽象,这样两者可以独立地变化。
在github上找到这些代码
Formatter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge;
interface Formatter
{
public function format(string $text): string;
}
PlainTextFormatter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge;
class PlainTextFormatter implements Formatter
{
public function format(string $text): string
{
return $text;
}
}
HtmlFormatter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge;
class HtmlFormatter implements Formatter
{
public function format(string $text): string
{
return sprintf('<p>%s</p>', $text);
}
}
Service.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge;
abstract class Service
{
public function __construct(protected Formatter $implementation)
{
}
public function setImplementation(Formatter $printer)
{
$this->implementation = $printer;
}
abstract public function get(): string;
}
HelloWorldService.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge;
class HelloWorldService extends Service
{
public function get(): string
{
return $this->implementation->format('Hello World');
}
}
PingService.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge;
class PingService extends Service
{
public function get(): string
{
return $this->implementation->format('pong');
}
}
Tests/BridgeTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Bridge\Tests;
use DesignPatterns\Structural\Bridge\HelloWorldService;
use DesignPatterns\Structural\Bridge\HtmlFormatter;
use DesignPatterns\Structural\Bridge\PlainTextFormatter;
use PHPUnit\Framework\TestCase;
class BridgeTest extends TestCase
{
public function testCanPrintUsingThePlainTextFormatter()
{
$service = new HelloWorldService(new PlainTextFormatter());
$this->assertSame('Hello World', $service->get());
}
public function testCanPrintUsingTheHtmlFormatter()
{
$service = new HelloWorldService(new HtmlFormatter());
$this->assertSame('<p>Hello World</p>', $service->get());
}
}
以单个对象的方式来对待一组对象
form类的实例包含多个子元素,而它也像单个子元素那样响应render()请求,当调用 render() 方法时,它会历遍所有的子元素,调用 render() 方法
在github上找到这些代码
Renderable.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Composite;
interface Renderable
{
public function render(): string;
}
Form.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Composite;
/**
* The composite node MUST extend the component contract. This is mandatory for building
* a tree of components.
*/
class Form implements Renderable
{
/**
* @var Renderable[]
*/
private array $elements;
/**
* runs through all elements and calls render() on them, then returns the complete representation
* of the form.
*
* from the outside, one will not see this and the form will act like a single object instance
*/
public function render(): string
{
$formCode = '<form>';
foreach ($this->elements as $element) {
$formCode .= $element->render();
}
return $formCode . '</form>';
}
public function addElement(Renderable $element)
{
$this->elements[] = $element;
}
}
InputElement.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Composite;
class InputElement implements Renderable
{
public function render(): string
{
return '<input type="text" />';
}
}
TextElement.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Composite;
class TextElement implements Renderable
{
public function __construct(private string $text)
{
}
public function render(): string
{
return $this->text;
}
}
Tests/CompositeTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Composite\Tests;
use DesignPatterns\Structural\Composite\Form;
use DesignPatterns\Structural\Composite\TextElement;
use DesignPatterns\Structural\Composite\InputElement;
use PHPUnit\Framework\TestCase;
class CompositeTest extends TestCase
{
public function testRender()
{
$form = new Form();
$form->addElement(new TextElement('Email:'));
$form->addElement(new InputElement());
$embed = new Form();
$embed->addElement(new TextElement('Password:'));
$embed->addElement(new InputElement());
$form->addElement($embed);
// This is just an example, in a real world scenario it is important to remember that web browsers do not
// currently support nested forms
$this->assertSame(
'<form>Email:<input type="text" /><form>Password:<input type="text" /></form></form>',
$form->render()
);
}
}
数据映射器是一个数据访问层,用于将数据在持久性数据存储(通常是一个关系数据库)和内存中的数据表示(领域层)之间进行相互转换。其目的是为了将数据的内存表示、持久存储、数据访问进行分离。该层由一个或者多个映射器组成(或者数据访问对象),并且进行数据的转换。映射器的实现在范围上有所不同。通用映射器将处理许多不同领域的实体类型,而专用映射器将处理一个或几个。
此模式的主要特点是,与Active Record不同,其数据模式遵循单一职责原则(Single Responsibility Principle)。
DB Object Relational Mapper (ORM) : Doctrine2 使用 DAO “EntityRepository” 作为DAO
在github上找到这些代码
User.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DataMapper;
class User
{
public static function fromState(array $state): User
{
// validate state before accessing keys!
return new self(
$state['username'],
$state['email']
);
}
public function __construct(private string $username, private string $email)
{
}
public function getUsername(): string
{
return $this->username;
}
public function getEmail(): string
{
return $this->email;
}
}
UserMapper.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DataMapper;
use InvalidArgumentException;
class UserMapper
{
public function __construct(private StorageAdapter $adapter)
{
}
/**
* finds a user from storage based on ID and returns a User object located
* in memory. Normally this kind of logic will be implemented using the Repository pattern.
* However the important part is in mapRowToUser() below, that will create a business object from the
* data fetched from storage
*/
public function findById(int $id): User
{
$result = $this->adapter->find($id);
if ($result === null) {
throw new InvalidArgumentException("User #$id not found");
}
return $this->mapRowToUser($result);
}
private function mapRowToUser(array $row): User
{
return User::fromState($row);
}
}
StorageAdapter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DataMapper;
class StorageAdapter
{
public function __construct(private array $data)
{
}
/**
* @return array|null
*/
public function find(int $id)
{
if (isset($this->data[$id])) {
return $this->data[$id];
}
return null;
}
}
Tests/DataMapperTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DataMapper\Tests;
use InvalidArgumentException;
use DesignPatterns\Structural\DataMapper\StorageAdapter;
use DesignPatterns\Structural\DataMapper\User;
use DesignPatterns\Structural\DataMapper\UserMapper;
use PHPUnit\Framework\TestCase;
class DataMapperTest extends TestCase
{
public function testCanMapUserFromStorage()
{
$storage = new StorageAdapter([1 => ['username' => 'domnikl', 'email' => 'liebler.dominik@gmail.com']]);
$mapper = new UserMapper($storage);
$user = $mapper->findById(1);
$this->assertInstanceOf(User::class, $user);
}
public function testWillNotMapInvalidData()
{
$this->expectException(InvalidArgumentException::class);
$storage = new StorageAdapter([]);
$mapper = new UserMapper($storage);
$mapper->findById(1);
}
}
动态地为类的实例添加功能
Web Service层:REST服务的JSON与XML装饰器(当然,在此只能使用其中的一种)
在github上找到这些代码
Booking.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Decorator;
interface Booking
{
public function calculatePrice(): int;
public function getDescription(): string;
}
BookingDecorator.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Decorator;
abstract class BookingDecorator implements Booking
{
public function __construct(protected Booking $booking)
{
}
}
DoubleRoomBooking.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Decorator;
class DoubleRoomBooking implements Booking
{
public function calculatePrice(): int
{
return 40;
}
public function getDescription(): string
{
return 'double room';
}
}
ExtraBed.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Decorator;
class ExtraBed extends BookingDecorator
{
private const PRICE = 30;
public function calculatePrice(): int
{
return $this->booking->calculatePrice() + self::PRICE;
}
public function getDescription(): string
{
return $this->booking->getDescription() . ' with extra bed';
}
}
WiFi.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Decorator;
class WiFi extends BookingDecorator
{
private const PRICE = 2;
public function calculatePrice(): int
{
return $this->booking->calculatePrice() + self::PRICE;
}
public function getDescription(): string
{
return $this->booking->getDescription() . ' with wifi';
}
}
Tests/DecoratorTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\Decorator\Tests;
use DesignPatterns\Structural\Decorator\DoubleRoomBooking;
use DesignPatterns\Structural\Decorator\ExtraBed;
use DesignPatterns\Structural\Decorator\WiFi;
use PHPUnit\Framework\TestCase;
class DecoratorTest extends TestCase
{
public function testCanCalculatePriceForBasicDoubleRoomBooking()
{
$booking = new DoubleRoomBooking();
$this->assertSame(40, $booking->calculatePrice());
$this->assertSame('double room', $booking->getDescription());
}
public function testCanCalculatePriceForDoubleRoomBookingWithWiFi()
{
$booking = new DoubleRoomBooking();
$booking = new WiFi($booking);
$this->assertSame(42, $booking->calculatePrice());
$this->assertSame('double room with wifi', $booking->getDescription());
}
public function testCanCalculatePriceForDoubleRoomBookingWithWiFiAndExtraBed()
{
$booking = new DoubleRoomBooking();
$booking = new WiFi($booking);
$booking = new ExtraBed($booking);
$this->assertSame(72, $booking->calculatePrice());
$this->assertSame('double room with wifi with extra bed', $booking->getDescription());
}
}
实现了松耦合的软件架构,可得到更好的测试,管理和扩展的代码
通过配置需要注入的依赖,Connection 能从 $config 中获取到所有它需要的依赖。如果没有依赖注入,Connection 会直接创建它需要的依赖,这样不利于测试和扩展 Connection。
Doctrine2 ORM 使用了依赖注入,它通过配置注入了 Connection 对象。为了达到方便测试的目的,可以很容易的通过配置创建一个mock的 Connection 对象。
many frameworks already have containers for DI that create objects via a configuration array and inject them where needed (i.e. in Controllers)
在github上找到这些代码
DatabaseConfiguration.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DependencyInjection;
class DatabaseConfiguration
{
public function __construct(
private string $host,
private int $port,
private string $username,
private string $password
) {
}
public function getHost(): string
{
return $this->host;
}
public function getPort(): int
{
return $this->port;
}
public function getUsername(): string
{
return $this->username;
}
public function getPassword(): string
{
return $this->password;
}
}
DatabaseConnection.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DependencyInjection;
class DatabaseConnection
{
public function __construct(private DatabaseConfiguration $configuration)
{
}
public function getDsn(): string
{
// this is just for the sake of demonstration, not a real DSN
// notice that only the injected config is used here, so there is
// a real separation of concerns here
return sprintf(
'%s:%s@%s:%d',
$this->configuration->getUsername(),
$this->configuration->getPassword(),
$this->configuration->getHost(),
$this->configuration->getPort()
);
}
}
Tests/DependencyInjectionTest.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Structural\DependencyInjection\Tests;
use DesignPatterns\Structural\DependencyInjection\DatabaseConfiguration;
use DesignPatterns\Structural\DependencyInjection\DatabaseConnection;
use PHPUnit\Framework\TestCase;
class DependencyInjectionTest extends TestCase
{
public function testDependencyInjection()
{
$config = new DatabaseConfiguration('localhost', 3306, 'domnikl', '1234');
$connection = new DatabaseConnection($config);
$this->assertSame('domnikl:1234@localhost:3306', $connection->getDsn());
}
}
在[github]()上找到这些代码
在[github]()上找到这些代码
在[github]()上找到这些代码
在[github]()上找到这些代码
在[github]()上找到这些代码