PHP設計模式範例 — DesignPatternsPHP(2)結構型設計模式

【搬運於GitHub開源項目DesignPatternsPHP】

項目地址: 戳我

二、結構型設計模式

在軟件工程中,結構型設計模式集是用來抽象真實程序中的對象實體之間的關係,並使這種關係可被描述,歸納和具體化。php

2.1 適配器模式

2.1.1 目的

將某個類的接口轉換成與另外一個接口兼容。適配器經過將原始接口進行轉換,給用戶提供一個兼容接口,使得原來由於接口不一樣而沒法一塊兒使用的類能夠獲得兼容。前端

2.1.2 例子

  • 數據庫客戶端庫適配器
  • 使用不一樣的webservices,經過適配器來標準化輸出數據,從而保證不一樣webservice輸出的數據是一致的

2.1.3 UML圖

clipboard.png

2.1.4 代碼

你能夠在 GitHub 上找到這些代碼node

BookInterface.phpios

<?php

namespace DesignPatterns\Structural\Adapter;

interface BookInterface
{
    public function turnPage();

    public function open();

    public function getPage(): int;
}

Book.phpgit

<?php

namespace DesignPatterns\Structural\Adapter;

class Book implements BookInterface
{
    /**
     * @var int
     */
    private $page;

    public function open()
    {
        $this->page = 1;
    }

    public function turnPage()
    {
        $this->page++;
    }

    public function getPage(): int
    {
        return $this->page;
    }
}

EBookAdapter.phpgithub

<?php

namespace DesignPatterns\Structural\Adapter;

/**
 * This is the adapter here. Notice it implements BookInterface,
 * therefore you don't have to change the code of the client which is using a Book
 */
class EBookAdapter implements BookInterface
{
    /**
     * @var EBookInterface
     */
    protected $eBook;

    /**
     * @param EBookInterface $eBook
     */
    public function __construct(EBookInterface $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: EBookInterface::getPage() will return two integers, but BookInterface
     * supports only a current page getter, so we adapt the behavior here
     *
     * @return int
     */
    public function getPage(): int
    {
        return $this->eBook->getPage()[0];
    }
}

EBookInterface.phpweb

<?php

namespace DesignPatterns\Structural\Adapter;

interface EBookInterface
{
    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;
}

Kindle.php數據庫

<?php

namespace DesignPatterns\Structural\Adapter;

/**
 * this is the adapted class. In production code, this could be a class from another package, some vendor code.
 * Notice that it uses another naming scheme and the implementation does something similar but in another way
 */
class Kindle implements EBookInterface
{
    /**
     * @var int
     */
    private $page = 1;

    /**
     * @var int
     */
    private $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];
    }
}

2.2 橋接模式

2.2.1 目的

解耦一個對象的實現與抽象,這樣二者能夠獨立地變化。segmentfault

2.2.2 例子

2.2.3 UML圖

clipboard.png

2.2.4 代碼

你能夠在 GitHub 上找到這些代碼設計模式

Formatter.php

<?php

namespace DesignPatterns\Structural\Bridge;

interface Formatter
{
    public function format(string $text): string;
}

PlainTextFormatter.php

<?php

namespace DesignPatterns\Structural\Bridge;

class PlainTextFormatter implements Formatter
{
    public function format(string $text): string
    {
        return $text;
    }
}

HtmlFormatter.php

<?php

namespace DesignPatterns\Structural\Bridge;

class HtmlFormatter implements Formatter
{
    public function format(string $text): string
    {
        return sprintf('<p>%s</p>', $text);
    }
}

Service.php

<?php

namespace DesignPatterns\Structural\Bridge;

abstract class Service
{
    /**
     * @var Formatter
     */
    protected $implementation;

    /**
     * @param Formatter $printer
     */
    public function __construct(Formatter $printer)
    {
        $this->implementation = $printer;
    }

    /**
     * @param Formatter $printer
     */
    public function setImplementation(Formatter $printer)
    {
        $this->implementation = $printer;
    }

