二、GoF 设计模式

有很多东西可以让你成为一名优秀的软件开发人员。设计模式的知识和使用就是其中之一。设计模式使开发人员能够使用各种软件交互的知名名称进行交流。无论某人是 PHP、Python、C#、Ruby 还是任何其他语言开发人员,设计模式都为频繁出现的软件问题提供了与语言无关的解决方案。

设计模式的概念出现于 1994 年,作为可重用面向对象软件一书元素的一部分。这本书详细介绍了 23 种不同的设计模式,由四位作者埃里希·伽玛、理查德·赫尔姆、拉尔夫·约翰逊和约翰·维利塞德斯撰写。作者通常被称为四人帮GoF),所呈现的设计模式有时被称为 GoF 设计模式。在 20 多年后的今天,如果不将设计模式作为实现的一部分,设计可扩展、可重用、可维护和可适应性的软件几乎是不可能的。

本章将介绍三种类型的设计模式:

  • 创造性的
  • 结构的
  • 关于行为的

在本章中,我们将不深入探讨每一个理论,因为这本身就是整本书的重要内容。接下来,我们将更多地关注每个模式的简单 PHP 实现示例,这样我们就可以更直观地了解情况。

创作模式

创造性的模式,顾名思义,为我们创建对象,因此我们不必直接实例化它们。实现创建模式为应用程序提供了一定程度的灵活性,应用程序本身可以决定在给定时间实例化哪些对象。以下是我们归类为创造模式的模式列表:

  • 抽象工厂模式
  • 建造者模式
  • 工厂方法模式
  • 原型模式
  • 单例模式

参见https://en.wikipedia.org/wiki/Creational_pattern 了解更多关于创意设计模式的信息。

抽象工厂模式

构建可移植的应用程序需要大量的依赖项封装。抽象工厂通过抽象相关或从属对象族的创建来实现这一点。客户机从不直接创建这些平台对象,工厂为它们创建,这样就可以在不更改使用它们的代码的情况下交换具体的实现,即使在运行时也是如此。

以下是可能的抽象工厂模式实现的示例:

interface Button {
    public function render();
}

interface GUIFactory {
    public function createButton();
}

class SubmitButton implements Button {
    public function render() {
        echo 'Render Submit Button';
    }
}

class ResetButton implements Button {
    public function render() {
        echo 'Render Reset Button';
    }
}

class SubmitFactory implements GUIFactory {
    public function createButton() {
        return new SubmitButton();
    }
}

class ResetFactory implements GUIFactory {
    public function createButton() {
        return new ResetButton();
    }
}

// Client
$submitFactory = new SubmitFactory();
$button = $submitFactory->createButton();
$button->render();

$resetFactory = new ResetFactory();
$button = $resetFactory->createButton();
$button->render();

我们从创建接口Button开始,之后由我们的SubmitButtonResetButton具体类实现。GUIFactoryResetFactory实现GUIFactory接口,指定createButton方式。然后客户端简单地实例化工厂并调用createButton,它返回一个适当的按钮实例,我们称之为render方法。

构建器模式

生成器模式将复杂对象的构造与其表示分离,使得相同的构造过程可以创建不同的表示。当一些创造性模式在一次调用中构建产品时,构建器模式在主管的控制下一步一步地构建产品。

以下是构建器模式实现的示例:

class Car {
    public function getWheels() {
        /* implementation... */
    }

    public function setWheels($wheels) {
        /* implementation... */
    }

    public function getColour($colour) {
        /* implementation... */
    }

    public function setColour() {
        /* implementation... */
    }
}

interface CarBuilderInterface {
    public function setColour($colour);
    public function setWheels($wheels);
    public function getResult();
}

class CarBuilder implements CarBuilderInterface {
    private $car;

    public function __construct() {
        $this->car = new Car();
    }

    public function setColour($colour) {
        $this->car->setColour($colour);
        return $this;
    }

    public function setWheels($wheels) {
        $this->car->setWheels($wheels);
        return $this;
    }

    public function getResult() {
        return $this->car;
    }
}

class CarBuildDirector {
    private $builder;

    public function __construct(CarBuilder $builder) {
        $this->builder = $builder;
    }

    public function build() {
        $this->builder->setColour('Red');
        $this->builder->setWheels(4);

        return $this;
    }

    public function getCar() {
        return $this->builder->getResult();
    }
}

// Client
$carBuilder = new CarBuilder();
$carBuildDirector = new CarBuildDirector($carBuilder);
$car = $carBuildDirector->build()->getCar();

