单例模式
在整个应用中只生成一个实例,不允许重复创建实例。有利于减少重复创建实例的开销
应用场景:
- 数据库连接
- 日志记录
- 文件锁
#文件结构
code
|-- Singleton
|-- Singleton.php
|--Tests
|-- SingletonTest.php
|-- phpunit.xml
|-- composer.json
#Singleton.php
<?php declare(strict_types=1);
namespace DesignPattern\Singleton;
class Singleton {
private static ?Singleton $instance = null; #php7的类型提示,?Singleton 表示可以为null或Singleton
/**
* 惰性加载实例 (第一次使用时创建实例)
*/
public static function getInstance(): Singleton
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
/**
* 避免从外部实例化
* 只能有一种实例化方式:Singleton::getInstance()
*/
private function __construct()
{
}
/**
* 避免被克隆 (会创建第二个实例)
*/
private function __clone()
{
}
/**
* 避免反序列化unserialize() (会创建第二个实例)
*/
private function __wakeup()
{
}
}
#Tests/SingletonTest.php
<?php declare(strict_types=1);
namespace DesignPattern\Singleton\Tests;
use DesignPattern\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->assertEquals($firstCall, $secondCall);
}
}
工厂模式
使用一个工厂类来统一创建其他类的实例(避免每次使用都使用new来实例化),这些类通常继承同一个抽象类,或实现同一个接口。
- 工厂模式有利于代码解耦:
如果将来要修改某个类名,参数的时候,只需要修改工厂类实现即可,不用修改每个使用该类的地方;
如果类的实例化比较复杂,使用工厂类还可以使类的实例化更简便
示例:
#文件结构
code
|-- Factory
|-- Logger.php
|-- FileLogger.php
|-- StdoutLogger.php
|-- LoggerFactory.php
|-- FileLoggerFactory.php
|-- StdoutLoggerFactory.php
|--Tests
|-- FactoryMethodTest.php
|-- phpunit.xml
|-- composer.json
#Logger.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
interface Logger {
public function log(string $message);
}
#FileLogger.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
class FileLogger implements Logger {
private string $filePath;
public function __construct(string $filePath)
{
$this->filePath = $filePath;
}
public function log(string $message)
{
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}
#StdoutLogger.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
class StdoutLogger implements Logger {
public function log(string $message){
echo $message;
}
}
#LoggerFactory.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
interface LoggerFactory {
public function createLogger(): Logger;
}
#FileLoggerFactory.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
class FileLoggerFactory implements LoggerFactory {
private string $filePath;
public function __construct(string $filePath)
{
$this->filePath = $filePath;
}
public function createLogger(): Logger
{
return new FileLogger($this->filePath);
}
}
#StdoutLoggerFactory.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
class StdoutLoggerFactory implements LoggerFactory {
public function createLogger(): Logger
{
return new StdoutLogger();
}
}
#Tests/FactoryMethodTest.php
<?php declare(strict_types=1);
namespace DesignPattern\Factory;
use DesignPattern\Factory\FileLogger;
use DesignPattern\Factory\FileLoggerFactory;
use DesignPattern\Factory\StdoutLogger;
use DesignPattern\Factory\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);
}
}
观察者模式
也可以看作发布/订阅模式,实现方式很简单:一个对象提供一个方法,让另一个对象(观察者)注册自己,当对象发生变化时,调用注册的观察者的方法通知观察者。
示例:
#文件结构
code
|-- Factory
|-- User.php
|-- UserObserver.php
|--Tests
|--ObserverTest.php
|-- phpunit.xml
|-- composer.json
#User.php
<?php declare(strict_types=1);
namespace DesignPattern\Observer;
use SplSubject;
use SplObserver;
use SplObjectStorage;
class User implements SplSubject {
private string $email;
private SplObjectStorage $observers;
public function __construct()
{
$this->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);
}
}
}
#UserObserver.php
<?php declare(strict_types=1);
namespace DesignPattern\Observer;
use SplObserver;
use SplSubject;
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;
}
}
#Tests/ObserverTest.php
<?php declare(strict_types=1);
namespace DesignPattern\Observer\Tests;
use DesignPattern\Observer\User;
use DesignPattern\Observer\UserObserver;
use PHPUnit\Framework\TestCase;
class ObserverTest extends TestCase {
public function testChangeInUserLeadsToUserObserverBeingNotified()
{
$observer = new UserObserver();
$user = new User();
$user->attach($observer);
$user->changeEmail('foo@bar.com');
$this->assertCount(1, $observer->getChangedUsers());
}
}
策略模式
主要用于在多种策略间快速切换,利用扩展:多种策略实现同一个接口,功能相似,但相互独立
示例:
#文件结构
code
|--Stragy
|--Context.php
|--Comparator.php
|--IdComparator.php
|--DateComparator.php
|--Tests
|--StrategyTest.php
|-- phpunit.xml
|-- composer.json
#Context.php
<?php declare(strict_types=1);
namespace DesignPattern\Strategy;
class Context {
private Comparator $comparator;
public function __construct(Comparator $comparator)
{
$this->comparator = $comparator;
}
public function executeStrategy(array $elements): array
{
uasort($elements, [$this->comparator, 'compare']);
return $elements;
}
}
#Comparator.php
<?php declare(strict_types=1);
namespace DesignPattern\Strategy;
interface Comparator {
/**
* @param mixed $a
* @param mixed $b
*
* @return int
*/
public function compare($a, $b): int;
}
#IdComparator.php
<?php declare(strict_types=1);
namespace DesignPattern\Strategy;
class IdComparator implements Comparator {
public function compare($a, $b): int
{
return $a['id'] <=> $b['id'];
}
}
#DateComparator.php
<?php declare(strict_types=1);
namespace DesignPattern\Strategy;
use DateTime;
class DateComparator implements Comparator {
public function compare($a, $b): int
{
$aDate = new DateTime($a['date']);
$bDate = new DateTime($b['date']);
return $aDate <=> $bDate;
}
}
#Tests/StrategyTest.php
<?php declare(strict_types=1);
namespace DesignPattern\Strategy\Tests;
use DesignPattern\Strategy\Context;
use DesignPattern\Strategy\DateComparator;
use DesignPattern\Strategy\IdComparator;
use PHPUnit\Framework\TestCase;
class StrategyTest extends TestCase {
public function provideIntegers()
{
return [
[
[['id' => 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);
}
}
控制链模式
多个对象按顺序执行,如果某个对象不能处理,则交由下个对象处理。比如缓存,首先缓存实例处理,如果不能处理,则使用数据库实例。
示例:
#文件结构
code
|--Stragy
|--Handler.php
|--FastStorage.php
|--SlowStorage.php
|--Tests
|--ChainTest.php
|-- phpunit.xml
|-- composer.json
#Handler.php
<?php declare(strict_types=1);
namespace DesignPattern\Chain;
use Psr\Http\Message\RequestInterface;
abstract class Handler
{
private ?Handler $successor = null;
public function __construct(Handler $handler = null)
{
$this->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;
}
#FastStorage.php
<?php declare(strict_types=1);
namespace DesignPattern\Chain;
use Psr\Http\Message\RequestInterface;
class HttpInMemoryCacheHandler extends Handler
{
private array $data;
public function __construct(array $data, ?Handler $successor = null)
{
parent::__construct($successor);
$this->data = $data;
}
protected function processing(RequestInterface $request): ?string
{
$key = sprintf(
'%s?%s',
$request->getUri()->getPath(),
$request->getUri()->getQuery()
);
if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
return $this->data[$key];
}
return null;
}
}
#SlowStorage.php
<?php declare(strict_types=1);
namespace DesignPattern\Chain;
use Psr\Http\Message\RequestInterface;
class SlowDatabaseHandler extends Handler
{
protected function processing(RequestInterface $request): ?string
{
// this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results
return 'Hello World!';
}
}
# ChainTest.php
<?php declare(strict_types=1);
namespace DesignPattern\Chain\Tests;
use DesignPattern\Chain\Handler;
use DesignPattern\Chain\HttpInMemoryCacheHandler;
use DesignPattern\Chain\SlowDatabaseHandler;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
class ChainTest extends TestCase
{
private Handler $chain;
protected function setUp(): void
{
$this->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));
}
}
测试
如果要运行以上测试文件,需使用composer自动加载,和PHPUnit来测试:
- 创建以下文件
composer.json 放在code目录下
{
"autoload": {
"psr-4": {
"DesignPattern\\": "./"
}
},
"require": {
"phpunit/phpunit": "^9.0",
"psr/http-message": "^1.0"
}
}
phpunit.xml 编排测试
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php">
<testsuites>
<testsuite name="Design Pattern">
<directory suffix="Test.php">*/Tests</directory>
</testsuite>
</testsuites>
</phpunit>
- code目录下执行
composer install
- code目录下执行:
./vendor/bin/phpunit
运行所有测试文件,./vendor/bin/phpunit ./Factory
运行Factory目录下的文件
$ cv ./vendor/bin/phpunit
PHPUnit 9.0.1 by Sebastian Bergmann and contributors.
.......... 10 / 10 (100%)
Time: 41 ms, Memory: 6.00 MB
OK (10 tests, 11 assertions)
如果出现类找不到的异常,请执行composer dump-autoload -o
更新自动加载