    abstract public function get(): string;
}

HelloWorldService.php

<?php

namespace DesignPatterns\Structural\Bridge;

class HelloWorldService extends Service
{
    public function get(): string
    {
        return $this->implementation->format('Hello World');
    }
}

PingService.php

<?php

namespace DesignPatterns\Structural\Bridge;

class PingService extends Service
{
    public function get(): string
    {
        return $this->implementation->format('pong');
    }
}

2.3 組合模式

2.3.1 目的

以單個對象的方式來對待一組對象

2.3.2 例子

  • form類的實例包含多個子元素,而它也像單個子元素那樣響應render()請求,當調用render()方法時,它會歷遍全部的子元素,調用render()方法
  • Zend_Config: 配置選項樹, 其每個分支都是Zend_Config對象

2.3.3 UML圖

clipboard.png

2.3.4 代碼

你能夠在 GitHub 上找到這些代碼

RenderableInterface.php

<?php

namespace DesignPatterns\Structural\Composite;

interface RenderableInterface
{
    public function render(): string;
}

Form.php

<?php

namespace DesignPatterns\Structural\Composite;

/**
 * The composite node MUST extend the component contract. This is mandatory for building
 * a tree of components.
 */
class Form implements RenderableInterface
{
    /**
     * @var RenderableInterface[]
     */
    private $elements;

    /**
     * runs through all elements and calls render() on them, then returns the complete representation
     * of the form.
     *
     * from the outside, one will not see this and the form will act like a single object instance
     *
     * @return string
     */
    public function render(): string
    {
        $formCode = '<form>';

        foreach ($this->elements as $element) {
            $formCode .= $element->render();
        }

        $formCode .= '</form>';

        return $formCode;
    }

    /**
     * @param RenderableInterface $element
     */
    public function addElement(RenderableInterface $element)
    {
        $this->elements[] = $element;
    }
}

InputElement.php

<?php

namespace DesignPatterns\Structural\Composite;

class InputElement implements RenderableInterface
{
    public function render(): string
    {
        return '<input type="text" />';
    }
}

TextElement.php

<?php

namespace DesignPatterns\Structural\Composite;

class TextElement implements RenderableInterface
{
    /**
     * @var string
     */
    private $text;

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

    public function render(): string
    {
        return $this->text;
    }
}

2.4 數據映射器

2.4.1 目的

數據映射器是一個數據訪問層,用於將數據在持久性數據存儲(一般是一個關係數據庫)和內存中的數據表示(領域層)之間進行相互轉換。其目的是爲了將數據的內存表示、持久存儲、數據訪問進行分離。該層由一個或者多個映射器組成(或者數據訪問對象),而且進行數據的轉換。映射器的實如今範圍上有所不一樣。通用映射器將處理許多不一樣領域的實體類型,而專用映射器將處理一個或幾個。

此模式的主要特色是,與Active Record不一樣,其數據模式遵循單一職責原則(Single Responsibility Principle)。

2.4.2 例子

  • DB對象關係映射器(ORM): Doctrine2使用「EntityRepository」做爲DAO

2.4.3 UML圖

clipboard.png

2.4.4 代碼

你能夠在 GitHub 上找到這些代碼

User.php

<?php

namespace DesignPatterns\Structural\DataMapper;

class User
{
    /**
     * @var string
     */
    private $username;

    /**
     * @var string
     */
    private $email;

    public static function fromState(array $state): User
    {
        // validate state before accessing keys!

        return new self(
            $state['username'],
            $state['email']
        );
    }

    public function __construct(string $username, string $email)
    {
        // validate parameters before setting them!

        $this->username = $username;
        $this->email = $email;
    }

    /**
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }
}

UserMapper.php

<?php

namespace DesignPatterns\Structural\DataMapper;

class UserMapper
{
    /**
     * @var StorageAdapter
     */
    private $adapter;

