項目地址: 戳我
在軟件工程中,結構型設計模式集是用來抽象真實程序中的對象實體之間的關係,並使這種關係可被描述,歸納和具體化。php
將某個類的接口轉換成與另外一個接口兼容。適配器經過將原始接口進行轉換,給用戶提供一個兼容接口,使得原來由於接口不一樣而沒法一塊兒使用的類能夠獲得兼容。前端
webservices
,經過適配器來標準化輸出數據,從而保證不一樣webservice
輸出的數據是一致的
你能夠在 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]; } }
解耦一個對象的實現與抽象,這樣二者能夠獨立地變化。segmentfault
你能夠在 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'); } }
以單個對象的方式來對待一組對象
form
類的實例包含多個子元素,而它也像單個子元素那樣響應render()
請求,當調用render()
方法時,它會歷遍全部的子元素,調用render()
方法Zend_Config
: 配置選項樹, 其每個分支都是Zend_Config
對象
你能夠在 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; } }
數據映射器是一個數據訪問層,用於將數據在持久性數據存儲(一般是一個關係數據庫)和內存中的數據表示(領域層)之間進行相互轉換。其目的是爲了將數據的內存表示、持久存儲、數據訪問進行分離。該層由一個或者多個映射器組成(或者數據訪問對象),而且進行數據的轉換。映射器的實如今範圍上有所不一樣。通用映射器將處理許多不一樣領域的實體類型,而專用映射器將處理一個或幾個。
此模式的主要特色是,與Active Record
不一樣,其數據模式遵循單一職責原則(Single Responsibility Principle
)。
你能夠在 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; } }
動態地爲類的實例添加功能
Zend_Form_Element
實例的裝飾器
你能夠在 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'; } }
實現了鬆耦合的軟件架構,可獲得更好的測試,管理和擴展的代碼
注入DatabaseConfiguration
, DatabaseConnection
將從$config
得到所需的全部內容。沒有DI
(依賴注入),配置將直接在DatabaseConnection
中建立,這不利於測試和擴展它。
Connection
對象。爲了達到方便測試的目的,能夠很容易的經過配置建立一個mock的 Connection
對象。
你能夠在 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() ); } }
Facade
模式的主要目標不是避免您必須閱讀複雜API的手冊。這只是反作用。主要目的是減小耦合並遵循Demeter
定律。
Facade
經過嵌入多個(固然,有時只有一個)接口來解耦訪客與子系統,固然也下降複雜度。
Facade
不會禁止你訪問子系統
你能夠爲一個子系統提供多個 Facade
所以一個好的 Facade
裏面不會有 new
。若是每一個方法裏都要構造多個對象,那麼它就不是 Facade
,而是生成器或者 [ 抽象 | 靜態 | 簡單 ] 工廠方法。
優秀的 Facade
不會有 new
,而且構造函數參數是接口類型的。若是你須要建立一個新實例,則在參數中傳入一個工廠對象。
你能夠在 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(); }
用來編寫易於閱讀的代碼,就像天然語言同樣(如英語)
QueryBuilder
,就像下面例子中相似CDbCommand
與 CActiveRecord
也使用此模式
你能夠在 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) ); } }
爲了儘量減小內存使用,Flyweight
與相似的對象共享盡量多的內存。當使用大量狀態相差不大的對象時,就須要它。一般的作法是保持外部數據結構中的狀態,並在須要時將其傳遞給flyweight
對象。
你能夠在 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); } }
爲昂貴或者沒法複製的資源提供接口。
你能夠在 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; } }
要爲整個應用程序中常用的對象實現中央存儲,一般只使用靜態方法(或使用單例模式)的抽象類來實現。請記住,這將引入全局狀態,這在任什麼時候候都應該避免!而是使用依賴注入來實現它!
Zend_Registry
持有應用的logger
對象,前端控制器等。CWebApplication
持有全部的應用組件,如 CWebUser
, CUrlManager
, 等。
你能夠在 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]; } }