执行及描述任务
笔记
解释器模式
使用场景
基本用不到,如果真的要说的话,就是针对特定录入做一个转换输出,核心其实就是数组的$key
和$value
而已。而这个模式的的好处就在于易于拓展转换的规则。
因为基本不常用,所以就不深入剖析了,等到需要的时候再来看吧。
策略模式
使用场景
只要发现自己正在不断地在继承树中的各个分支上重复同一个算法(无论是通过子类还是通过重复条件语句),请将这些算法抽象成独立的类型。
配合一个UML类图来理解可能更容易一点:
没有使用策略模式前,针对继承树上的2个分支,系统写了完全相同的类来为这2个分支服务。接下来尝试使用策略模式:
继承树中依旧是2个分支,但是我们将所需要的操作整理出来成为单独别的类。这样我们就不必为继承树中的多个分支创建多个类似的类了。
具体实现
<?php
/**
* 策略类
* Interface Math
*/
interface Math{
function calc($op1,$op2);
}
class MathAdd implements Math{
function calc($op1,$op2){
return $op1+$op2;
}
}
class MathDelete implements Math{
function calc($op1,$op2){
return $op1-$op2;
}
}
/**
* 调用类
* Class CMath
*/
class CMath{
private $op1;
private $op2;
private $math;
public function __construct($op1,$op2,Math $math){
$this->op1=$op1;
$this->op2=$op2;
$this->math=$math;
}
public function getResult(){
return $this->math->calc($this->op1,$this->op2);
}
}
$cMath=new CMath(1,2,new MathAdd());
print $cMath->getResult();
只是很简单的一个例子,这里我们将策略相关的类抽象出来,并以显示参数的形式传入构造函数中。这里只写了一个类,并没有继承树,但是实现策略模式的原理是一样的。就是将策略类显示的传入调用的类中,而不是写子类。
这个时候可能会出现杠精或者思维已经混乱的人说:"我就是要为继承树中的两个分支写一些单独的处理类啊,因为就是要做区分啊。”工厂模式了解一下?
观察者模式
这个模式蛮好的,因为太有用了。在这里会介绍2种观察者模式,一种是普遍的观察者模式,另一种是针对特定类编写的观察者。
使用场景
<?php
/**
* 审批类
* Class Approve
*/
class Approve{
/**
* 审批通过
*/
public function pass(){
// 通知部门主管
// 通知经理
// 通知人事
// 通知财务
// 审批通过后的部分代码
}
}
是不是觉得这个代码结构异常的眼熟,没错这不就是我们一般写的代码吗?根据需要一行一行的执行我们设定的操作,这个代码看似没有问题,那得看你从哪个角度看这段代码了,假设我现在新设定了一个审批通过后的通知流程,不需要通知财务了,那么你有什么办法?第一就是写一个新的类,在其中复写pass
方法,但是这样就造成了子类和父类中针对同样的步骤的代码是重复的,最简单的就是你肯定会ctrl+c
和ctrl+v
过去,这个时候你就知道自己的代码有坏代码的味道了。
示例1
<?php
/**
* 审批类
* Class Approve
*/
class Approve implements SplSubject {
private $storage;
public function __construct()
{
$this->storage=new SplObjectStorage();
}
public function attach(SplObserver $observer)
{
$this->storage->attach($observer);
}
public function detach(SplObserver $observer)
{
$this->storage->detach($observer);
}
public function notify()
{
foreach ($this->storage as $obj){
$obj->update($this);
}
}
/**
* 审批通过
*/
public function pass(){
// 审批通过后的部分代码
// do some code
$this->notify();
}
}
/*
* 下面都是观察者
*/
/**
* 经理
* Class Manager
*/
class Manager implements SplObserver{
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
print "已通知经理\r\n";
}
}
/**
* 人事
* Class Human
*/
class Human implements SplObserver{
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
print "已通知人事\r\n";
}
}
/**
* 财务
* Class Finance
*/
class Finance implements SplObserver{
public function update(SplSubject $subject)
{
// TODO: Implement update() method.
print "已通知财务\r\n";
}
}
// 配置审批流程
$approveMode1=new Approve();
$approveMode1->attach(new Manager());
$approveMode1->attach(new Human());
$approveMode1->attach(new Finance());
$approveMode1->pass();
这个代码的好处就在于你将审批的具体流程从审批通过后的代码中剥离出来了。并在控制器中进行具体的配置。
这里再着重说明一下这三个类,是直接封装在PHP标准类中的,专门用于实现观察者模式的。
SplObserver,SplSubject,SplObjectStorage
前2个是接口,最后一个是一个工具类,专门帮你管理观察者的,结合上面的例子去理解吧,有了这3个类的辅助,观察者模式实现起来还是蛮爽的。
示例2
abstract class LoginObserver implements SplObserver{
private $login;
public function __construct(Login $login)
{
$this->login=$login;
}
function update(SplSubject $subject)
{
$subject===$this->login && $this->doUpdate($subject);
}
abstract protected function doUpdate(Login $login);
}
class Manager extends LoginObserver{
protected function doUpdate(Login $login)
{
print "已经通知经理\r\n";
}
}
class Human extends LoginObserver{
protected function doUpdate(Login $login)
{
print "已经通知人事\r\n";
}
}
这里我们创建了1个新的类LoginObserver
,并在其中实现了update
方法,并且定义了一个新的抽象方法:doUpdate
,这么做是为了解决以下问题:
- 为
Login
类定制他的专属观察者,因为如果我们不做类型判断,那么任何继承SplSubject
的类都可以传入对应的观察者中 - 我们实现了接口,但是将具体的实现封装成抽象接口,留给具体的类去实现,也就是说这些观察者类都是为这个
Login
类服务的。
个人感觉,如果装饰模式是在往前拓展类的功能,那么观察者就是在向后拓展类的功能。
访问者模式
使用场景
用于遍历多个组合对象中的每个对象。这里就拿组合模式中的类来做说明。
示例1
<?php
abstract class Union{
protected $unions=array();
abstract function addUnion(Union $union);
// 用于接受访问者类
function accept(Visitor $visitor){
$method="visit".get_class($visitor);
$visitor->$method($this);
foreach ($this->unions as $union){
$union->accept($visitor);
}
}
}
class Archer extends Union{
function addUnion(Union $union)
{
// TODO: Implement addUnion() method.
}
}
class Army extends Union{
function addUnion(Union $union)
{
array_push($this->unions,$union);
}
}
/**
* 访问者类的抽象类
**/
abstract class Visitor{
abstract function visit(Union $union);
public function __call($name, $arguments)
{
$this->visit($arguments[0]);
}
}
/**
* 定义一个访问者类
**/
class ArmyVisitor extends Visitor{
private static $count=0;
function visit(Union $union)
{
self::$count++;
print "遍历第".self::$count."个对象:".get_class($union)."\r\n";
}
}
$archer1=new Archer();
$archer2=new Archer();
$army1=new Army();
$army2=new Army();
$army2->addUnion($archer1);
$army2->addUnion($archer2);
$army2->addUnion($army1);
$armyVisitor=new ArmyVisitor();
$army2->accept($armyVisitor);
这里我们写了一个简单的组合模式的代码。再来我们定义了一个accept
方法来接收访问者。并在其中采用了递归,再去遍历 。关于访问者模式有以下的问题:
- 一次只能设置一个访问者,该访问者会被组合模式中的一个对象和其下的所有对象调用
- 访问者模式的核心其实是递归,也就是在抽象类中的
accept
方法 - 关于调用访问者模式中的什么方法,完全可以按照你的需要来,这里是设置了
visit
+class_name
的形式,再在外部使用魔术方法__call
进行抓取,但是这只是一种实现方式,也可以直接调用visit
方法
访问者存在的问题
首先,虽然完美的符合组合模式,但事实上访问者可以用于任何对象集合。举例来说,你可以把访问者用于每个对象都保存对兄弟节点引用的一组对象。
其次,外部化操作可能破坏封装。也就是说,你可能需要公开被访问对象的内部来让访问者能对他们做任何有用的操作。
其中第二句感触尤为深刻,这里我们在访问者类中肯定要调用被访问对象的方法,这样两者就在访问者类内部耦合了,这样很危险。
命令行模式
个人感觉在工作中用到的可能性不大,因为一般情况下像书中的模式,也就是我们一般使用框架时的路由到具体控制器的模式,所以使用的情况不多。这里就不再赘述了。
问题
- 策略模式的使用场景是什么?
针对类的继承树中的2个分支有类似的操作时,可以使用策略模式,将处理操作抽象出单独的类来执行,而不是为2个分支上的类设置单独的操作类。
- 简单概述策略模式实现的原理。
将操作抽象出单独的类,并定义一个方法,将操作类传入对应的类中。
- 观察者模式的使用场景是什么?简析观察者模式的原理。
SplObserver,SplSubject,SplObjectStorage
这三个类和观察者模式有什么联系?如何使用?- 观察者模式中的示例2是为了说明什么问题?
- 访问者模式的使用场景是什么?简析访问者模式的实现原理。
- 访问者模式存在什么弊端?
总结
感觉结合起来讲解可能会好一点,第9章是如何创建对象,第10章是如何管理对象,第11章,就是如何编写类中的方法,其中不论是策略模式,观察者模式,访问者模式,其目的都是为了减少代码之间的耦合。