    /**
     * @param StorageAdapter $storage
     */
    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
     *
     * @param int $id
     *
     * @return User
     */
    public function findById(int $id): User
    {
        $result = $this->adapter->find($id);

        if ($result === null) {
            throw new \InvalidArgumentException("User #$id not found");
        }

        return $this->mapRowToUser($result);
    }

    private function mapRowToUser(array $row): User
    {
        return User::fromState($row);
    }
}

StorageAdapter.php

<?php

namespace DesignPatterns\Structural\DataMapper;

class StorageAdapter
{
    /**
     * @var array
     */
    private $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;
    }
}

2.5 裝飾器

2.5.1 目的

動態地爲類的實例添加功能

2.5.2 例子

  • Zend Framework: Zend_Form_Element 實例的裝飾器
  • Web Service層:REST服務的JSON與XML裝飾器(固然,在此只能使用其中的一種)

2.5.3 UML圖

clipboard.png

2.5.4 代碼

你能夠在 GitHub 上找到這些代碼

Booking.php

<?php

namespace DesignPatterns\Structural\Decorator;

interface Booking
{
    public function calculatePrice(): int;

    public function getDescription(): string;
}

BookingDecorator.php

<?php

namespace DesignPatterns\Structural\Decorator;

abstract class BookingDecorator implements Booking
{
    /**
     * @var Booking
     */
    protected $booking;

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

DoubleRoomBooking.php

<?php

namespace DesignPatterns\Structural\Decorator;

class DoubleRoomBooking implements Booking
{
    public function calculatePrice(): int
    {
        return 40;
    }

    public function getDescription(): string
    {
        return 'double room';
    }
}

ExtraBed.php

<?php

namespace DesignPatterns\Structural\Decorator;

class ExtraBed extends BookingDecorator
{
    private const PRICE = 30;

    public function calculatePrice(): int
    {
        return $this->booking->calculatePrice() + self::PRICE;
    }

    public function getDescription(): string
    {
        return $this->booking->getDescription() . ' with extra bed';
    }
}

WiFi.php

<?php

namespace DesignPatterns\Structural\Decorator;

class WiFi extends BookingDecorator
{
    private const PRICE = 2;

    public function calculatePrice(): int
    {
        return $this->booking->calculatePrice() + self::PRICE;
    }

    public function getDescription(): string
    {
        return $this->booking->getDescription() . ' with wifi';
    }
}

2.6 依賴注入

2.6.1 目的

實現了鬆耦合的軟件架構,可獲得更好的測試,管理和擴展的代碼

2.6.2 用例

注入DatabaseConfiguration, DatabaseConnection將從$config得到所需的全部內容。沒有DI(依賴注入),配置將直接在DatabaseConnection中建立,這不利於測試和擴展它。

2.6.3 例子

  • Doctrine2 ORM 使用了依賴注入,它經過配置注入了 Connection 對象。爲了達到方便測試的目的,能夠很容易的經過配置建立一個mock的 Connection 對象。
  • Symfony 和 Zend Framework 2 也有了專門的依賴注入容器,用來經過配置數據建立須要的對象(好比在控制器中使用依賴注入容器獲取所需的對象)

2.6.4 UML圖

clipboard.png

2.6.5 代碼

你能夠在 GitHub 上找到這些代碼

DatabaseConfiguration.php

<?php

namespace DesignPatterns\Structural\DependencyInjection;

class DatabaseConfiguration
{
    /**
     * @var string
     */
    private $host;

    /**
     * @var int
     */
    private $port;

    /**
     * @var string
     */
    private $username;

    /**
     * @var string
     */
    private $password;

    public function __construct(string $host, int $port, string $username, string $password)
    {
        $this->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;
    }
}

DatabaseConnection.php

<?php

namespace DesignPatterns\Structural\DependencyInjection;

class DatabaseConnection
{
    /**
     * @var DatabaseConfiguration
     */
    private $configuration;

