大话 PHP 设计模式--结构型
前言
我们学习的设计模式分为三类:创建者模式、结构型模式、行为型模式;创建型模式与对象的创建有关;结构型模式处理类或对象的组合;而行为型模式是对类或对象怎样交互和怎样分配职责进行描述;
内容:上一篇介绍PHP 设计模式的创建型一篇,这一篇是结构型。包括:适配器模式(Adapter),桥梁模式(Bridge),组合模式(Composite),装饰模式(Decorator),门面模式(Facade),享元模式(Flyweight),代理模式(Proxy),数据映射模式(Data Mapper),依赖注入模式(Dependency Injection),流接口模式(Fluent Interface),注册模式(Registry)
(一)适配器模式(Adapter)
- 定义
将一个类的接口转换成可应用的兼容接口。适配器使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 解释例: 在朋友聚会上碰到了一个美女Sarah,从香港来的,可我不会说粤语,她不会说普通话,只好求助于我的朋友kent了,他作为我和Sarah之间的Adapter,让我和Sarah可以相互交谈了 实例:客户端数据库适配器
- 实例: 客户端数据库适配器
- 实例代码实现
interface IDatabase { function connect($host, $user, $passwd, $dbname); function query($sql); function close(); } class MySQL implements IDatabase { protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = mysqli_connect($host, $user, $passwd, $dbname); $this->conn = $conn; } function query($sql) { return mysqli_query($this->conn, $sql); } function close() { mysqli_close($this->conn); } } class PDO1 implements IDatabase { protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = new \PDO("mysql:host=$host;dbname=$dbname", $user, $passwd); $this->conn = $conn; } function query($sql) { return $this->conn->query($sql); } function close() { unset($this->conn); } } $db = new MySQL(); $db->connect('127.0.0.1','root','root','item'); $db->query('select * from p1'); $db->close(); $db = new PDO1(); $db->connect('127.0.0.1','root','root','item'); $db->query('select * from p1'); $db->close();
(二)桥梁模式(Bridge)
- 定义
将抽象化与实现化脱耦,使得二者可以独立的变化,也就是说将他们之间的强关联变成弱关联,也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。
- 解释例: BRIDGE-早上碰到MM,要说早上好,晚上碰到MM,要说晚上好;碰到MM穿了件新衣服,要说你的衣服好漂亮哦,碰到MM新做的发型,要说你的头发好漂亮哦。不要问我"早上碰到MM新做了个发型怎么说"这种问题,自己用BRIDGE组合一下不就行了.
- 实例: 服务需要需要输出字符串格式时,调字符串格式化类,需要输出HTML格式时,调HTML格式化类
- 实例代码实现
interface FormatterInterface { public function format(string $text); } class PlainTextFormatter implements FormatterInterface { /** * 返回字符串格式。 */ public function format(string $text) { return $text; } } class HtmlFormatter implements FormatterInterface { /** * 返回 HTML 格式。 */ public function format(string $text) { return sprintf('<p>%s</p>', $text); } } abstract class Service { /** * @var FormatterInterface * 定义实现属性。 */ protected $implementation; /** * @param FormatterInterface $printer * 传入 FormatterInterface 实现类对象。 */ public function __construct(FormatterInterface $printer) { $this->implementation = $printer; } /** * @param FormatterInterface $printer * 和构造方法的作用相同。 */ public function setImplementation(FormatterInterface $printer) { $this->implementation = $printer; } /** * 创建抽象方法 get() 。 */ abstract public function get(); } class HelloWorldService extends Service { /** * 定义抽象方法 get() 。 * 根据传入的格式类定义来格式化输出 'Hello World' 。 */ public function get() { return $this->implementation->format('Hello World'); } } $service = new HelloWorldService(new PlainTextFormatter()); $service->get(); //Hello World $service->setImplementation(new HtmlFormatter()); $service->get(); //Hello World
(三)组合模式(Composite)
- 定义
合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。
- 解释例: Mary今天过生日。"我过生日,你要送我一件礼物。""嗯,好吧,去商店,你自己挑。""这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买。""喂,买了三件了呀,我只答应送一件礼物的哦。""什么呀,T恤加裙子加包包,正好配成一套呀,小姐,麻烦你包起来。""……",MM都会用Composite模式了,你会了没有?
- 实例: 想要生成一个form表单框.需要一个text生成类,一个input生成类.一个合成类
- 实例代码实现
interface RenderableInterface { public function render(): string; } class Form implements RenderableInterface { /** * @var RenderableInterface[] */ private $elements; /** * 遍历所有元素,并对他们调用 render() 方法,然后返回表单的完整 * 的解析表达。 * * 从外部上看,我们不会看到遍历过程,该表单的操作过程与单一对 * 象实例一样 * * @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; } } class InputElement implements RenderableInterface { public function render(): string { return '<input type="text" />'; } } class TextElement implements RenderableInterface { /** * @var string */ private $text; public function __construct(string $text) { $this->text = $text; } public function render(): string { return $this->text; } } $form = new Form(); $form->addElement(new TextElement('Email:')); $form->addElement(new InputElement()); $embed->addElement(new TextElement('Password:')); $embed->addElement(new InputElement()); $form->addElement($embed); echo $form->render();
(四)装饰模式(Decorator)
- 定义
装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案,提供比继承更多的灵活性。动态给一个对象增加功能,这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。
- 解释例: Mary过完轮到Sarly过生日,还是不要叫她自己挑了,不然这个月伙食费肯定玩完,拿出我去年在华山顶上照的照片,在背面写上"最好的的礼物,就是爱你的Fita",再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦)再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是Decorator,最终都在修饰我这个人呀,怎么样,看懂了吗?
- 实例: 返回json数据和xml数据
-
实例代码实现
abstract class RendererDecorator { /** * 定义渲染接口变量。 */ protected $wrapped; /** * 传入渲染接口类对象 */ public function __construct($data) { $this->wrapped = $data; } abstract function renderData(): string; } class XmlRenderer extends RendererDecorator { /** * 对传入的渲染接口对象进行处理,生成 DOM 数据文件。 */ public function renderData(): string { $doc = new \DOMDocument(); $doc->appendChild($doc->createElement('content', $this->wrapped)); return $doc->saveXML(); } } class JsonRenderer extends RendererDecorator { /** * 对传入的渲染接口对象进行处理,生成 JSON 数据。 */ public function renderData(): string { return json_encode($this->wrapped); } } $data = 'hello world'; $json = new JsonRenderer($data); $json->renderData(); $xml = new XmlRenderer($data); $xml->renderData();
(五)门面模式(Facade)
- 定义
要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。就如同医院的接待员一样,门面模式的门面类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与门面对象打交道,而不需要与子系统内部的很多对象打交道。
- 解释例: 我有一个专业的Nikon相机,我就喜欢自己手动调光圈、快门,这样照出来的照片才专业,但MM可不懂这些,教了半天也不会。幸好相机有Facade设计模式,把相机调整到自动档,只要对准目标按快门就行了,一切由相机自动调整,这样MM也可以用这个相机给我拍张照片了。
- 实例: 一个保安系统由录像机、电灯组成。保安系统的操作人员需要经常将这些仪器上班启动和下班关闭。
- 实例代码实现
abstract class SecuritySystem { abstract function TurnOn(); abstract function TurnOff(); } class Camera extends SecuritySystem { public function TurnOn() { echo "Turning on the camera".'<br/>'; } public function TurnOff() { echo "Turning off the camera".'<br/>'; } } class Light extends SecuritySystem { public function TurnOn() { echo "Turning on the light".'<br/>'; } public function TurnOff() { echo "Turning off the light".'<br/>'; } } class SecurityFacade { private $securitySystem; public function __construct(SecuritySystem ...$securitySystem) { $this->securitySystem = $securitySystem; } //开启系统 public function TurnOnSystem() { foreach ($this->securitySystem as $key => $value) { $value->TurnOn(); } } //关闭系统 public function TurnOffSystem() { foreach ($this->securitySystem as $key => $value) { $value->TurnOff(); } } } $system = new SecurityFacade(new Camera,new Light); $system->TurnOnSystem(); $system->TurnOffSystem(); /** * Turning on the camera * Turning on the light * Turning off the camera * Turning off the light */
(六)享元模式(Flyweight)
- 定义
为了节约内存的使用,享元模式会尽量使类似的对象共享内存。在大量类似对象被使用的情况中这是十分必要的。常用做法是在外部数据结构中保存类似对象的状态,并在需要时将他们传递给享元对象。
- 解释例: 跟MM在网上聊天,一开头总是"hi,你好","你从哪儿来呀?""你多大了?""身高多少呀?"这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自动的回答,接收到其他的话时再通知我回答.
- 实例: 由抽象享元-具体享元-享元工厂生成享元数据。
-
实例代码实现
interface FlyweightInterface { /** * 创建传递函数。 * 返回字符串格式数据。 */ public function render(string $extrinsicState): string; } class CharacterFlyweight implements FlyweightInterface { /** * 任何具体的享元对象存储的状态必须独立于其运行环境。 * 享元对象呈现的特点,往往就是对应的编码的特点。 * * @var string */ private $name; /** * 输入一个字符串对象 $name。 */ public function __construct(string $name) { $this->name = $name; } /** * 实现 FlyweightInterface 中的传递方法 render() 。 */ public function render(string $font): string { // 享元对象需要客户端提供环境依赖信息来自我定制。 // 外在状态经常包含享元对象呈现的特点,例如字符。 return sprintf('Character %s with font %s', $this->name, $font); } } class FlyweightFactory implements \Countable { /** * @var CharacterFlyweight[] * 定义享元特征数组。 * 用于存储不同的享元特征。 */ private $pool = []; /** * 输入字符串格式数据 $name。 * 返回 CharacterFlyweight 对象。 */ 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); } } $characters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; $fonts = ['Arial', 'Times New Roman', 'Verdana', 'Helvetica']; $factory = new FlyweightFactory(); foreach ($characters as $char) { foreach ($fonts as $font) { $flyweight = $factory->get($char); $rendered = $flyweight->render($font); echo $rendered.'<br/>'; } }
(七)代理模式(Proxy)
- 定义
代理模式(Proxy)为其他对象提供一种代理以控制对这个对象的访问。使用代理模式创建代理对象,让代理对象控制目标对象的访问(目标对象可以是远程的对象、创建开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能.
- 解释例: 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
- 实例: 代理主题角色.
-
实例代码实现
abstract class Subject { // 抽象主题角色 abstract public function action(); } class RealSubject extends Subject { // 真实主题角色 public function __construct() {} public function action() {} } class ProxySubject extends Subject { // 代理主题角色 private $_real_subject = NULL; public function __construct() {} public function action() { $this->_beforeAction(); if (is_null($this->_real_subject)) { $this->_real_subject = new RealSubject(); } $this->_real_subject->action(); $this->_afterAction(); } private function _beforeAction() { echo '在action前,我想干点啥....'; } private function _afterAction() { echo '在action后,我还想干点啥....'; } } // client $subject = new ProxySubject(); $subject->action();//输出:在action前,我想干点啥....在action后,我还想干点啥....
(八)数据映射模式(Data Mapper)
- 定义
描述如何创建提供透明访问任何数据源的对象。数据映射模式,也叫数据访问对象模式,或数据对象映射模式。
- 实例: 持久化数据存储层、驻于内存的数据表现层、以及数据映射本身三者相互独立、互不依赖。这个数据访问层由一个或多个映射器(或者数据访问对象)组成,用于实现数据传输。通用的数据访问层可以处理不同的实体类型,而专用的则处理一个或几个.
-
实例代码实现
class User { /** * @var string */ private $username; /** * @var string */ private $email; public static function fromState(array $state): User { // 在你访问的时候验证状态 return new self( $state['username'], $state['email'] ); } public function __construct(string $username, string $email) { // 先验证参数在设置他们 $this->username = $username; $this->email = $email; } /** * @return string */ public function getUsername() { return $this->username; } /** * @return string */ public function getEmail() { return $this->email; } } class UserMapper { /** * @var StorageAdapter */ private $adapter; /** * @param StorageAdapter $storage */ public function __construct(StorageAdapter $storage) { $this->adapter = $storage; } /** * 根据 id 从存储器中找到用户,并返回一个用户对象 * 在内存中,通常这种逻辑将使用 Repository 模式来实现 * 然而,重要的部分是在下面的 mapRowToUser() 中,它将从中创建一个业务对象 * 从存储中获取的数据 * @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); } } 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; } } $storage = new StorageAdapter([1 => ['username' => 'domnikl', 'email' => 'liebler.dominik@gmail.com']]); $mapper = new UserMapper($storage); $user = $mapper->findById(1); //object(User)#3 (2) { ["username":"User":private]=> string(7) "domnikl" ["email":"User":private]=> string(25) "liebler.dominik@gmail.com" }
(九)依赖注入模式(Dependency Injection)
- 定义
用松散耦合的方式来更好的实现可测试、可维护和可扩展的代码。
- 实例: DatabaseConfiguration 被注入 DatabaseConnection 并获取所需的 $config 如果没有依赖注入模式,配置将直接创建 DatabaseConnection.
-
实例代码实现
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; } } class DatabaseConnection { /** * @var DatabaseConfiguration */ private $configuration; /** * @param DatabaseConfiguration $config */ public function __construct(DatabaseConfiguration $config) { $this->configuration = $config; } public function getDsn(): string { // 这仅仅是演示,而不是一个真正的 DSN // 注意,这里只使用了注入的配置。 所以, // 这里是关键的分离关注点。 return sprintf( '%s:%s@%s:%d', $this->configuration->getUsername(), $this->configuration->getPassword(), $this->configuration->getHost(), $this->configuration->getPort() ); } } $config = new DatabaseConfiguration('localhost', 3306, 'domnikl', '1234'); $connection = new DatabaseConnection($config); $connection->getDsn();
(十)流接口模式(Fluent Interface)
- 定义
用来编写易于阅读的代码,就像自然语言一样
- 实例: 数据查询操作.
-
实例代码实现
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) ); } } $query = (new Sql())->select(['foo', 'bar']) ->from('foobar', 'f') ->where('f.bar = ?'); echo $query; //SELECT foo, bar FROM foobar AS f WHERE f.bar = ?
(十一)注册模式(Registry)
- 定义
目的是能够存储在应用程序中经常使用的对象实例,通常会使用只有静态方法的抽象类来实现(或使用单例模式)。需要注意的是这里可能会引入全局的状态,我们需要使用依赖注入来避免它。
- 实例: 对象存储.
-
实例代码实现
class Register { protected static $objects; static public function set($alias, $object) { self::$objects[$alias] = $object; } static public function get($key) { if (!isset(self::$objects[$key])) { return false; } return self::$objects[$key]; } public function _unset($alias) { unset(self::$objects[$alias]); } } Register::set('LOGGER',new \stdClass); var_dump(Register::get('LOGGER'));
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: