【搬运于GitHub开源项目DesignPatternsPHP】
项目地址:戳我
2、结构型设计模式
在软件工程中,结构型设计模式集是用来抽象真实程序中的对象实体之间的关系,并使这种关系可被描述,概括和具体化。
2.1 适配器模式
2.1.1 目的
将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。
2.1.2 例子
- 数据库客户端库适配器
- 使用不同的
webservices
,通过适配器来标准化输出数据,从而保证不同webservice
输出的数据是一致的
2.1.3 UML图
2.1.4 代码
你可以在 GitHub 上找到这些代码
BookInterface.php
<?php
namespace DesignPatterns\Structural\Adapter;
interface BookInterface
{
public function turnPage();
public function open();
public function getPage(): int;
}
Book.php
<?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.php
<?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.php
<?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 目的
解耦一个对象的实现与抽象,这样两者可以独立地变化。
2.2.2 例子
2.2.3 UML图
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图
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图
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图
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图
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图
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 框架:
CDbCommand
与CActiveRecord
也使用此模式
2.8.3 UML图
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图
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图
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图
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];
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。