    /**
     * @param DatabaseConfiguration $config
     */
    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 here

        return sprintf(
            '%s:%s@%s:%d',
            $this->configuration->getUsername(),
            $this->configuration->getPassword(),
            $this->configuration->getHost(),
            $this->configuration->getPort()
        );
    }
}

2.7 外觀模式

2.7.1 目的

Facade模式的主要目標不是避免您必須閱讀複雜API的手冊。這只是反作用。主要目的是減小耦合並遵循Demeter定律。

Facade經過嵌入多個(固然,有時只有一個)接口來解耦訪客與子系統,固然也下降複雜度。

Facade不會禁止你訪問子系統
你能夠爲一個子系統提供多個 Facade
所以一個好的 Facade 裏面不會有 new 。若是每一個方法裏都要構造多個對象,那麼它就不是 Facade,而是生成器或者 [ 抽象 | 靜態 | 簡單 ] 工廠方法。

優秀的 Facade 不會有 new,而且構造函數參數是接口類型的。若是你須要建立一個新實例,則在參數中傳入一個工廠對象。

2.7.2 UML圖

clipboard.png

2.7.3 代碼

你能夠在 GitHub 上找到這些代碼

Facade.php

<?php

namespace DesignPatterns\Structural\Facade;

class Facade
{
    /**
     * @var OsInterface
     */
    private $os;

    /**
     * @var BiosInterface
     */
    private $bios;

    /**
     * @param BiosInterface $bios
     * @param OsInterface   $os
     */
    public function __construct(BiosInterface $bios, OsInterface $os)
    {
        $this->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();
    }
}

OsInterface.php

<?php

namespace DesignPatterns\Structural\Facade;

interface OsInterface
{
    public function halt();

    public function getName(): string;
}

BiosInterface.php

<?php

namespace DesignPatterns\Structural\Facade;

interface BiosInterface
{
    public function execute();

    public function waitForKeyPress();

    public function launch(OsInterface $os);

    public function powerDown();
}

2.8 連貫接口

2.8.1 目的

用來編寫易於閱讀的代碼,就像天然語言同樣(如英語)

2.8.2 例子

  • Doctrine2 的 QueryBuilder,就像下面例子中相似
  • PHPUnit 使用連貫接口來建立 mock 對象
  • Yii 框架:CDbCommandCActiveRecord 也使用此模式

2.8.3 UML圖

clipboard.png

2.8.4 代碼

你能夠在 GitHub 上找到這些代碼

Sql.php

<?php

namespace DesignPatterns\Structural\FluentInterface;

class Sql
{
    /**
     * @var array
     */
    private $fields = [];

    /**
     * @var array
     */
    private $from = [];

    /**
     * @var array
     */
    private $where = [];

    public function select(array $fields): Sql
    {
        $this->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)
        );
    }
}

2.9 享元

2.9.1 目的

爲了儘量減小內存使用,Flyweight與相似的對象共享盡量多的內存。當使用大量狀態相差不大的對象時,就須要它。一般的作法是保持外部數據結構中的狀態,並在須要時將其傳遞給flyweight對象。

2.9.2 UML圖

clipboard.png

2.9.3 代碼

你能夠在 GitHub 上找到這些代碼

FlyweightInterface.php

<?php

namespace DesignPatterns\Structural\Flyweight;

interface FlyweightInterface
{
    public function render(string $extrinsicState): string;
}

CharacterFlyweight.php

<?php

namespace DesignPatterns\Structural\Flyweight;

/**
 * Implements the flyweight interface and adds storage for intrinsic state, if any.
 * Instances of concrete flyweights are shared by means of a factory.
 */
class CharacterFlyweight implements FlyweightInterface
{
    /**
     * Any state stored by the concrete flyweight must be independent of its context.
     * For flyweights representing characters, this is usually the corresponding character code.
     *
     * @var string
     */
    private $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);
    }
}

FlyweightFactory.php

<?php