我们从创建一个具体的Car类开始,该类有几个方法定义汽车的一些基本特性。然后我们创建了一个CarBuilderInterface,它将控制其中一些特征,并得到最终结果(car。然后,混凝土类CarBuilder实现了CarBuilderInterface,接着是混凝土类CarBuildDirector,定义了 build 和getCar方法。然后,客户端简单地实例化了一个新的CarBuilder实例,并将其作为构造函数参数传递给一个新的CarBuildDirector实例。最后,我们调用了CarBuildDirectorbuildgetCar方法来获得实际的 carCar实例。

工厂方法模式

factory方法模式处理创建对象的问题,而不必指定要创建的对象的确切类别。

以下是 factory 方法模式实现的示例:

interface Product {
    public function getType();
}

interface ProductFactory {
    public function makeProduct();
}

class SimpleProduct implements Product {
    public function getType() {
        return 'SimpleProduct';
    }
}

class SimpleProductFactory implements ProductFactory {
    public function makeProduct() {
        return new SimpleProduct();
    }
}

/* Client */
$factory = new SimpleProductFactory();
$product = $factory->makeProduct();
echo $product->getType(); //outputs: SimpleProduct

我们首先创建了一个ProductFactoryProduct接口。SimpleProductFactory实现ProductFactory并通过makeProduct方法返回新的product实例。SimpleProduct类实现Product,并返回产品类型。最后,客户端创建实例SimpleProductFactory,并对其调用makeProduct方法。makeProduct返回Product的实例,其getType方法返回SimpleProduct字符串。

原型模式

原型模式通过克隆复制其他对象。这意味着我们没有使用new关键字来实例化新对象。PHP 提供了一个clone关键字,该关键字生成对象的浅拷贝,从而提供了非常直接的原型模式实现。浅复制不复制引用,只将值复制到新对象。我们可以在我们的类上进一步使用 magic__clone方法,以实现更健壮的克隆行为。

以下是原型模式实现的示例:

class User {
    public $name;
    public $email;
}

class Employee extends User {
    public function __construct() {
        $this->name = 'Johhn Doe';
        $this->email = 'john.doe@fake.mail';
    }

    public function info() {
        return sprintf('%s, %s', $this->name, $this->email);
    }

    public function __clone() {
        /* additional changes for (after)clone behavior? */
    }
}

$employee = new Employee();
echo $employee->info();

$director = clone $employee;
$director->name = 'Jane Doe';
$director->email = 'jane.doe@fake.mail';
echo $director->info(); //outputs: Jane Doe, jane.doe@fake.mail

我们首先创建了一个简单的User类。Employee然后扩展User,同时在其构造函数中设置nameemail。然后,客户端通过new关键字实例化Employee,并将其克隆到director变量中。$director变量现在是一个新实例,它不是由new关键字生成的,而是通过使用clone关键字进行克隆生成的。更改$director上的nameemail不会影响$employee

单子模式

singleton 模式的目的是将类的实例化限制为单个对象。它是通过在类中创建一个方法来实现的,该方法在不存在该类的情况下创建该类的新实例。如果对象实例已经存在,则该方法只返回对现有对象的引用。

以下是单例模式实现的示例:

class Logger {
    private static $instance;

    public static function getInstance() {
        if (!isset(self::$instance)) {
            self::$instance = new self;
        }

        return self::$instance;
    }

    public function logNotice($msg) {
        return 'logNotice: ' . $msg;
    }

    public function logWarning($msg) {
        return 'logWarning: ' . $msg;
    }

    public function logError($msg) {
        return 'logError: ' . $msg;
    }
}

// Client
echo Logger::getInstance()->logNotice('test-notice');
echo Logger::getInstance()->logWarning('test-warning');
echo Logger::getInstance()->logError('test-error');
// Outputs:
// logNotice: test-notice
// logWarning: test-warning
// logError: test-error

我们从开始创建一个Logger类,其中包含一个静态$instance成员和始终返回该类单个实例的getInstance方法。然后,我们添加了一些示例方法来演示客户机在单个实例上执行各种方法。

结构模式

结构模式处理类和对象的组合。使用接口或抽象类和方法,它们定义组合对象的方法,从而获得新的功能。以下是我们归类为结构模式的模式列表:

  • 适配器
  • 混合成的
  • 室内装修设计师
  • 外观
  • 飞锤
  • 代理

参见https://en.wikipedia.org/wiki/Structural_pattern 了解更多有关结构设计模式的信息。

适配器模式

适配器模式允许从另一个接口使用现有类的接口,基本上,通过将一个类的接口转换为另一个类期望的接口,帮助两个不兼容的接口协同工作。

以下是适配器模式实现的示例:

class Stripe {
    public function capturePayment($amount) {
        /* Implementation... */
    }

    public function authorizeOnlyPayment($amount) {
        /* Implementation... */
    }

    public function cancelAmount($amount) {
        /* Implementation... */
    }
}

interface PaymentService {
    public function capture($amount);
    public function authorize($amount);
    public function cancel($amount);
}

class StripePaymentServiceAdapter implements PaymentService {
    private $stripe;

    public function __construct(Stripe $stripe) {
        $this->stripe = $stripe;
    }

    public function capture($amount) {
        $this->stripe->capturePayment($amount);
    }

    public function authorize($amount) {
        $this->stripe->authorizeOnlyPayment($amount);
    }

    public function cancel($amount) {
        $this->stripe->cancelAmount($amount);
    }
}

// Client
$stripe = new StripePaymentServiceAdapter(new Stripe());
$stripe->authorize(49.99);
$stripe->capture(19.99);
$stripe->cancel(9.99);

我们从创建一个具体的Stripe类开始。然后我们用一些基本的支付处理方法定义了接口PaymentServiceStripePaymentServiceAdapter实现PaymentService接口,提供付款处理方式的具体实现。最后,客户端实例化StripePaymentServiceAdapter并执行支付处理方法。

桥型

桥接模式用于将类或抽象与其实现解耦时,允许它们独立更改。当类及其实现经常变化时,这很有用。

以下是桥接模式实现的示例:

interface MailerInterface {
    public function setSender(MessagingInterface $sender);
    public function send($body);
}

abstract class Mailer implements MailerInterface {
    protected $sender;

    public function setSender(MessagingInterface $sender) {
        $this->sender = $sender;
    }
}

class PHPMailer extends Mailer {
    public function send($body) {
        $body .= "\n\n Sent from a phpmailer.";
        return $this->sender->send($body);
    }
}

class SwiftMailer extends Mailer {
    public function send($body) {
        $body .= "\n\n Sent from a SwiftMailer.";
        return $this->sender->send($body);
    }
}

interface MessagingInterface {
    public function send($body);
}

class TextMessage implements MessagingInterface {
    public function send($body) {
        echo 'TextMessage > send > $body: ' . $body;
    }
}

class HtmlMessage implements MessagingInterface {
    public function send($body) {
        echo 'HtmlMessage > send > $body: ' . $body;
    }
}

// Client
$phpmailer = new PHPMailer();
$phpmailer->setSender(new TextMessage());
$phpmailer->send('Hi!');

$swiftMailer = new SwiftMailer();
$swiftMailer->setSender(new HtmlMessage());
$swiftMailer->send('Hello!');

我们从创建MailerInterface开始。然后,具体的Mailer类实现了MailerInterface,为PHPMailerSwiftMailer提供了一个基类。然后我们定义MessagingInterface,它由TextMessageHtmlMessage类实现。最后,客户端实例化PHPMailerSwiftMailer,在调用send方法之前传递TextMessageHtmlMessage的实例。

复合图案

复合模式是关于通过公共接口将对象的层次结构作为单个对象处理。其中,对象由三个结构组成,客户机对底层结构中的更改不感兴趣,因为它只使用公共接口。

以下是复合模式实现的示例:

interface Graphic {
    public function draw();
}

class CompositeGraphic implements Graphic {
    private $graphics = array();

    public function add($graphic) {
        $objId = spl_object_hash($graphic);
        $this->graphics[$objId] = $graphic;
    }

    public function remove($graphic) {
        $objId = spl_object_hash($graphic);
        unset($this->graphics[$objId]);
    }

    public function draw() {
        foreach ($this->graphics as $graphic) {
            $graphic->draw();
        }
    }
}

class Circle implements Graphic {
    public function draw()
    {
        echo 'draw-circle';
    }
}

class Square implements Graphic {
    public function draw() {
        echo 'draw-square';
    }
}

class Triangle implements Graphic {
    public function draw() {
        echo 'draw-triangle';
    }
}

$circle = new Circle();
$square = new Square();
$triangle = new Triangle();

$compositeObj1 = new CompositeGraphic();
$compositeObj1->add($circle);
$compositeObj1->add($triangle);
$compositeObj1->draw();

$compositeObj2 = new CompositeGraphic();
$compositeObj2->add($circle);
$compositeObj2->add($square);
$compositeObj2->add($triangle);
$compositeObj2->remove($circle);
$compositeObj2->draw();

我们从创建Graphic接口开始。然后我们创建了CompositeGraphicCircleSquareTriangle,它们都实现了Graphic接口。除了从Graphic接口实现draw方法外,CompositeGraphic还增加了两种方法,用于跟踪添加到其中的图形的内部收集。然后,客户端实例化所有这些Graphic类,将它们全部添加到CompositeGraphic,然后调用draw方法。

装饰图案

装饰器模式允许将行为添加到单个对象实例中,而不会影响同一类的其他实例的行为。我们可以定义多个装饰器,每个装饰器都添加了新功能。

以下是装饰器模式实现的示例:

interface LoggerInterface {
    public function log($message);
}

class Logger implements LoggerInterface {
    public function log($message) {
        file_put_contents('app.log', $message, FILE_APPEND);
    }
}

abstract class LoggerDecorator implements LoggerInterface {
    protected $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    abstract public function log($message);
}

class ErrorLoggerDecorator extends LoggerDecorator {
    public function log($message) {
        $this->logger->log('ERROR: ' . $message);
    }

}

class WarningLoggerDecorator extends LoggerDecorator {
    public function log($message) {
        $this->logger->log('WARNING: ' . $message);
    }
}

class NoticeLoggerDecorator extends LoggerDecorator {
    public function log($message) {
        $this->logger->log('NOTICE: ' . $message);
    }
}

$logger = new Logger();
$logger->log('Resource not found.');

$logger = new Logger();
$logger = new ErrorLoggerDecorator($logger);
$logger->log('Invalid user role.');

$logger = new Logger();
$logger = new WarningLoggerDecorator($logger);
$logger->log('Missing address parameters.');

$logger = new Logger();
$logger = new NoticeLoggerDecorator($logger);
$logger->log('Incorrect type provided.');

我们从创建LoggerInterface开始,使用一个简单的log方法。然后我们定义了LoggerLoggerDecorator,它们都实现了LoggerInterface。其次是实现LoggerDecoratorErrorLoggerDecoratorWarningLoggerDecoratorNoticeLoggerDecorator。最后,客户机部分实例化logger三次,并将不同的装饰器传递给它。

立面图案

facade 模式用于通过更简单的界面简化大型系统的复杂性。它通过客户机使用的单个包装器类为大多数常见任务提供方便的方法来实现。

下面是 facade 模式实现的示例:

class Product {
    public function getQty() {
        // Implementation
    }
}

class QuickOrderFacade {
    private $product = null;
    private $orderQty = null;

    public function __construct($product, $orderQty) {
        $this->product = $product;
        $this->orderQty = $orderQty;
    }

    public function generateOrder() {
        if ($this->qtyCheck()) {
            $this->addToCart();
            $this->calculateShipping();
            $this->applyDiscount();
            $this->placeOrder();
        }
    }

    private function addToCart() {
        // Implementation...
    }

    private function qtyCheck() {
        if ($this->product->getQty() > $this->orderQty) {
            return true;
        } else {
            return true;
        }
    }

    private function calculateShipping() {
        // Implementation...
    }

    private function applyDiscount() {
        // Implementation...
    }

    private function placeOrder() {
        // Implementation...
    }
}

// Client
$order = new QuickOrderFacade(new Product(), $qty);
$order->generateOrder();

我们从创建一个Product类开始,使用一个getQty方法。然后我们创建了一个QuickOrderFacade类,该类通过constructor接受product实例和数量,并进一步提供了聚合所有订单生成操作的generateOrder方法。最后,客户端实例化product,并将其传递到QuickOrderFacade实例上,对其调用generateOrder

飞锤图案

flyweight 模式是关于性能和资源减少的,在相似的对象之间共享尽可能多的数据。这意味着在实现中共享相同的类实例。当需要创建大量相同的类实例时,这种方法效果最好。

以下是 flyweight 图案实现的示例:

interface Shape {
    public function draw();
}

class Circle implements Shape {
    private $colour;
    private $radius;

    public function __construct($colour) {
        $this->colour = $colour;
    }

    public function draw() {
        echo sprintf('Colour %s, radius %s.', $this->colour, $this->radius);
    }

    public function setRadius($radius) {
        $this->radius = $radius;
    }
}

class ShapeFactory {
    private $circleMap;

    public function getCircle($colour) {
        if (!isset($this->circleMap[$colour])) {
            $circle = new Circle($colour);
            $this->circleMap[$colour] = $circle;
        }

        return $this->circleMap[$colour];
    }
}

// Client
$shapeFactory = new ShapeFactory();
$circle = $shapeFactory->getCircle('yellow');
$circle->setRadius(10);
$circle->draw();

$shapeFactory = new ShapeFactory();
$circle = $shapeFactory->getCircle('orange');
$circle->setRadius(15);
$circle->draw();

$shapeFactory = new ShapeFactory();
$circle = $shapeFactory->getCircle('yellow');
$circle->setRadius(20);
$circle->draw();

我们首先创建一个Shape接口,使用一个draw方法。然后我们定义了实现Shape接口的Circle类,然后定义了类ShapeFactory类。在ShapeFactory中,getCircle方法基于color选项返回新Circle的实例。最后,客户端实例化几个ShapeFactory对象,以不同的颜色传递给getCircle方法调用。

代理模式

代理设计模式在幕后充当原始对象的接口。它可以充当一个简单的转发包装器,甚至可以围绕其包装的对象提供附加功能。额外添加功能的示例可能是延迟加载或缓存,这可能补偿原始对象的资源密集型操作。

以下是代理模式实现的示例:

interface ImageInterface {
    public function draw();
}

class Image implements ImageInterface {
    private $file;

    public function __construct($file) {
        $this->file = $file;
        sleep(5); // Imagine resource intensive image load
    }

    public function draw() {
        echo 'image: ' . $this->file;
    }
}

class ProxyImage implements ImageInterface {
    private $image = null;
    private $file;

    public function __construct($file) {
        $this->file = $file;
    }

    public function draw() {
        if (is_null($this->image)) {
            $this->image = new Image($this->file);
        }

        $this->image->draw();
    }
}

// Client
$image = new Image('image.png'); // 5 seconds
$image->draw();

$image = new ProxyImage('image.png'); // 0 seconds
$image->draw();

我们从开始创建一个ImageInterface,使用一个draw方法。然后我们定义了ImageProxyImage类,这两个类都扩展了ImageInterface。在Image类的__construct中,我们使用sleep方法调用模拟资源密集型操作。最后,客户端实例化ImageProxyImage,显示两者之间的执行时间差。

行为模式

行为模式解决不同对象之间的沟通难题。它们描述了不同的对象和类如何相互发送消息以使事情发生。以下是我们归类为行为模式的模式列表:

  • 责任链
  • 命令
  • 口译译员
  • 迭代器
  • 调解人
  • 纪念品
  • 观察者
  • 状态
  • 策略
  • 模板法
  • 来访者

责任链模式

责任链模式通过允许多个对象以链式方式处理请求,将请求的发送方与其接收方分离。可以将各种类型的处理对象动态添加到链中。使用递归合成链允许无限数量的处理对象。

以下是责任链模式实施的示例:

abstract class SocialNotifier {
    private $notifyNext = null;

    public function notifyNext(SocialNotifier $notifier) {
        $this->notifyNext = $notifier;
        return $this->notifyNext;
    }

    final public function push($message) {
        $this->publish($message);

        if ($this->notifyNext !== null) {
            $this->notifyNext->push($message);
        }
    }

    abstract protected function publish($message);
}

class TwitterSocialNotifier extends SocialNotifier {
    public function publish($message) {
        // Implementation...
    }
}

class FacebookSocialNotifier extends SocialNotifier {
    protected function publish($message) {
        // Implementation...
    }
}

class PinterestSocialNotifier extends SocialNotifier {
    protected function publish($message) {
        // Implementation...
    }
}

// Client
$notifier = new TwitterSocialNotifier();

$notifier->notifyNext(new FacebookSocialNotifier())
    ->notifyNext(new PinterestSocialNotifier());

$notifier->push('Awesome new product available!');

我们首先使用抽象方法publishnotifyNext、和push方法实现创建了一个抽象的SocialNotifier类。然后我们定义了TwitterSocialNotifierFacebookSocialNotifierPinterestSocialNotifier,所有这些都扩展了摘要SocialNotifier。客户端首先实例化TwitterSocialNotifier,然后进行两次notifyNext调用,在调用最终的push方法之前,将另外两种notifier类型的实例传递给它。

指挥方式

命令模式将执行某些操作的对象与知道如何使用它的对象分离。它通过封装稍后执行某个操作所需的所有相关信息来实现。这意味着有关对象、方法名称和方法参数的信息。

以下是命令模式实现的示例:

interface LightBulbCommand {
    public function execute();
}

class LightBulbControl {
    public function turnOn() {
        echo 'LightBulb turnOn';
    }

    public function turnOff() {
        echo 'LightBulb turnOff';
    }
}

class TurnOnLightBulb implements LightBulbCommand {
    private $lightBulbControl;

    public function __construct(LightBulbControl $lightBulbControl) {
        $this->lightBulbControl = $lightBulbControl;
    }

    public function execute() {
        $this->lightBulbControl->turnOn();
    }
}

class TurnOffLightBulb implements LightBulbCommand {
    private $lightBulbControl;

    public function __construct(LightBulbControl $lightBulbControl) {
        $this->lightBulbControl = $lightBulbControl;
    }

    public function execute() {
        $this->lightBulbControl->turnOff();
    }
}

// Client
$command = new TurnOffLightBulb(new LightBulbControl());
$command->execute();

我们首先创建了一个LightBulbCommand接口。然后我们定义了LightBulbControl类,该类提供了两个简单的turnOn/turnOff方法。然后定义了实现LightBulbCommand接口的TurnOnLightBulb类和TurnOffLightBulb类。最后,客户端用一个实例LightBulbControl实例化TurnOffLightBulb对象,并对其调用execute方法。

解释器模式

解释器模式指定如何计算语言语法或表达式。我们为语言语法定义了一个表示法和一个解释器。语言语法的表示使用复合类层次结构,其中规则映射到类。然后,解释器使用表示来解释语言中的表达式。

以下是解释器模式实现的示例:

interface MathExpression
{
    public function interpret(array $values);
}

class Variable implements MathExpression {
    private $char;

    public function __construct($char) {
        $this->char = $char;
    }

    public function interpret(array $values) {
        return $values[$this->char];
    }
}

class Literal implements MathExpression {
    private $value;

    public function __construct($value) {
        $this->value = $value;
    }

    public function interpret(array $values) {
        return $this->value;
    }
}

class Sum implements MathExpression {
    private $x;
    private $y;

    public function __construct(MathExpression $x, MathExpression $y) {
        $this->x = $x;
        $this->y = $y;
    }

    public function interpret(array $values) {
        return $this->x->interpret($values) + $this->y->interpret($values);
    }
}

class Product implements MathExpression {
    private $x;
    private $y;

    public function __construct(MathExpression $x, MathExpression $y) {
        $this->x = $x;
        $this->y = $y;
    }

    public function interpret(array $values) {
        return $this->x->interpret($values) * $this->y->interpret($values);
    }
}

// Client
$expression = new Product(
    new Literal(5),
    new Sum(
        new Variable('c'),
        new Literal(2)
    )
);

echo $expression->interpret(array('c' => 3)); // 25

我们从创建MathExpression接口开始,使用单一interpret方法。然后我们添加了VariableLiteralSumProduct类,所有这些类都实现了MathExpression接口。然后,客户机从Product类实例化,将LiteralSum的实例传递给它,并完成interpret方法调用。

迭代器模式

迭代器模式用于遍历容器并访问其元素。换句话说,一个类可以遍历另一个类的元素。PHP 本机支持迭代器,作为内置的\Iterator\IteratorAggregate接口的一部分。

以下是迭代器模式实现的示例:

class ProductIterator implements \Iterator {
    private $position = 0;
    private $productsCollection;

    public function __construct(ProductCollection $productsCollection) {
        $this->productsCollection = $productsCollection;
    }

    public function current() {
        return $this->productsCollection->getProduct($this->position);
    }

    public function key() {
        return $this->position;
    }

    public function next() {
        $this->position++;
    }

    public function rewind() {
        $this->position = 0;
    }

    public function valid() {
        return !is_null($this->productsCollection->getProduct($this->position));
    }
}

class ProductCollection implements \IteratorAggregate {
    private $products = array();

    public function getIterator() {
        return new ProductIterator($this);
    }

    public function addProduct($string) {
        $this->products[] = $string;
    }

    public function getProduct($key) {
        if (isset($this->products[$key])) {
            return $this->products[$key];
        }
        return null;
    }

    public function isEmpty() {
        return empty($products);
    }
}

$products = new ProductCollection();
$products->addProduct('T-Shirt Red');
$products->addProduct('T-Shirt Blue');
$products->addProduct('T-Shirt Green');
$products->addProduct('T-Shirt Yellow');

foreach ($products as $product) {
    var_dump($product);
}

我们从开始创建一个ProductIterator,它实现了标准 PHP\Iterator接口。然后我们添加了实现标准 PHP\IteratorAggregate接口的ProductCollection。客户机创建一个ProductCollection实例,通过addProduct方法调用将值堆叠到其中,并在整个集合中循环。

中介模式

我们的软件中的类越多,它们的通信就越复杂。中介模式通过将其封装到中介对象中来解决这种复杂性。对象不再直接通信,而是通过中介对象通信,因此降低了总体耦合。

以下是中介模式实现的示例:

interface MediatorInterface {
    public function fight();
    public function talk();
    public function registerA(ColleagueA $a);
    public function registerB(ColleagueB $b);
}

class ConcreteMediator implements MediatorInterface {
    protected $talk; // ColleagueA
    protected $fight; // ColleagueB

    public function registerA(ColleagueA $a) {
        $this->talk = $a;
    }

    public function registerB(ColleagueB $b) {
        $this->fight = $b;
    }

    public function fight() {
        echo 'fighting...';
    }

    public function talk() {
        echo 'talking...';
    }
}

abstract class Colleague {
    protected $mediator; // MediatorInterface
    public abstract function doSomething();
}

class ColleagueA extends Colleague {

    public function __construct(MediatorInterface $mediator) {
        $this->mediator = $mediator;
        $this->mediator->registerA($this);
    }

public function doSomething() {
        $this->mediator->talk();
}
}

class ColleagueB extends Colleague {

    public function __construct(MediatorInterface $mediator) {
        $this->mediator = $mediator;
        $this->mediator->registerB($this);
    }

    public function doSomething() {
        $this->mediator->fight();
    }
}

// Client
$mediator = new ConcreteMediator();
$talkColleague = new ColleagueA($mediator);
$fightColleague = new ColleagueB($mediator);

$talkColleague->doSomething();
$fightColleague->doSomething();

我们首先用几个方法创建了一个MediatorInterface,这些方法由ConcreteMediator类实现。然后,我们定义了抽象类Colleague来强制在以下ColleagueAColleagueB类上实现doSomething方法。客户端首先实例化ConcreteMediator并将其实例传递给ColleagueAColleagueB实例,然后调用doSomething方法。

纪念图案

memento 模式提供对象还原功能。实现通过三个不同的对象完成;发起人、看守人和纪念品,其中发起人是保存后续恢复所需内部状态的人。

以下是 memento 模式实现的示例:

class Memento {
    private $state;

    public function __construct($state) {
        $this->state = $state;
    }

    public function getState() {
        return $this->state;
    }
}

class Originator {
    private $state;

    public function setState($state) {
        return $this->state = $state;
    }

    public function getState() {
        return $this->state;
    }

    public function saveToMemento() {
        return new Memento($this->state);
    }

    public function restoreFromMemento(Memento $memento) {
        $this->state = $memento->getState();
    }
}

// Client - Caretaker
$savedStates = array();

$originator = new Originator();
$originator->setState('new');
$originator->setState('pending');
$savedStates[] = $originator->saveToMemento();
$originator->setState('processing');
$savedStates[] = $originator->saveToMemento();
$originator->setState('complete');
$originator->restoreFromMemento($savedStates[1]);
echo $originator->getState(); // processing

我们从创建Memento类开始,该类将通过getState方法提供对象的当前状态。然后我们定义了将状态推送到MementoOriginator类。最后,客户端通过实例化Originator来扮演caretaker的角色,在它的几个状态之间切换,从memento保存并恢复它们。

观察者模式

观察者模式在对象之间实现了一个过多的依赖关系。持有依赖项列表的对象称为主体,而依赖项称为观察者。当 subject 对象更改状态时,将自动通知并更新所有从属对象。

以下是观察者模式实现的示例:

class Customer implements \SplSubject {
    protected $data = array();
    protected $observers = array();

    public function attach(\SplObserver $observer) {
        $this->observers[] = $observer;
    }

    public function detach(\SplObserver $observer) {
        $index = array_search($observer, $this->observers);

        if ($index !== false) {
            unset($this->observers[$index]);
        }
    }

    public function notify() {
        foreach ($this->observers as $observer) {
            $observer->update($this);
            echo 'observer updated';
        }
    }

    public function __set($name, $value) {
        $this->data[$name] = $value;

        // notify the observers, that user has been updated
        $this->notify();
    }
}

class CustomerObserver implements \SplObserver {
    public function update(\SplSubject $subject) {
        /* Implementation... */
    }
}

// Client
$user = new Customer();
$customerObserver = new CustomerObserver();
$user->attach($customerObserver);

$user->name = 'John Doe';
$user->email = 'john.doe@fake.mail';

我们从创建一个Customer类开始,该类实现了标准 PHP\SplSubject接口。然后我们定义了实现标准 PHP\SplObserver接口的CustomerObserver类。最后,客户端实例化CustomerCustomerObserver对象,并将CustomerObserver对象附加到Customer。对nameemail属性的任何更改都会被observer捕获。

状态模式

状态模式封装了同一对象基于其内部状态的变化行为,使对象看起来好像已经改变了它的类。

以下是状态模式实现的示例:

interface Statelike {
    public function writeName(StateContext $context, $name);
}

class StateLowerCase implements Statelike {
    public function writeName(StateContext $context, $name) {
        echo strtolower($name);
        $context->setState(new StateMultipleUpperCase());
    }
}

class StateMultipleUpperCase implements Statelike {
    private $count = 0;

    public function writeName(StateContext $context, $name) {
        $this->count++;
        echo strtoupper($name);
        /* Change state after two invocations */
        if ($this->count > 1) {
            $context->setState(new StateLowerCase());
        }
    }
}

class StateContext {
    private $state;

    public function setState(Statelike $state) {
        $this->state = $state;
    }

    public function writeName($name) {
        $this->state->writeName($this, $name);
    }
}

// Client
$stateContext = new StateContext();
$stateContext->setState(new StateLowerCase());
$stateContext->writeName('Monday');
$stateContext->writeName('Tuesday');
$stateContext->writeName('Wednesday');
$stateContext->writeName('Thursday');
$stateContext->writeName('Friday');
$stateContext->writeName('Saturday');
$stateContext->writeName('Sunday');

我们首先创建一个Statelike接口,然后是实现该接口的StateLowerCaseStateMultipleUpperCaseStateMultipleUpperCase在其writeName中添加了一点计数逻辑,因此它在两次调用后启动新状态。然后我们定义了StateContext类,我们将使用它来切换上下文。最后,客户端实例化StateContext,并通过setState方法将StateLowerCase的实例传递给它,之后是几个writeName方法。

战略模式

策略模式定义了一系列算法,每个算法都被封装并与该家族中的其他成员互换。

以下是战略模式实施的示例:

interface PaymentStrategy {
    public function pay($amount);
}

class StripePayment implements PaymentStrategy {
    public function pay($amount) {
        echo 'StripePayment...';
    }

}

class PayPalPayment implements PaymentStrategy {
    public function pay($amount) {
        echo 'PayPalPayment...';
    }
}

class Checkout {
    private $amount = 0;

    public function __construct($amount = 0) {
        $this->amount = $amount;
    }

    public function capturePayment() {
        if ($this->amount > 99.99) {
            $payment = new PayPalPayment();
        } else {
            $payment = new StripePayment();
        }

        $payment->pay($this->amount);
    }
}

$checkout = new Checkout(49.99);
$checkout->capturePayment(); // StripePayment...

$checkout = new Checkout(199.99);
$checkout->capturePayment(); // PayPalPayment...

我们首先创建了一个PaymentStrategy接口,然后是实现它的具体类StripePaymentPayPalPayment。然后,我们用capturePayment方法中的一些决策逻辑定义了Checkout类。最后,客户端实例化Checkout,通过其构造函数传递一定数量的。根据金额,Checkout在调用capturePayment时会在内部触发一个或另一个payment

模板图案

模板设计模式在方法中定义算法的程序框架。它允许我们通过使用类重写来重新定义算法的某些步骤,而不会真正改变算法的结构。

以下是模板模式实现的示例:

abstract class Game {
    private $playersCount;

    abstract function initializeGame();
    abstract function makePlay($player);
    abstract function endOfGame();
    abstract function printWinner();

    public function playOneGame($playersCount)
    {
        $this->playersCount = $playersCount;
        $this->initializeGame();
        $j = 0;
        while (!$this->endOfGame()) {
            $this->makePlay($j);
            $j = ($j + 1) % $playersCount;
        }
        $this->printWinner();
    }
}

class Monopoly extends Game {
    public function initializeGame() {
        // Implementation...
    }

    public function makePlay($player) {
        // Implementation...
    }

    public function endOfGame() {
        // Implementation...
    }

    public function printWinner() {
        // Implementation...
    }
}

class Chess extends Game {
    public function  initializeGame() {
        // Implementation...
    }

    public function  makePlay($player) {
        // Implementation...
    }

    public function  endOfGame() {
        // Implementation...
    }

    public function  printWinner() {
        // Implementation...
    }
}

$game = new Chess();
$game->playOneGame(2);

$game = new Monopoly();
$game->playOneGame(4);

我们从创建一个抽象Game类开始,该类提供封装游戏的所有实际抽象方法。然后,我们定义了MonopolyChess类,这两个类都是从Game类扩展而来的,分别实现了特定于游戏的方法—游戏玩法。客户机只是实例化了MonopolyChess对象,并分别调用playOneGame方法。

访客模式

访问者设计模式是将算法与其操作的对象结构分离的一种方式。因此,我们能够向现有对象结构添加新操作,而无需实际修改这些结构。

以下是访问者模式实现的示例:

interface RoleVisitorInterface {
    public function visitUser(User $role);
    public function visitGroup(Group $role);
}

class RolePrintVisitor implements RoleVisitorInterface {
    public function visitGroup(Group $role) {
        echo 'Role: ' . $role->getName();
    }

    public function visitUser(User $role) {
        echo 'Role: ' . $role->getName();
    }
}

abstract class Role {
    public function accept(RoleVisitorInterface $visitor) {
        $klass = get_called_class();
        preg_match('#([^\\\\]+)$#', $klass, $extract);
        $visitingMethod = 'visit' . $extract[1];

        if (!method_exists(__NAMESPACE__ . '\RoleVisitorInterface', $visitingMethod)) {
            throw new \InvalidArgumentException("The visitor you provide cannot visit a $klass instance");
        }

        call_user_func(array($visitor, $visitingMethod), $this);
    }
}

class User extends Role {
    protected $name;

    public function __construct($name) {
        $this->name = (string)$name;
    }

    public function getName() {
        return 'User ' . $this->name;
    }
}

class Group extends Role {
    protected $name;

    public function __construct($name) {
        $this->name = (string)$name;
    }

    public function getName() {
        return 'Group: ' . $this->name;
    }
}

$group = new Group('my group');
$user = new User('my user');

$visitor = new RolePrintVisitor;

$group->accept($visitor);
$user->accept($visitor);

我们从创建RoleVisitorInterface开始,然后是实现RoleVisitorInterface本身的RolePrintVisitor。然后,我们定义了抽象类Role,其中 accept 方法采用RoleVisitorInterface参数类型。我们进一步定义了混凝土UserGroup类,这两个类都是从Role扩展而来的。客户端实例化UserGroupRolePrintVisitor;将visitor传入UserGroup实例的接受方法调用。

总结

设计模式是开发人员常用的高级语言。它们提供了一种在团队成员之间交流应用程序设计的快捷方式。理解如何识别和实现设计模式将我们的重点转移到业务需求解决上,而不是修补如何在代码级别将解决方案粘合在一起。

编码,就像大多数手工制作的规程一样,是一种花钱就能买到东西的规程。虽然实现许多设计模式需要一定的时间,但是在一个更大的项目上不这样做可能会在将来以某种方式赶上我们。与“是否使用框架”的争论类似,实现正确的设计模式会影响我们代码的可扩展性可重用性适应性可维护性。因此,让它更能证明未来。

接下来,在下一章中,我们将探讨坚实的设计原则及其在软件开发过程中所起的作用。