【搬运于GitHub开源项目DesignPatternsPHP】项目地址:戳我2、结构型设计模式在软件工程中,结构型设计模式集是用来抽象真实程序中的对象实体之间的关系,并使这种关系可被描述,概括和具体化。2.1 适配器模式2.1.1 目的将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。2.1.2 例子数据库客户端库适配器使用不同的webservices,通过适配器来标准化输出数据,从而保证不同webservice输出的数据是一致的2.1.3 UML图2.1.4 代码你可以在 GitHub 上找到这些代码BookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface BookInterface{ public function turnPage(); public function open(); public function getPage(): int;}Book.php<?phpnamespace 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.php<?phpnamespace 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.php<?phpnamespace 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<?phpnamespace 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 目的解耦一个对象的实现与抽象,这样两者可以独立地变化。2.2.2 例子Symfony DoctrineBridge2.2.3 UML图2.2.4 代码你可以在 GitHub 上找到这些代码Formatter.php<?phpnamespace DesignPatterns\Structural\Bridge;interface Formatter{ public function format(string $text): string;}PlainTextFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class PlainTextFormatter implements Formatter{ public function format(string $text): string { return $text; }}HtmlFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class HtmlFormatter implements Formatter{ public function format(string $text): string { return sprintf(’<p>%s</p>’, $text); }}Service.php<?phpnamespace 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<?phpnamespace DesignPatterns\Structural\Bridge;class HelloWorldService extends Service{ public function get(): string { return $this->implementation->format(‘Hello World’); }}PingService.php<?phpnamespace 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图2.3.4 代码你可以在 GitHub 上找到这些代码RenderableInterface.php<?phpnamespace DesignPatterns\Structural\Composite;interface RenderableInterface{ public function render(): string;}Form.php<?phpnamespace 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<?phpnamespace DesignPatterns\Structural\Composite;class InputElement implements RenderableInterface{ public function render(): string { return ‘<input type=“text” />’; }}TextElement.php<?phpnamespace 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”作为DAO2.4.3 UML图2.4.4 代码你可以在 GitHub 上找到这些代码User.php<?phpnamespace 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<?phpnamespace 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<?phpnamespace 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图2.5.4 代码你可以在 GitHub 上找到这些代码Booking.php<?phpnamespace DesignPatterns\Structural\Decorator;interface Booking{ public function calculatePrice(): int; public function getDescription(): string;}BookingDecorator.php<?phpnamespace DesignPatterns\Structural\Decorator;abstract class BookingDecorator implements Booking{ /* * @var Booking / protected $booking; public function __construct(Booking $booking) { $this->booking = $booking; }}DoubleRoomBooking.php<?phpnamespace DesignPatterns\Structural\Decorator;class DoubleRoomBooking implements Booking{ public function calculatePrice(): int { return 40; } public function getDescription(): string { return ‘double room’; }}ExtraBed.php<?phpnamespace 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<?phpnamespace 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图2.6.5 代码你可以在 GitHub 上找到这些代码DatabaseConfiguration.php<?phpnamespace 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<?phpnamespace 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图2.7.3 代码你可以在 GitHub 上找到这些代码Facade.php<?phpnamespace 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<?phpnamespace DesignPatterns\Structural\Facade;interface OsInterface{ public function halt(); public function getName(): string;}BiosInterface.php<?phpnamespace 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 框架:CDbCommand 与 CActiveRecord 也使用此模式2.8.3 UML图2.8.4 代码你可以在 GitHub 上找到这些代码Sql.php<?phpnamespace 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图2.9.3 代码你可以在 GitHub 上找到这些代码FlyweightInterface.php<?phpnamespace DesignPatterns\Structural\Flyweight;interface FlyweightInterface{ public function render(string $extrinsicState): string;}CharacterFlyweight.php<?phpnamespace 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<?phpnamespace 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图2.10.4 代码你可以在 GitHub 上找到这些代码BankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;interface BankAccount{ public function deposit(int $amount); public function getBalance(): int;}HeavyBankAccount.php<?phpnamespace 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<?phpnamespace 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图2.11.4 代码你可以在 GitHub 上找到这些代码Registry.php<?phpnamespace 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)创建型设计模式