namespace DesignPatterns\Structural\Flyweight;

/**
 * A factory manages shared flyweights. Clients should not instantiate them directly,
 * but let the factory take care of returning existing objects or creating new ones.
 */
class FlyweightFactory implements \Countable
{
    /**
     * @var CharacterFlyweight[]
     */
    private $pool = [];

    public function get(string $name): CharacterFlyweight
    {
        if (!isset($this->pool[$name])) {
            $this->pool[$name] = new CharacterFlyweight($name);
        }

        return $this->pool[$name];
    }

    public function count(): int
    {
        return count($this->pool);
    }
}

2.10 代理模式

2.10.1 目的

爲昂貴或者沒法複製的資源提供接口。

2.10.2 例子

  • Doctrine2 使用代理來實現框架特性(如延遲初始化),同時用戶仍是使用本身的實體類而且不會使用或者接觸到代理

2.10.3 UML圖

clipboard.png

2.10.4 代碼

你能夠在 GitHub 上找到這些代碼

BankAccount.php

<?php

namespace DesignPatterns\Structural\Proxy;

interface BankAccount
{
    public function deposit(int $amount);

    public function getBalance(): int;
}

HeavyBankAccount.php

<?php

namespace DesignPatterns\Structural\Proxy;

class HeavyBankAccount implements BankAccount
{
    /**
     * @var int[]
     */
    private $transactions = [];

    public function deposit(int $amount)
    {
        $this->transactions[] = $amount;
    }

    public function getBalance(): int
    {
        // this is the heavy part, imagine all the transactions even from
        // years and decades ago must be fetched from a database or web service
        // and the balance must be calculated from it

        return array_sum($this->transactions);
    }
}

BankAccountProxy.php

<?php

namespace DesignPatterns\Structural\Proxy;

class BankAccountProxy extends HeavyBankAccount implements BankAccount
{
    /**
     * @var int
     */
    private $balance;

    public function getBalance(): int
    {
        // because calculating balance is so expensive,
        // the usage of BankAccount::getBalance() is delayed until it really is needed
        // and will not be calculated again for this instance

        if ($this->balance === null) {
            $this->balance = parent::getBalance();
        }

        return $this->balance;
    }
}

2.11 註冊模式

2.11.1 目的

要爲整個應用程序中常用的對象實現中央存儲,一般只使用靜態方法(或使用單例模式)的抽象類來實現。請記住,這將引入全局狀態,這在任什麼時候候都應該避免!而是使用依賴注入來實現它!

2.11.2 例子

  • Zend Framework 1: Zend_Registry 持有應用的logger對象,前端控制器等。
  • Yii 框架: CWebApplication 持有全部的應用組件,如 CWebUser, CUrlManager, 等。

2.11.3 UML圖

clipboard.png

2.11.4 代碼

你能夠在 GitHub 上找到這些代碼

Registry.php

<?php

namespace DesignPatterns\Structural\Registry;

abstract class Registry
{
    const LOGGER = 'logger';

    /**
     * this introduces global state in your application which can not be mocked up for testing
     * and is therefor considered an anti-pattern! Use dependency injection instead!
     *
     * @var array
     */
    private static $storedValues = [];

    /**
     * @var array
     */
    private static $allowedKeys = [
        self::LOGGER,
    ];

    /**
     * @param string $key
     * @param mixed  $value
     *
     * @return void
     */
    public static function set(string $key, $value)
    {
        if (!in_array($key, self::$allowedKeys)) {
            throw new \InvalidArgumentException('Invalid key given');
        }

        self::$storedValues[$key] = $value;
    }

    /**
     * @param string $key
     *
     * @return mixed
     */
    public static function get(string $key)
    {
        if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) {
            throw new \InvalidArgumentException('Invalid key given');
        }

        return self::$storedValues[$key];
    }
}

相關文章:
PHP設計模式範例 — DesignPatternsPHP(1)建立型設計模式

相關文章
相關標籤/搜索