#### 第21章:
#### 设计模式
#### 21.1 面向对象编程(OOP)
##### 21.1.1 为什么采用面向对象编程
##### 解决问题更容易
设计计算机程序是为了解决人类的问题。所有有了将大问题分解为小问题的技术"动态编程"。
我们先尝试分解一个问题:五月是否可以去西雅图旅游。
1. 西雅图是否存在?(yes/no):答案=yes。
2. 西雅图是否有机场?(yes/no):答案是=yes,机场标识=SEA。
3. 五月有没有去SEA的航班?(yes/no):答案是=yes。
4. 五月有没有计划外的事情?(yes/no):答案=可能有。
5. 五月前是否可以把计划外的事情做完?(yes/no):答案=yes,可以做完;no,不能做完。
6. 西雅图的旅游业和商业能保证安全嘛?(yes/no):答案=yes。
7. 从我国有到美国(西雅图所在国家)的签证吗?(yes/no):答案=yes。
8. 美国允许中国人入境吗?(yes/no)答案=yes。
9. 需要接种疫苗吗?(yes/no)答案=yes。
以上总体可以将`五月是否可以去西雅图旅游`看作一个复杂的事情,只是细分为了多个小问题,提供了`yes`和`no`
两种答案。而这多个小问题还可以继续细分为更多小问题,比如答案是`可能`的,说明需要询问更多问题得到一个明确答案。
##### 模块化
将一个问题细分为多个子问题的过程就是模块化。
:-: ![](https://img.kancloud.cn/46/09/4609d91b0d2028c28ed4fdac14750569_350x432.png)
问题模块化过程
OOP是为了将复杂的问题简单化,将问题"分而治之"。越复杂的问题模块化的意义越大。
##### 类与对象
对某个问题模块化后需要`组织模块`,并且需要让它们相互协作共同处理解决大问题,所以将一个模块看作是相关处理函数和属性的集合。我们将这种集合称为`类`,它拥有`属性`和`方法`,它的作用是用来专门处理这个模块。
##### 单一职责
可以把类看作是有共同特征对象的集合。比如"五月是否可以去西雅图旅游"、"六月是否可以去西雅图旅游"、"五月是否可以去夏威夷旅游"可以看作有共同特征的由同一模块化问题抽象出来的类所具体生成的三个解决问题的对象。所以我们得到面了向对象的`首要原则:单一职责,一个类应当只有一个职责`,所有满足单一职责的类在OOP编程中都可以看作是"高质量"的类。
并不是指一个类不能有多个职责,而是要把一个复杂的问题分解为简单问题就是为了将它转化为多个容易解决的问题逐个解决。这样能够更容易组织模块和解决问题的逻辑。
##### 21.1.2 速度
程序员应该关心两种速度:`代码在系统中运行的速度`和`创建、更新程序需要花费的时间`。
##### 代码运行的速度
影响代码运行速度的因素有:硬件能力(CPU处理能力、内存大小以及读写能力、网络传输处理能力及介质等),系统层及软件能力,代码质量(空间复杂度、时间复杂度)等。
##### 开发和修改的速度
使用设计模式就是为了解决开发和修改的速度。不至于使一个开发任务总是需要重构系统。
##### 团队速度
团队协作的开发任务存在速度问题。处理大而复杂的程序需要了解协商一个共同的计划和目标,便于高效创建、维护大型程序。OOP和设计模式就有很多公用的东西可以加快团队工作。设计模式提供了一种编程方法允许团队中的程序员分头工作,最后将工作汇聚在一起。就像一个生产线,每个小组负责不同的部件。小组之间需要一个开发模式理解不同部件的关系。
##### 21.1.3 顺序和过程式编程有什么问题
##### 顺序编程
```
<?php
$a = 10;
$b = 20;
$total = $a + $b;
echo $total;
?>
```
程序逐条执行(建立一系列代码执行一个程序)称为顺序编程。
##### 过程式编程
```
<?php
function addTotal($a,$b)
{
$total = $a + $b;
echo $total;
}
addTotal(10,20);
?>
```
过程式编程通过引入函数,可以在任何一个地方利用一条语句调用某个操作完成一个程序序列。函数或过程允许程序员将代码序列分为多个模块,方便重用。
其类似OOP,但是没有提供类。类对象可以处理自己的数据结构,这式函数无法单独做到的。比如某个类对象可以在生成的时候初始化自己的初始属性值,这是属于它本身的数据结构;也可以在序列处理传递的过程中改变其属性值,从而改变其内在的数据结构。过程式编程合作困难,不同的开发组成员无法采用OOP那样轻松地处理相互关联且独立地类。
#### 21.2 OOP的基本概念
##### 21.2.1 抽象
抽象指示一个对象的基本特征,对其提供了清晰的定界以便它与其他对象区分开来。
所有类都是对数据的一组操作的抽象。
##### 抽象类和抽象方法
抽象类以abstract命名:
```asp
<?php
abstract class onePersonAbstract
{
public $age;
abstract public function shout($name);
public function study($curriculums)
{
echo $curriculums . "</br>";
}
}
```
抽象类的特点:
- 可以声明属性。
- 可以有抽象方法或者已实现的方法。
- 抽象类不能实例化只能被继承。
- 继承抽象类的子类必须实现抽象类的所有抽象方法才能实例化,否则也应加上abstract作为抽象类。
- 抽象类的子类也是抽象类的情况下,其抽象方法的访问控制(public、protected、private)不可比父抽象类严格。
继承抽象类的子类:
```
<?php
class onePersonExample extends onePersonAbstract
{
public function shout($name)
{
echo $name . "</br>";
}
public function study($curriculums)
{
echo $curriculums . "</br>";
}
}
$p = new onePersonExample;
$p->shout('李');
$p->study('数学');
```
这个子类继承了抽象类onePersonAbstract的$age属性以及study方法,并实现了study方法。并且子类同样可以重写父抽象类实现的方法。
:-: ![](https://img.kancloud.cn/1b/d5/1bd595a2eee2ab0b788be8373befb9fb_533x275.png)
##### 21.2.2 特性Trait
PHP不允许多继承,所以提供了一个Trait结构,称为特性,是一种代码重用机制。当一个类继承另一个类,同时使用Trait,相当于多重继承。
```
<?php
trait Dog{
public $name="dog";
public function bark(){
echo "This is dog";
}
}
class Animal{
public function eat(){
echo "This is animal eat";
}
}
class Cat extends Animal{
use Dog;
public function drive(){
echo "This is cat drive";
}
}
$cat = new Cat();
$cat->drive();
echo "<br/>";
$cat->eat();
echo "<br/>";
$cat->bark();
?>
```
:-: ![](https://img.kancloud.cn/8c/de/8cde251fa324c41e69b4c84271a96c98_520x283.png)
##### 21.2.3 接口interface
一般约定接口以为`I`开头:
```
<?php
interface IMethodHolder
{
public function getInfo($info);
public function sendInfo($info);
public function calculate($first,$second);
}
```
接口的特点:
- 不能有具体实现的方法或声明变量。
- 只负责定义功能,不负责具体实现,相当于方法是纯粹的模板,且方法的访问控制只能是public。
- 可以声明常量。
- 实现接口的类必须实现接口的所有方法,否则应该作为抽象类加上abstract关键字。
- 一个类可以继承多个接口(接口同样可以继承多个接口)。
继承接口的类必须实现接口的方法,使用implements关键字,继承多个接口以`,`隔开:
```
<?php
include_once('ImethodHolder.php');
class methodExample implements ImethodHolder
{
public function getInfo($info)
{
echo "This info is " . $info . "</br>";
}
public function sendInfo($info)
{
return $info;
}
public function calculate($first,$second)
{
$num = $first + $second;
return $num;
}
public function useMethod(){
$this->getInfo('message success');
echo $this->sendInfo('send message success');
}
}
$worker = new methodExample();
$worker->useMethod();
```
:-: ![](https://img.kancloud.cn/2a/c9/2ac98d1b28fee72ad3bae6841bf3f95c_523x275.png)
继承接口的类除了实现接口必须实现的方法外,还可以更具需要增加更多方法和属性。
##### 21.2.4 封装
来自O'REILLY《PHP设计模式》对封装的解释是:`封装是划分一个抽象的诸多元素的过程,这些元素构成该抽象的结构和行为;封装的作用是将抽象的契约接口与其实现分离`。
其通俗的意思是:封装提供了标准的契约接口将复杂的实现过程透明化。例子:你要使用汽车,汽车由很多对象构成,比如内燃机,机械控制系统,传动器,计算机程序等。你并不了解这些运行的原理,但是你只需要拧动钥匙就可以发动汽车,这就是封装了使用汽车的过程。放在编程上面的例子:我想知道运行信息info,运行时有多种复杂的对象构成,比如请求解析,响应生成,数据结构创建维护,缓冲区维护,事件响应,格式生成等。但是我只需要得到一个标准的运行信息info,于是我将前面的方法组合在一起加以修饰,封装了一个getInfo()方法,可以得到标准信息。
##### 利用控制访问的封装
利用控制访问可以很好的对程序进程封装。
- private:仅内部可以访问。
- protected:仅内部和继承者可以访问。
- public:公开的,用于对内外都可以使用。可以作为契约标准提供封装的服务。
##### 21.2.5继承
如果一个类扩展了另一个类,就会拥有这个类的所有属性和方法。
##### 21.2.6 多态
调用相同的接口(封装程序)可以完成不同的工作。
多态例子:
```
<?php
include_once('mongoDb.php');
include_once('mysql.php');
class db
{
function __construct($dbname)
{
$db = new $dbname;
return $db;
}
}
$mongoDb = new db('mongoDb');
$mysql = new db('mysql');
```
上面的代码通过一个公共接口(__construct的控制访问属于public)可以实现不同的工作。
多态例子:
```
<?php
interface Imethod
{
public getNum($num);
public setNum($num);
}
class bank implements Imethod
{
public $bankNum;
public getNum($num)
{
return $num;
}
public setNum($num)
{
return $this->bankNum = $num;
}
}
class restaurant implements Imethod
{
public $restaurantNum;
public getNum($num)
{
if(is_int($num)){
return $num;
}
return "不是整数";
}
public setNum($num)
{
if(is_int($num)){
return $this->restaurantNum = $num;
}
return "不是整数";
}
}
```
bank和restaurant同样实现了Imethod接口。但是它们的内部方法实现的功能不一样。
#### 21.3 MVC
MVC指的是`模型-视图-控制器`设计模式。可以实现`松耦合`和`重新聚焦`。
- 模型:负责数据部分,处理企业和应用逻辑。
- 视图:作为窗口显示需要显示的信息。例如最终需要显示出来的页面、图片、文本等信息都是视图的一部分。
- 控制器:控制器调用模型、视图,处理好其逻辑和业务顺序,并关联其上下文,达到控制数据和表现信息的目的。
在很多PHP框架代码中,将代码分为controller(控制器层)、data-dao-model(model负责调用db的方法、dao负责对db的读写方法,data负责从db拿出数据的应用逻辑算法,总体为模型层)、view(视图层)。其控制器层负责调用模型层和视图层,用以应用逻辑生成结果反馈用户。
```
<?php
class numController
{
//控制器接口addNum
public function addNum($id,$num)
{
$data = new numData;
//控制器负责组织模型、视图的业务逻辑和调用顺序,最终将结果反馈
$totalNum = $data->addNum($id,$num); //控制器调用模型层经过内部封装的方法实现应用逻辑
View::assign('totalNum',totalNum); //控制器调用视图层将结果反馈用户
}
}
```
通过MVC,一个控制器类可以实现多个方法;一个模型层类可以实现多种数据操作方法。通过组合可以使其能解决数个最小子问题,且代码不重复实现了松耦合。试想如果每遇到一个小问题就要重复代码,对研发效率和可维护性都是挑战;同时可以聚焦在具体的小问题上,通过定制的控制器调用专用的最小数量最小作用的数据处理方法解决问题以专用的视图方法将结果反馈。
#### 21.4 设计模式原则
##### 按接口而不是按实现来编程
设计一个类的时候不是为了应用逻辑而按照应用逻辑的具体实例设计,而是按照接口的方式设计为抽象类或者接口数据类型的实例。这样设计的才是一个能解决共同特征问题的类,而不仅是一个仅能解决本次实现的实例,前者可以解耦,后者会使得设计和代码冗余。
##### 尽量选择对象组合而不是类继承
子类通过组合实现多个类的功能,而不是仅通过继承父类,同样都可以进行功能扩展。其缺点是:依赖关系比继承复杂。其优点更加突出:避免维护多个继承层次的类,避免更改上层层次导致的错误;子类不会因为继承到大量的不用的属性和方法变得臃肿;使用组合可以将一个类的任务传递给另一个类,称为"委托",使用组合可以使用一个类或者一组类完成一系列任务。所以设计模式方法建议使用`浅继承`。
##### 设计模式的组织
设计模式按作用分类:
1. 创建型模式:组合一组灵活的行为到更为复杂的集合中。提供一些方法封装这些行为组合。
例如有一个类是负责请求处理,另一个类负责响应处理,需要另外一个类隐式地既处理请求又处理响应,就需要用创建型模式将两个类组合并使其行为组织到另外一个类中,这时可以创建新的方法。
2. 结构型模式:保证组合结构的结构化。类通过继承来组合接口或实现,对象通过组合对象使之产生关联来建立新功能的方法。
3. 行为型模式:负责算法和对象之间职责的分配。
按照范围分类:
1. 类模式:重点在于类与子类的关系。
2. 对象模式:与类模式的区别在于对象模式强调的是可以在运行时改变对象具有动态性。
| 类型 | 范围 | 模式名 | 可能变化的方面 |
| ------ | ------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------ |
| 创建型 | 类<br />对象 | 工厂方法<br />原型 | 实例化对象的子类<br />实例化对象的类 |
| 结构型 | 类<br />对象 | 适配器*<br />适配器*<br />装饰器 | 对象的接口<br />对象职责而不是派生类 |
| 行为型 | 类<br />对象<br />对象<br />对象<br />对象 | 模板方法<br />状态<br />策略<br />职责链<br />观察者 | 算法中的步骤<br />对象状态<br />算法<br />可以满足的对象<br />依赖于其他对象的对象数;当前可以有多少个依赖对象 |
设计模式作用、范围和变化
##### 关系
设计模式里有多种关系。
##### 相识关系
一个参与者包含另一个参与者的引用。
```
<?php
class A extends base
{
private $obRequest;
protected function request()
{
$this->obRequest = new obRequest;
$this->obRequest->request();
}
public function pass($str)
{
//口令
if($str == 'oqshhhasdh'){
$this->request();
}
print('$str error');
}
}
```
A的方法中包含了obRequest的引用。
##### 聚合关系
和相识关系类似,不过生命周期相同。
```
<?php
class A extends base
{
private $obRequest;
public __construct(obRequest $obRequest){
$this->obRequest = new obRequest;
}
public function pass($str)
{
//口令
if($str == 'oqshhhasdh'){
$this->obRequest->request();
}
print('$str error');
}
}
```
实例化A的时候构造函数就实例化了obRequest,使两个类拥有同样的生命周期。
##### 创建关系
一个对象创建另一个对象的实例。
#### 21.5 UML统一建模语言
UML是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言,是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具,独立于任何具体程序设计语言。使用UML能够帮助更好地设计程序。
![](https://img.kancloud.cn/0e/bb/0ebb1d6cead7ff6e0a1237c82e2d620a_341x311.png)
UML类图
:-: ![](https://img.kancloud.cn/32/b1/32b145d65a1b4dd4f2bc4116683c0324_515x209.png)
对象图
:-: ![](https://img.kancloud.cn/d1/65/d16539a4ae4e1b9e20c4b34e14288eb1_579x352.png)
交互图
PHP有UML扩展可以生成类关系图,有兴趣地同学可以自行学习。
#### 21.6 创建型设计模式
创建型设计强调实例化过程,设计时目的是隐藏实例创建过程,封装实例内部逻辑,其模式包括:
- 抽象共创
- 生成器
- 工厂方法
- 原型
- 单例
##### 21.6.1 工厂方法设计模式
当一个类创建对象数量固定或需要创建有类似特征数量不定的对象时,可以考虑工厂方法设计模式。
工厂模式例子:
- 工厂抽象类
```
<?php
abstract class Creator
{
protected abstract function factoryMethod();
public function startFactory()
{
$mfg = $this->factoryMethod();
return $mfg;
}
}
```
- 产品接口
```
<?php
interface product
{
public function getProperties();
}
```
- 输出文字的工厂
```
<?php
include_once(Creator.php);
include_once(textProduct.php);
class textFactory extends Creator
{
protected function factoryMethod()
{
$product = new textProduct();
return($product->getProperties());
}
}
```
- 输出图片的工厂
```
<?php
include_once(Creator.php);
include_once(graphicProduct.php);
class graphicFactory extends Creator
{
protected function factoryMethod()
{
$product = new graphicProduct();
return($product->getProperties());
}
}
```
- 文字产品
```
<?php
include_once(product.php);
class textProduct implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "这是文字产品";
return $this->mfgProduct;
}
}
```
- 图片产品
```
<?php
include_once(product.php);
class graphicProduct implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "这是图片产品";
return $this->mfgProduct;
}
}
```
- 客户
```
<?php
include_once(graphicFactory.php);
include_once(testFactory.php);
class client
{
private $someGraphicObject;
private $someTextObject;
public function __construct()
{
$this->someGraphicObject = new graphicFactory();
echo $this->someGraphicObject->factoryMethod();
$this->someTextObject = new textFactory();
echo $this->someTextObject->factoryMethod();
}
}
```
此客户类需要同时输出图片和文字产品。所以它将图片工厂和文字工厂聚合与自己聚合。并由文字工厂生产文字,图片工厂生产图片。如果此时只需要图片产品:
```
<?php
include_once(graphicFactory.php);
include_once(testFactory.php);
class client
{
private $someGraphicObject;
private $someTextObject;
public function __construct()
{
$this->someGraphicObject = new graphicFactory();
echo $this->someGraphicObject->getProperties();
}
}
```
此时只需要把文字工厂去掉。并且由于产品方法getProperties()有完全不同的实现,有多态的作用。
##### 一个工厂多个产品
这样的工厂可以创建不同的产品,例如:图片工厂可以产生不同的图片形式、呈现效果。
- 工厂抽象类
```
<?php
abstract class Creator
{
protected abstract function factoryMethod(product $product);
public function doFactory($productNow)
{
$mfg = $this->factoryMethod($productNow);
return $mfg;
}
}
```
- 工厂
```
<?php
include_once(Creator.php);
class factory extends Creator
{
private $country;
protected function factoryMethod(product $product)
{
$this->country = $product;
return($this->country->getProperties());
}
}
```
- 产品1
```
<?php
include_once(product.php);
class product1 implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "产品1";
return $this->mfgProduct;
}
}
```
- 产品2
```
<?php
include_once(product.php);
class product2 implements product
{
private $mfgProduct;
public function getProperties()
{
$this->mfgProduct = "产品2";
return $this->mfgProduct;
}
}
```
这样通过一个工厂我们可以创建多种产品:
```
<?php
include_once(factory.php);
class client
{
private $factory;
public function __construct()
{
$this->factory = new factory();
echo $this->factory->doFactory(new product1);
echo $this->factory->doFactory(new product2);
}
}
```
这样可以根据需要增加产品和创建产品。
在前面PHP的章节我我们讲过一个简单的工厂模式,我们来回忆以下:
```
<?php
class client
{
private $db;
function __construct($dbClass,$info)
{
$this->db = new factoryDb($dbClass,$info);
}
public function insert($arr)
{
try{
$this->insert($arr);
}catch(Exception $e){
echo "写入数据错误";
}
}
}
//mysql
$db1 = new client('mysql',['user1','passwd1']);
$db1->insert(['level'=>1,','apple']);
//mongodb
$db2 = new client('mongodb',['user2','passwd2']);
$db2->insert(['age'=>1,['color'=>'red','price']]);
```
##### 21.6.2 原型设计模式
需要创建某个原型对象的多个实例时就要使用原型模式。例如游戏开发中可以通过克隆一个原型士兵增加军队的人数和兵种。
```
<?php
abstract class humans
{
public $name;
public $photo;
}
class person extends humans
{
public function __construct()
{
$this->name = '大兵';
$this->photo = 'soldier.png' . '</br>';
}
public function exec()
{
echo "这是" . $this->name . '</br>';
echo $this->photo;
}
}
$soldier1 = new person();
$soldier1->exec();
$soldier2 = clone $soldier1;
$soldier2->name = '小兵';
$soldier2->exec();
$soldier1->exec();
```
注意:克隆不会启动构造函数。
:-: ![](https://img.kancloud.cn/4c/89/4c8924afbcbfade86f294a9dbd53dcaa_516x279.png)
##### 现代企业组织的原型设计模式应用
现代企业系统复杂而庞大,但又有很多共同特征。可以利用原型设计模式设计程序。例如设计工程部负责创建产品,管理部负责处理资源协调和组织、市场部负责销售和推广。
#### 21.7 结构型设计模式
结构型模式研究组合对象和类构成更大的结构。部分结构型设计模式:
- 适配器模式(类和对象)
- 桥接模式
- 组合模式
- 装饰器模式
- 外观模式
- 享元模式
- 代理模式
##### 21.7.1 适配器模式
在需要的场景下提供对应适配的模块就是适配器模式。例如:PC端和手机端分别为不同的页面呈现。当我们研发了一个PHP系统可以做各种工作(数据库处理、页面布局、图片展示等),但是仅仅做了PC端的页面。为了制作移动端页面我们不能将所有的程序推翻重做或者另外再做一套系统,而是应该适配上移动页面,使之兼容原系统功能且能有另外的页面呈现。
页面呈现适配例子:
- PC处理类
```
<?php
class pc
{
private $info;
private $width;
public function request($info,$width)
{
$this->info = $info;
$this->width = $width;
return $this->view();
}
}
```
- 移动端处理类
```
<?php
class moble
{
private $info;
private $width;
public function request($info,$width)
{
$this->info = $info;
$this->width = $width;
return $this->view();
}
}
```
- 页面处理适配接口
```
<?php
interface Itarget
{
function infoHandler(); //用于不同场景的不同处理
}
```
- 移动端适配器
```
<?php
class mobleAdapter extends moble implements Itarget
{
public function infoHandler(){
View::assign('info',$this->info);
View::fetch('moble');
}
}
```
移动端适配器既继承了移动端处理类的方法,也实现了移动端基于页面适配接口的特定方法,并没有影响PC类和PC处理类的表现。对对象适配模式有兴趣的同学可以自行学习:比如通过一个适配器传入pc处理类或者移动处理类就能分别处理pc和移动。
##### 21.7.2装饰器模式
装饰器模式向现有的对象增加对象,有时候称为`包装器`。需要对现有对象增加新功能而又不想影响其他对象时可以使用装饰器模式。
添加商品计算总价的包装器例子:
- 包装基础抽象类
```
<?php
abstract class Icomponent
{
protected $things;
abstract public function getThings();
abstract public function getPrice();
}
```
- 装饰器接口
```
<?php
abstract class Decotator extends Icomponent
{
abstract public function getThings();
abstract public function getPrice();
//可以实现按照需要增加方法和属性
}
```
- 组件
```
<?php
class BasicThings extends Icomponent
{
public function __construct()
{
$this->things = 'Basic Things' . '</br>';
}
public function getThings()
{
return $this->things;
}
public function getPrice()
{
return 10;
}
}
```
- 装饰器(a)
```
<?php
class a extends Decotator
{
public function __construct(Icomponent $thingNow)
{
$this->things = $thingNow;
}
public function getThings()
{
return $this->things->getThings() . 'a';
}
public function getPrice()
{
return 12 + $this->things->getPrice();
}
}
```
- 装饰器(b)
```
<?php
class b extends Decotator
{
public function __construct(Icomponent $thingNow)
{
$this->things = $thingNow;
}
public function getThings()
{
return $this->things->getThings() . 'b';
}
public function getPrice()
{
return 13 + $this->things->getPrice();
}
}
```
- 装饰器(c)
```
<?php
class c extends Decotator
{
public function __construct(Icomponent $thingNow)
{
$this->things = $thingNow;
}
public function getThings()
{
return $this->things->getThings() . 'c';
}
public function getPrice()
{
return 15 + $this->things->getPrice();
}
}
```
- 使用组件并按需求添加装饰器
```
<?php
class Client
{
private $things;
public function __construct()
{
$this->things = new BasicThings();
$this->things = $this->wrapComponent($this->things);
echo $this->things->getThings();
echo $this->things->getprice();
}
/*
c的$things属性是b对象,b的$things属性是a对象,a的$things属性是BasicThings对象
再配合装饰对象的链式调用方法可以增加装饰对象或者减少装饰对象来增减其功能
*/
private function wrapComponent(Icomponent $component)
{
$component = new a($component);
$component = new b($component);
$component = new c($component);
return $component;
}
}
$worker = new Client();
```
这样随时可以在组件BasicThings对象里增减'abcdefg'等功能对象。可以把组件BasicThings想象成一个巨大的空间,里面可以随意组合各种功能。
:-: ![](https://img.kancloud.cn/a6/9b/a69b48b085a539093a0d142b01f045b5_525x286.png)
回忆一下数据结构队列的知识,组件对象添加装饰对象,并且执行装饰对象功能的顺序是不是按照先添加的先执行的顺序执行程序的呢?上面组件按照abc顺序添加装饰,并且先调用组件基础功能后按照abc顺序调用装饰的功能。
上面是单组件的装饰器实现,对于多组件的装饰器实现,只需要在添加组件再按需要实例化相应一个或者多个组件完成工作。对于多组件的具体装饰器可以为一个装饰器对应一个功能选项或一个装饰器对应多个功能选项。
##### 总结
程序越庞大,OOP编程和结构性设计模式就月有用。
#### 21.8 行为型设计模式
行为型设计模式强调参与者之间的通信,部分模式名称:
- 职责链模式
- 命令模式
- 解释器模式
- 迭代器模式
- 中介这模式
- 备忘录模式
- 观察者模式
- 状态模式
- 模板方法模式(类设计模式)
- 策略模式
- 访问者模式
##### 21.8.1 模板方法模式
如果明确算法中的步骤,步骤可以采用不同的方法实现时,可以采用模板方法模式。模板方法模式一般由一个抽象类和一个或多个实现抽象类的具体类组成。
例如一个计算数字的例子:
- 模板方法抽象类
```
<?php
abstract class NumberMath
{
protected $first;
protected $second;
public function templateMethod($num1,$num2)
{
$this->first = $num1;
$this->second = $num2;
$this->addNum();
$this->multiplyNum();
}
abstract protected function addNum();
abstract protected function multiplyNum();
}
```
- 模板方法具体实现类
```
<?php
class NumberOne extends NumberMath
{
protected function addNum()
{
echo($this->$first + $this->$second);
}
protected function multiplyNum()
{
echo($this->$first * $this->$second);
}
}
```
- 客户
```
<?php
class Client
{
function __construct()
{
$num1 = 10;
$num2 = 20;
$numObject = new NumberOne();
$numObject->templateMethod(num1,$num2);
}
}
$worker = new Client();
```
在模板方法抽象类中templateMethod方法已经确定算法步骤的情况下,可以有不同的模板方法具体实现类封装不同的步骤方法供这个算法步骤使用。也就是说模板方法具体实现类可以有一个或者多个,因为算法的步骤是固定的,只是算法的实现不同。
##### 21.8.2 职责链模式
需要将一个对象经过一个个步骤进行处理返回最终对象,并且步骤的数量顺序是可以调整时,可以考虑使用职责链模式。我们在PHP的框架中经常遇到称为中间件(或者钩子)的过程,它就是使用了职责链模式:会根据情况添加特殊处理的中间件程序'abcde...',对象由a处理后返回给b处理再返回给c处理直到中间件程序全部处理完成交由下一个程序步骤,这就是职责链。从一个场景去理解职责链,假如你在一家公司有时候需要申请办公金用于工作,1000以内要上报小组长,1000-3000以上要上报VP,3000以上要上报总经理。如果你要申请3100元的办公金,你首先要上报小组长,小组长审批通过后会上报VP,VP审批通过后会上报总经理,总经理审批通过后才能使用这3100元。并且在任何环节都可以有特殊处理甚至有中断权,比如小组长审批时可以直接拒绝,这个申请就不会传到VP或者总经理那一级去。
##### 21.8.3 状态设计模式
状态模式允许对象改变状态时改变其行为,是一种最有用的模式。比如游戏角色的状态改变导致的动作变化。在PHP程序中状态改变太依赖条件语句的情况下会造成程序混乱,成为负担。此时从另外一个角度"对象"的状态考虑,就不需要查看对象的控制流,使之"以状态为中心"。
##### 理解状态机
触发器请求一个改变后发生变迁,变迁是及时的可能很快也可能有很复杂的过程,最后达到另外一个状态。
状态模型的本质:
- 状态
- 变迁
- 触发器
将一个台灯比作状态机:开灯关灯就是状态,触发器就是开关,变迁就是触发器控制的开灯关灯状态变化的过程。
状态机例子:
- 上下文信息类
```
<?php
class Context
{
private $offState; //用以保存关状态对象
private $onState; //用以保存开状态对象
private $currentState; //当前状态对象
public function __construct()
{
$this->offState = new OffState($this);
$this->onState = new OnState($this);
//开始状态设置为Off
$this->currentState = $this->offState;
}
//触发器方法 开
public function turnOnlihgt()
{
$this->currentState->turnLightOn();
}
//触发器方法 关
public function turnOfflihgt()
{
$this->currentState->turnLightOff();
}
//设置状态
public function setState(IState $state)
{
$this->currentState->$state;
}
//获得开状态
public function getOnState()
{
return $this->onState;
}
//获得关状态
public function getOnState()
{
return $this->offState;
}
}
```
上下文类用以保存所有的状态和当前状态,以及提供设置/获取/触发状态的方法。
- 状态接口
```
<?php
interface IState
{
public function turnLightOn();
public function turnLightOff();
}
```
状态接口依据所有状态指定必须完成的状态处理抽象方法,供状态类必须完成。
- 开状态类
```
<?
class OnState implements IState
{
$private $context;
public function __construct($contextNow)
{
$this->context = $contextNow;
}
public function turnLightOn()
{
echo "已经打开";
}
public function turnLightOff()
{
$this->context->setState($this->context->getOffState);
}
}
```
- 关状态类
```
<?
class OffState implements IState
{
$private $context;
public function __construct($contextNow)
{
$this->context = $contextNow;
}
public function turnLightOn()
{
$this->context->setState($this->context->getOnState);
}
public function turnLightOff()
{
echo "已经关闭";
}
}
```
- 客户
```
<?php
class Client
{
private $context;
public function __construct()
{
$this->context = new Context();
$this->context->turnOnlihgt();
$this->context->turnOnlihgt();
$this->context->turnOflihgt();
$this->context->turnOflihgt();
}
}
```
:-: ![](https://img.kancloud.cn/0f/d0/0fd09535d7a8ef6a18efdc4c9427c7b9_518x282.png)
##### 添加状态
如何让状态机记录并处理更多的状态。
- 上下文类记录更多状态对象。
- 状态接口制定更多状态处理方法。
- 具体状态实现状态接口里每种状态变迁的方法,并做好状态边界检测(比如灯关了不能再关,飞行器左边界不能再向左走)。
为上面可以添加颜色状态(白、蓝、黄光),有兴趣的同学可以自己试试。
#### 21.9 代理模式
代理模式强调的是请求到达真实对象前先经过"守门人"。"守门人"会在此之间做检测、判断、分派、信息处理、特殊处理等。
- 请求接口
```
<?php
interface ISubject
{
function request();
}
```
- 代理(守门人)
```
<?php
class proxy implements ISubject
{
public function check()
{
//code
}
public function request()
{
$this->relaSubject = new RelaSubject();
$this->relaSubject->request();
}
}
```
- 真实对象
```
<?php
class RelaSubject implement ISubject
{
public function request()
{
echo "请求成功";
}
}
```
- 客户
```
<?php
class Client
{
private $proxy;
function __construct()
{
//伪代码
$this->proxy = new proxy();
if($this->proxy->check() === true){
$this->proxy->request();
}else{
echo "信息错误不能传入";
}
}
}
```