美文网首页
【PHP】常见的五种设计模式

【PHP】常见的五种设计模式

作者: 马蹄哒 | 来源:发表于2020-03-09 16:52 被阅读0次

    单例模式

    在整个应用中只生成一个实例,不允许重复创建实例。有利于减少重复创建实例的开销
    应用场景:

    • 数据库连接
    • 日志记录
    • 文件锁
    #文件结构
    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来测试:

    1. 创建以下文件
      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>
    
    1. code目录下执行composer install
    2. 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更新自动加载


    参考:designpatternsphp

    相关文章

      网友评论

          本文标题:【PHP】常见的五种设计模式

          本文链接:https://www.haomeiwen.com/subject/gwbdqhtx.html