Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/leonzai/php-design-patterns
php design patterns
https://github.com/leonzai/php-design-patterns
Last synced: 26 days ago
JSON representation
php design patterns
- Host: GitHub
- URL: https://github.com/leonzai/php-design-patterns
- Owner: leonzai
- Created: 2020-12-19T02:29:09.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2020-12-19T02:30:06.000Z (about 4 years ago)
- Last Synced: 2024-11-07T09:45:35.338Z (3 months ago)
- Size: 18.6 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 创建型
### 抽象工厂模式
当需要生产有相关依赖但又不是简单逻辑的对象时使用。
```php
与工厂方法的区别:
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
工厂方法:每种产品由一种工厂来创建,一个工厂生产一个对象。基本完美,完全遵循 “不改代码”的原则
抽象工厂:仅仅是工厂方法的复杂化,生产多个对象。一般大型项目才用得到。
``````php
assertInstanceOf(JsonWriter::class, $writerFactory->createJsonWriter());
$this->assertInstanceOf(CsvWriter::class, $writerFactory->createCsvWriter());
}
}
```### 建造者模式
为具有伸缩性构造器的反模式设计找到解决方案。
```php
size = $builder->size;
$this->cheese = $builder->cheese;
$this->pepperoni = $builder->pepperoni;
$this->lettuce = $builder->lettuce;
$this->tomato = $builder->tomato;
}
}class BurgerBuilder
{
public $size;public $cheese = false;
public $pepperoni = false;
public $lettuce = false;
public $tomato = false;public function __construct(int $size)
{
$this->size = $size;
}public function addPepperoni()
{
$this->pepperoni = true;
return $this;
}public function addLettuce()
{
$this->lettuce = true;
return $this;
}public function addCheese()
{
$this->cheese = true;
return $this;
}public function addTomato()
{
$this->tomato = true;
return $this;
}public function build(): Burger
{
return new Burger($this);
}
}
``````php
addPepperoni()->build();$this->assertInstanceOf(Burger::class, $builder);
}
}
```### 工厂方法模式
当类中有一些泛型处理,但所需的子类是在运行时动态决定时,这很有用。或者换句话说,无法确认子类如何实现时使用。
```php
与工厂方法的区别:
工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。
工厂方法:每种产品由一种工厂来创建,一个工厂生产一个对象。基本完美,完全遵循 “不改代码”的原则
抽象工厂:仅仅是工厂方法的复杂化,生产多个对象。一般大型项目才用得到。
``````php
filePath = $filePath;
}public function log(string $message)
{
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}interface LoggerFactory
{
public function createLogger(): Logger;
}class StdoutLoggerFactory implements LoggerFactory
{
public function createLogger(): Logger
{
return new StdoutLogger();
}
}class FileLoggerFactory implements LoggerFactory
{
private string $filePath;public function __construct(string $filePath)
{
$this->filePath = $filePath;
}public function createLogger(): Logger
{
return new FileLogger($this->filePath);
}
}
``````php
createLogger();$this->assertInstanceOf(StdoutLogger::class, $logger);
}public function test_can_create_file_logging()
{
$loggerFactory = new FileLoggerFactory(sys_get_temp_dir());
$logger = $loggerFactory->createLogger();$this->assertInstanceOf(FileLogger::class, $logger);
}
}
```### 池模式
一组随时准备使用的已初始化对象(“池”),而不是按需分配和销毁它们。
简单的对象池(不占用任何外部资源,只占用内存)可能不会提高效率,并且可能会降低性能。
多用于数据库连接,套接字连接,线程和大图形对象(如字体或位图)```php
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);
}
}
```````php
get();
$worker2 = $pool->get();$this->assertCount(2, $pool);
$this->assertNotSame($worker1, $worker2);
}public function test_can_get_same_instance_twice_when_disposing_it_first()
{
$pool = new WorkerPool();
$worker1 = $pool->get();
$pool->dispose($worker1);
$worker2 = $pool->get();$this->assertCount(1, $pool);
$this->assertSame($worker1, $worker2);
}
}
````### 原型模式
当需要一个与现有对象相似的对象时,或者与克隆相比创建成本很高时使用。
```php
title;
}public function setTitle(string $title)
{
$this->title = $title;
}
}class BarBookPrototype extends BookPrototype
{
protected string $category = 'Bar';public function __clone()
{
}
}class FooBookPrototype extends BookPrototype
{
protected string $category = 'Foo';public function __clone()
{
}
}
``````php
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);
}
}
}
```### 简单工厂模式
工厂类负责创建的对象比较少,客户只知道传入了工厂类的参数,对于如何创建对象(逻辑)不关心。
```php
createBicycle();
$this->assertInstanceOf(Bicycle::class, $bicycle);
}
}
```### 单例模式
确保某个类有且只有一个对象会被创建。
```php
assertInstanceOf(Singleton::class, $firstCall);
$this->assertSame($firstCall, $secondCall);
}
}
```# 结构型
### 适配器模式 / 包装器模式
允许将现有类的接口用作另一个接口。 通常用于使现有类与其他类一起使用而无需修改其源代码。
```php
page = 1;
}public function turnPage()
{
$this->page++;
}public function getPage(): int
{
return $this->page;
}
}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;
}class EBookAdapter implements Book
{
protected EBook $eBook;public function __construct(EBook $eBook)
{
$this->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];
}
}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];
}
}
```### 桥模式
```php
%s', $text);
}
}abstract class Service
{
protected Formatter $implementation;public function __construct(Formatter $printer)
{
$this->implementation = $printer;
}public function setImplementation(Formatter $printer)
{
$this->implementation = $printer;
}abstract public function get(): string;
}class HelloWorldService extends Service
{
public function get(): string
{
return $this->implementation->format('Hello World');
}
}class PingService extends Service
{
public function get(): string
{
return $this->implementation->format('pong');
}
}
``````php
assertSame('Hello World', $service->get());
}public function test_can_print_using_the_html_formatter()
{
$service = new HelloWorldService(new HtmlFormatter());$this->assertSame('
Hello World
', $service->get());
}
}
```### 组合模式
是一种分区设计模式。目的是表示部分整体层次结构。让客户端统一地对待单个对象和组合。
```php
';foreach ($this->elements as $element) {
$formCode .= $element->render();
}$formCode .= '';
return $formCode;
}public function addElement(Renderable $element)
{
$this->elements[] = $element;
}
}class InputElement implements Renderable
{
public function render(): string
{
return '';
}
}class TextElement implements Renderable
{
private string $text;public function __construct(string $text)
{
$this->text = $text;
}public function render(): string
{
return $this->text;
}
}
``````php
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(
'Email:Password:',
$form->render()
);
}
}
```### 数据映射模式
就是 ORM。
```php
username = $username;
$this->email = $email;
}public function getUsername(): string
{
return $this->username;
}public function getEmail(): string
{
return $this->email;
}
}class UserMapper
{
private StorageAdapter $adapter;public function __construct(StorageAdapter $storage)
{
$this->adapter = $storage;
}/**
* 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);
}
}class StorageAdapter
{
private array $data = [];public function __construct(array $data)
{
$this->data = $data;
}/**
* @param int $id
*
* @return array|null
*/
public function find(int $id)
{
if (isset($this->data[$id])) {
return $this->data[$id];
}return null;
}
}
``````php
['username' => 'domnikl', 'email' => '[email protected]']]);
$mapper = new UserMapper($storage);$user = $mapper->findById(1);
$this->assertInstanceOf(User::class, $user);
}public function test_will_not_map_invalid_data()
{
$this->expectException(InvalidArgumentException::class);$storage = new StorageAdapter([]);
$mapper = new UserMapper($storage);$mapper->findById(1);
}
}
```### 装饰模式
将对象包装在装饰器类的对象中,从而在运行时动态更改其行为。
```php
booking = $booking;
}
}class DoubleRoomBooking implements Booking
{
public function calculatePrice(): int
{
return 40;
}public function getDescription(): string
{
return 'double room';
}
}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';
}
}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';
}
}
``````php
assertSame(40, $booking->calculatePrice());
$this->assertSame('double room', $booking->getDescription());
}public function test_can_calculate_price_for_basic_double_room_booking_with_wifi()
{
$booking = new DoubleRoomBooking();
$booking = new WiFi($booking);$this->assertSame(42, $booking->calculatePrice());
$this->assertSame('double room with wifi', $booking->getDescription());
}public function test_can_calculate_price_for_basic_double_room_booking_with_wifi_and_extra_bed()
{
$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());
}
}
```### 依赖注入模式
解耦。
```php
host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $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;
}
}class DatabaseConnection
{
private DatabaseConfiguration $configuration;public function __construct(DatabaseConfiguration $config)
{
$this->configuration = $config;
}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 herereturn sprintf(
'%s:%s@%s:%d',
$this->configuration->getUsername(),
$this->configuration->getPassword(),
$this->configuration->getHost(),
$this->configuration->getPort()
);
}
}
``````php
assertSame('domnikl:1234@localhost:3306', $connection->getDsn());
}
}
```### 门面模式
好的门面模式不需要向构造器传参甚至不需要 new,目的是解耦、遵循迪米特原则(又名最少知道原则,一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话)。
```php
bios = $bios;
$this->os = $os;
}public function turnOn()
{
$this->bios->execute();
$this->bios->waitForKeyPress();
$this->bios->launch($this->os);
}public function turnOff()
{
$this->os->halt();
$this->bios->powerDown();
}
}interface OperatingSystem
{
public function halt();public function getName(): string;
}interface Bios
{
public function execute();public function waitForKeyPress();
public function launch(OperatingSystem $os);
public function powerDown();
}
``````php
createMock(OperatingSystem::class);$os->method('getName')
->will($this->returnValue('Linux'));$bios = $this->createMock(Bios::class);
$bios->method('launch')
->with($os);/** @noinspection PhpParamsInspection */
$facade = new Facade($bios, $os);
$facade->turnOn();$this->assertSame('Linux', $os->getName());
}
}
```### 流接口模式
编写易于阅读的代码,就像自然语言(如英语)中的句子一样。
```php
fields = $fields;return $this;
}public function from(string $table, string $alias): Sql
{
$this->from[] = $table.' AS '.$alias;return $this;
}public function where(string $condition): Sql
{
$this->where[] = $condition;return $this;
}public function __toString(): string
{
return sprintf(
'SELECT %s FROM %s WHERE %s',
join(', ', $this->fields),
join(', ', $this->from),
join(' AND ', $this->where)
);
}
}
``````php
select(['foo', 'bar'])
->from('foobar', 'f')
->where('f.bar = ?');$this->assertSame('SELECT foo, bar FROM foobar AS f WHERE f.bar = ?', (string) $query);
}
}
```### 享元模式
为了最大限度地减少内存的使用,与相似的对象共享尽可能多的内存。当使用大量状态相差不大的对象时,就需要使用它。有点像原型模式,又有点像单例模式。
```php
name = $name;
}public function render(string $font): string
{
return sprintf('Word %s with font %s', $this->name, $font);
}
}class Character implements Text
{
/**
* Any state stored by the concrete flyweight must be independent of its context.
* For flyweights representing characters, this is usually the corresponding character code.
*/
private string $name;public function __construct(string $name)
{
$this->name = $name;
}public function render(string $font): string
{
// Clients supply the context-dependent information that the flyweight needs to draw itself
// For flyweights representing characters, extrinsic state usually contains e.g. the font.return sprintf('Character %s with font %s', $this->name, $font);
}
}class TextFactory implements Countable
{
/**
* @var Text[]
*/
private array $charPool = [];public function get(string $name): Text
{
if (!isset($this->charPool[$name])) {
$this->charPool[$name] = $this->create($name);
}return $this->charPool[$name];
}private function create(string $name): Text
{
if (strlen($name) == 1) {
return new Character($name);
} else {
return new Word($name);
}
}public function count(): int
{
return count($this->charPool);
}
}
``````php
characters as $char) {
foreach ($this->fonts as $font) {
$flyweight = $factory->get($char);
$rendered = $flyweight->render($font);$this->assertSame(sprintf('Character %s with font %s', $char, $font), $rendered);
}
}
}foreach ($this->fonts as $word) {
$flyweight = $factory->get($word);
$rendered = $flyweight->render('foobar');$this->assertSame(sprintf('Word %s with font foobar', $word), $rendered);
}// Flyweight pattern ensures that instances are shared
// instead of having hundreds of thousands of individual objects
// there must be one instance for every char that has been reused for displaying in different fonts
$this->assertCount(count($this->characters) + count($this->fonts), $factory);
}
}
```### 代理模式
一个类代理另一个类的功能。有点像装饰模式,但是用途不一样。
装饰模式是为装饰的对象增强功能;而代理模式对代理的对象施加控制,但不对对象本身的功能进行增强。
```php
door = $door;
}/**
* @param $password
*/
public function open($password)
{
if ($this->authenticate($password)) {
return $this->door->open();
}
return "Big no! It ain't possible.";
}/**
* @param $password
*
* @return bool
*/
public function authenticate($password)
{
return $password === '$ecr@t';
}/**
*
*/
public function close()
{
return $this->door->close();
}
}
``````php
assertSame("Big no! It ain't possible.", $door->open("hello"));
$this->assertSame("Opening lab door", $door->open('$ecr@t'));
}
}
```### 注册模式
将对象集中存储起来,这会引入全局状态,应当避免使用这个模式,使用依赖注入取代。
```php
service = $this->getMockBuilder(Service::class)->getMock();
}public function testSetAndGetLogger()
{
Registry::set(Registry::LOGGER, $this->service);$this->assertSame($this->service, Registry::get(Registry::LOGGER));
}public function testThrowsExceptionWhenTryingToSetInvalidKey()
{
$this->expectException(InvalidArgumentException::class);Registry::set('foobar', $this->service);
}/**
* notice @runInSeparateProcess here: without it, a previous test might have set it already and
* testing would not be possible. That's why you should implement Dependency Injection where an
* injected class may easily be replaced by a mockup
*
* @runInSeparateProcess
*/
public function testThrowsExceptionWhenTryingToGetNotSetKey()
{
$this->expectException(InvalidArgumentException::class);Registry::get(Registry::LOGGER);
}
}
```# 行为型
### 责任链模式
按顺序处理调用对象链。如果一个对象不能处理调用,它将调用委托给链中的下一个对象,依此类推。
```php
successor = $handler;
}/**
* This approach by using a template method pattern ensures you that
* each subclass will not forget to call the successor
*/
final public function handle(RequestInterface $request): ?string
{
$processed = $this->processing($request);if ($processed === null && $this->successor !== null) {
// the request has not been processed by this handler => see the next
$processed = $this->successor->handle($request);
}return $processed;
}abstract protected function processing(RequestInterface $request): ?string;
}
``````php
chain = new HttpInMemoryCacheHandler(
['/foo/bar?index=1' => 'Hello In Memory!'],
new SlowDatabaseHandler()
);
}public function testCanRequestKeyInFastStorage()
{
$uri = $this->createMock(UriInterface::class);
$uri->method('getPath')->willReturn('/foo/bar');
$uri->method('getQuery')->willReturn('index=1');$request = $this->createMock(RequestInterface::class);
$request->method('getMethod')
->willReturn('GET');
$request->method('getUri')->willReturn($uri);$this->assertSame('Hello In Memory!', $this->chain->handle($request));
}public function testCanRequestKeyInSlowStorage()
{
$uri = $this->createMock(UriInterface::class);
$uri->method('getPath')->willReturn('/foo/baz');
$uri->method('getQuery')->willReturn('');$request = $this->createMock(RequestInterface::class);
$request->method('getMethod')
->willReturn('GET');
$request->method('getUri')->willReturn($uri);$this->assertSame('Hello World!', $this->chain->handle($request));
}
}
```### 命令模式
解耦命令人和执行人。
```php
output = $console;
}/**
* execute and output "Hello World".
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which does all the work
$this->output->write('Hello World');
}
}class AddMessageDateCommand implements UndoableCommand
{
private Receiver $output;/**
* Each concrete command is built with different receivers.
* There can be one, many or completely no receivers, but there can be other commands in the parameters.
*/
public function __construct(Receiver $console)
{
$this->output = $console;
}/**
* Execute and make receiver to enable displaying messages date.
*/
public function execute()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->enableDate();
}/**
* Undo the command and make receiver to disable displaying messages date.
*/
public function undo()
{
// sometimes, there is no receiver and this is the command which
// does all the work
$this->output->disableDate();
}
}class Receiver
{
private bool $enableDate = false;/**
* @var string[]
*/
private array $output = [];public function write(string $str)
{
if ($this->enableDate) {
$str .= ' ['.date('Y-m-d').']';
}$this->output[] = $str;
}public function getOutput(): string
{
return join("\n", $this->output);
}/**
* Enable receiver to display message date
*/
public function enableDate()
{
$this->enableDate = true;
}/**
* Disable receiver to display message date
*/
public function disableDate()
{
$this->enableDate = false;
}
}class Invoker
{
private Command $command;/**
* in the invoker we find this kind of method for subscribing the command
* There can be also a stack, a list, a fixed set ...
*/
public function setCommand(Command $cmd)
{
$this->command = $cmd;
}/**
* executes the command; the invoker is the same whatever is the command
*/
public function run()
{
$this->command->execute();
}
}
``````php
setCommand(new HelloCommand($receiver));
$invoker->run();
$this->assertSame('Hello World', $receiver->getOutput());
}
}
```### 迭代器模式
使对象可迭代,并使其看起来像对象的集合。
```php
author = $author;
$this->title = $title;
}public function getAuthor(): string
{
return $this->author;
}public function getTitle(): string
{
return $this->title;
}public function getAuthorAndTitle(): string
{
return $this->getTitle().' by '.$this->getAuthor();
}
}use Countable;
use Iterator;class BookList implements Countable, Iterator
{
/**
* @var Book[]
*/
private array $books = [];
private int $currentIndex = 0;public function addBook(Book $book)
{
$this->books[] = $book;
}public function removeBook(Book $bookToRemove)
{
foreach ($this->books as $key => $book) {
if ($book->getAuthorAndTitle() === $bookToRemove->getAuthorAndTitle()) {
unset($this->books[$key]);
}
}$this->books = array_values($this->books);
}public function count(): int
{
return count($this->books);
}public function current(): Book
{
return $this->books[$this->currentIndex];
}public function key(): int
{
return $this->currentIndex;
}public function next()
{
$this->currentIndex++;
}public function rewind()
{
$this->currentIndex = 0;
}public function valid(): bool
{
return isset($this->books[$this->currentIndex]);
}
}
``````php
addBook(new Book('Learning PHP Design Patterns', 'William Sanders'));
$bookList->addBook(new Book('Professional Php Design Patterns', 'Aaron Saray'));
$bookList->addBook(new Book('Clean Code', 'Robert C. Martin'));$books = [];
foreach ($bookList as $book) {
$books[] = $book->getAuthorAndTitle();
}$this->assertSame(
[
'Learning PHP Design Patterns by William Sanders',
'Professional Php Design Patterns by Aaron Saray',
'Clean Code by Robert C. Martin',
],
$books
);
}public function testCanIterateOverBookListAfterRemovingBook()
{
$book = new Book('Clean Code', 'Robert C. Martin');
$book2 = new Book('Professional Php Design Patterns', 'Aaron Saray');$bookList = new BookList();
$bookList->addBook($book);
$bookList->addBook($book2);
$bookList->removeBook($book);$books = [];
foreach ($bookList as $book) {
$books[] = $book->getAuthorAndTitle();
}$this->assertSame(
['Professional Php Design Patterns by Aaron Saray'],
$books
);
}public function testCanAddBookToList()
{
$book = new Book('Clean Code', 'Robert C. Martin');$bookList = new BookList();
$bookList->addBook($book);$this->assertCount(1, $bookList);
}public function testCanRemoveBookFromList()
{
$book = new Book('Clean Code', 'Robert C. Martin');$bookList = new BookList();
$bookList->addBook($book);
$bookList->removeBook($book);$this->assertCount(0, $bookList);
}
}
```### 媒介模式
所有组件(称为同事)仅与 Mediator 耦合,这是一件好事,一个好朋友比许多好。 这是此模式的关键特征。
```php
interface ChatRoomMediator
{
public function showMessage(User $user, string $message);
}// Mediator
class ChatRoom implements ChatRoomMediator
{
public function showMessage(User $user, string $message)
{
$time = date('M d, y H:i');
$sender = $user->getName();echo $time.'['.$sender.']:'.$message;
}
}class User
{
protected $name;
protected $chatMediator;public function __construct(string $name, ChatRoomMediator $chatMediator)
{
$this->name = $name;
$this->chatMediator = $chatMediator;
}public function getName()
{
return $this->name;
}public function send($message)
{
$this->chatMediator->showMessage($this, $message);
}
}
``````php
$mediator = new ChatRoom();$john = new User('John Doe', $mediator);
$jane = new User('Jane Doe', $mediator);$john->send('Hi there!');
$jane->send('Hey!');
```### 备忘录模式
提供了将对象恢复到先前状态或访问对象状态的能力。
备忘录模式通过三个对象实现:发起者,看守者和备忘录。
```php
state = $stateToSave;
}public function getState(): State
{
return $this->state;
}
}class State
{
const STATE_CREATED = 'created';
const STATE_OPENED = 'opened';
const STATE_ASSIGNED = 'assigned';
const STATE_CLOSED = 'closed';private string $state;
/**
* @var string[]
*/
private static array $validStates = [
self::STATE_CREATED,
self::STATE_OPENED,
self::STATE_ASSIGNED,
self::STATE_CLOSED,
];public function __construct(string $state)
{
self::ensureIsValidState($state);$this->state = $state;
}private static function ensureIsValidState(string $state)
{
if (!in_array($state, self::$validStates)) {
throw new InvalidArgumentException('Invalid state given');
}
}public function __toString(): string
{
return $this->state;
}
}class Ticket
{
private State $currentState;public function __construct()
{
$this->currentState = new State(State::STATE_CREATED);
}public function open()
{
$this->currentState = new State(State::STATE_OPENED);
}public function assign()
{
$this->currentState = new State(State::STATE_ASSIGNED);
}public function close()
{
$this->currentState = new State(State::STATE_CLOSED);
}public function saveToMemento(): Memento
{
return new Memento(clone $this->currentState);
}public function restoreFromMemento(Memento $memento)
{
$this->currentState = $memento->getState();
}public function getState(): State
{
return $this->currentState;
}
}
``````php
open();
$openedState = $ticket->getState();
$this->assertSame(State::STATE_OPENED, (string) $ticket->getState());$memento = $ticket->saveToMemento();
// assign the ticket
$ticket->assign();
$this->assertSame(State::STATE_ASSIGNED, (string) $ticket->getState());// now restore to the opened state, but verify that the state object has been cloned for the memento
$ticket->restoreFromMemento($memento);$this->assertSame(State::STATE_OPENED, (string) $ticket->getState());
$this->assertNotSame($openedState, $ticket->getState());
}
}
```### 空对象模式
简化了死板的代码,消除了客户端代码中的条件检查,例如 `if (!is_null($obj)) { $obj->callSomething(); }`,只需 `$obj->callSomething();` 就行。
```php
logger = $logger;
}/**
* do something ...
*/
public function doSomething()
{
// notice here that you don't have to check if the logger is set with eg. is_null(), instead just use it
$this->logger->log('We are in '.__METHOD__);
}
}interface Logger
{
public function log(string $str);
}class PrintLogger implements Logger
{
public function log(string $str)
{
echo $str;
}
}class NullLogger implements Logger
{
public function log(string $str)
{
// do nothing
}
}
``````php
expectOutputString('');
$service->doSomething();
}public function testStandardLogger()
{
$service = new Service(new PrintLogger());
$this->expectOutputString('We are in DesignPatterns\Behavioral\NullObject\Service::doSomething');
$service->doSomething();
}
}
```### 观察者模式
改其状态时,都会通知所附加的"观察者"。
```php
observers = new SplObjectStorage();
}public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}public function detach(SplObserver $observer)
{
$this->observers->detach($observer);
}public function changeEmail(string $email)
{
$this->email = $email;
$this->notify();
}public function notify()
{
/** @var SplObserver $observer */
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
}class UserObserver implements SplObserver
{
/**
* @var SplSubject[]
*/
private array $changedUsers = [];/**
* It is called by the Subject, usually by SplSubject::notify()
*/
public function update(SplSubject $subject)
{
$this->changedUsers[] = clone $subject;
}/**
* @return SplSubject[]
*/
public function getChangedUsers(): array
{
return $this->changedUsers;
}
}
``````php
attach($observer);$user->changeEmail('[email protected]');
$this->assertCount(1, $observer->getChangedUsers());
}
}
```### 规格模式
每个规范类中都有一个称为 isSatisfiedBy 的方法,方法判断给定的规则是否满足规范从而返回 true 或 false。
```php
price = $price;
}public function getPrice(): float
{
return $this->price;
}
}interface Specification
{
public function isSatisfiedBy(Item $item): bool;
}class OrSpecification implements Specification
{
/**
* @var Specification[]
*/
private array $specifications;/**
* @param Specification[] $specifications
*/
public function __construct(Specification ...$specifications)
{
$this->specifications = $specifications;
}/*
* if at least one specification is true, return true, else return false
*/
public function isSatisfiedBy(Item $item): bool
{
foreach ($this->specifications as $specification) {
if ($specification->isSatisfiedBy($item)) {
return true;
}
}return false;
}
}class PriceSpecification implements Specification
{
private ?float $maxPrice;
private ?float $minPrice;public function __construct(?float $minPrice, ?float $maxPrice)
{
$this->minPrice = $minPrice;
$this->maxPrice = $maxPrice;
}public function isSatisfiedBy(Item $item): bool
{
if ($this->maxPrice !== null && $item->getPrice() > $this->maxPrice) {
return false;
}if ($this->minPrice !== null && $item->getPrice() < $this->minPrice) {
return false;
}return true;
}
}class AndSpecification implements Specification
{
/**
* @var Specification[]
*/
private array $specifications;/**
* @param Specification[] $specifications
*/
public function __construct(Specification ...$specifications)
{
$this->specifications = $specifications;
}/**
* if at least one specification is false, return false, else return true.
*/
public function isSatisfiedBy(Item $item): bool
{
foreach ($this->specifications as $specification) {
if (!$specification->isSatisfiedBy($item)) {
return false;
}
}return true;
}
}class NotSpecification implements Specification
{
private Specification $specification;public function __construct(Specification $specification)
{
$this->specification = $specification;
}public function isSatisfiedBy(Item $item): bool
{
return !$this->specification->isSatisfiedBy($item);
}
}
``````php
assertFalse($orSpec->isSatisfiedBy(new Item(100)));
$this->assertTrue($orSpec->isSatisfiedBy(new Item(51)));
$this->assertTrue($orSpec->isSatisfiedBy(new Item(150)));
}public function testCanAnd()
{
$spec1 = new PriceSpecification(50, 100);
$spec2 = new PriceSpecification(80, 200);$andSpec = new AndSpecification($spec1, $spec2);
$this->assertFalse($andSpec->isSatisfiedBy(new Item(150)));
$this->assertFalse($andSpec->isSatisfiedBy(new Item(1)));
$this->assertFalse($andSpec->isSatisfiedBy(new Item(51)));
$this->assertTrue($andSpec->isSatisfiedBy(new Item(100)));
}public function testCanNot()
{
$spec1 = new PriceSpecification(50, 100);
$notSpec = new NotSpecification($spec1);$this->assertTrue($notSpec->isSatisfiedBy(new Item(150)));
$this->assertFalse($notSpec->isSatisfiedBy(new Item(50)));
}
}
```### 状态模式
将一系列行为抽象成状态。状态的变迁和产生的行为由内部实现,外部只需要调用接口。
```php
state = new StateCreated();return $order;
}public function setState(State $state)
{
$this->state = $state;
}public function proceedToNext()
{
$this->state->proceedToNext($this);
}public function toString()
{
return $this->state->toString();
}
}interface State
{
public function proceedToNext(OrderContext $context);public function toString(): string;
}class StateCreated implements State
{
public function proceedToNext(OrderContext $context)
{
$context->setState(new StateShipped());
}public function toString(): string
{
return 'created';
}
}class StateShipped implements State
{
public function proceedToNext(OrderContext $context)
{
$context->setState(new StateDone());
}public function toString(): string
{
return 'shipped';
}
}class StateDone implements State
{
public function proceedToNext(OrderContext $context)
{
// there is nothing more to do
}public function toString(): string
{
return 'done';
}
}
``````php
assertSame('created', $orderContext->toString());
}public function testCanProceedToStateShipped()
{
$contextOrder = OrderContext::create();
$contextOrder->proceedToNext();$this->assertSame('shipped', $contextOrder->toString());
}public function testCanProceedToStateDone()
{
$contextOrder = OrderContext::create();
$contextOrder->proceedToNext();
$contextOrder->proceedToNext();$this->assertSame('done', $contextOrder->toString());
}public function testStateDoneIsTheLastPossibleState()
{
$contextOrder = OrderContext::create();
$contextOrder->proceedToNext();
$contextOrder->proceedToNext();
$contextOrder->proceedToNext();$this->assertSame('done', $contextOrder->toString());
}
}
```### 策略模式
允许您根据情况切换算法或策略。
```php
comparator = $comparator;
}public function executeStrategy(array $elements): array
{
uasort($elements, [$this->comparator, 'compare']);return $elements;
}
}interface Comparator
{
/**
* @param mixed $a
* @param mixed $b
*
* @return int
*/
public function compare($a, $b): int;
}class DateComparator implements Comparator
{
public function compare($a, $b): int
{
$aDate = new DateTime($a['date']);
$bDate = new DateTime($b['date']);return $aDate <=> $bDate;
}
}class IdComparator implements Comparator
{
public function compare($a, $b): int
{
return $a['id'] <=> $b['id'];
}
}
``````php
2], ['id' => 1], ['id' => 3]],
['id' => 1],
],
[
[['id' => 3], ['id' => 2], ['id' => 1]],
['id' => 1],
],
];
}public function provideDates()
{
return [
[
[['date' => '2014-03-03'], ['date' => '2015-03-02'], ['date' => '2013-03-01']],
['date' => '2013-03-01'],
],
[
[['date' => '2014-02-03'], ['date' => '2013-02-01'], ['date' => '2015-02-02']],
['date' => '2013-02-01'],
],
];
}/**
* @dataProvider provideIntegers
*
* @param array $collection
* @param array $expected
*/
public function testIdComparator($collection, $expected)
{
$obj = new Context(new IdComparator());
$elements = $obj->executeStrategy($collection);$firstElement = array_shift($elements);
$this->assertSame($expected, $firstElement);
}/**
* @dataProvider provideDates
*
* @param array $collection
* @param array $expected
*/
public function testDateComparator($collection, $expected)
{
$obj = new Context(new DateComparator());
$elements = $obj->executeStrategy($collection);$firstElement = array_shift($elements);
$this->assertSame($expected, $firstElement);
}
}
```### 模板方法模式
允许子类重新定义父类的某些方法,而不得更改父类的结构。
```php
thingsToDo[] = $this->buyAFlight();
$this->thingsToDo[] = $this->takePlane();
$this->thingsToDo[] = $this->enjoyVacation();
$buyGift = $this->buyGift();if ($buyGift !== null) {
$this->thingsToDo[] = $buyGift;
}$this->thingsToDo[] = $this->takePlane();
}/**
* This method must be implemented, this is the key-feature of this pattern.
*/
abstract protected function enjoyVacation(): string;/**
* This method is also part of the algorithm but it is optional.
* You can override it only if you need to
*/
protected function buyGift(): ?string
{
return null;
}private function buyAFlight(): string
{
return 'Buy a flight ticket';
}private function takePlane(): string
{
return 'Taking the plane';
}/**
* @return string[]
*/
public function getThingsToDo(): array
{
return $this->thingsToDo;
}
}class BeachJourney extends Journey
{
protected function enjoyVacation(): string
{
return "Swimming and sun-bathing";
}
}class CityJourney extends Journey
{
protected function enjoyVacation(): string
{
return "Eat, drink, take photos and sleep";
}protected function buyGift(): ?string
{
return "Buy a gift";
}
}
``````php
takeATrip();$this->assertSame(
['Buy a flight ticket', 'Taking the plane', 'Swimming and sun-bathing', 'Taking the plane'],
$beachJourney->getThingsToDo()
);
}public function testCanGetOnAJourneyToACity()
{
$cityJourney = new CityJourney();
$cityJourney->takeATrip();$this->assertSame(
[
'Buy a flight ticket',
'Taking the plane',
'Eat, drink, take photos and sleep',
'Buy a gift',
'Taking the plane'
],
$cityJourney->getThingsToDo()
);
}
}
```### 访问者模式
将访问者和被访问者分离。如果不分离,比如已经写了一个方法来遍历一个数组,如果换种方式遍历就会需要修改类中的方法,这是面向对象的大忌。如果分离了,就可以新写一个类。
```php
visited[] = $role;
}public function visitUser(User $role)
{
$this->visited[] = $role;
}/**
* @return Role[]
*/
public function getVisited(): array
{
return $this->visited;
}
}interface Role
{
public function accept(RoleVisitor $visitor);
}class User implements Role
{
private string $name;public function __construct(string $name)
{
$this->name = $name;
}public function getName(): string
{
return sprintf('User %s', $this->name);
}public function accept(RoleVisitor $visitor)
{
$visitor->visitUser($this);
}
}class Group implements Role
{
private string $name;public function __construct(string $name)
{
$this->name = $name;
}public function getName(): string
{
return sprintf('Group: %s', $this->name);
}public function accept(RoleVisitor $visitor)
{
$visitor->visitGroup($this);
}
}
``````php
visitor = new RecordingVisitor();
}public function provideRoles()
{
return [
[new User('Dominik')],
[new Group('Administrators')],
];
}/**
* @dataProvider provideRoles
*/
public function testVisitSomeRole(Role $role)
{
$role->accept($this->visitor);
$this->assertSame($role, $this->visitor->getVisited()[0]);
}
}
```