28.4用了模式的实现

代码结构图 第二十八章 男人和女人——访问者模式 - 图1代码实现
‘状态’的抽象类和‘人’的抽象类

  1. abstract class Action
  2. {
  3. //得到男人结论或反应
  4. abstract function GetManConclusion($concreteElementA);
  5. //得到女人结论或反应
  6. abstract function GetWomanConclusion($concreteElementB);
  7. }
  8. abstract class Person
  9. {
  10. //接受
  11. abstract function Accept($visitor);
  12. }

这里关键就在于人就只分男人和女人,这个性别的分类是稳定的,所以可以在状态类中,增加‘男人反应’和‘女人反应’两个方法,方法个数是稳定的,不会很容易的发生变化。而‘人’抽象类中有一个抽象方法‘接受’,它是用来获得‘状态’对象的。每一种具体状态都继承‘状态’抽象类,实现两个反应的方法。
具体“状态”类

  1. //成功
  2. class Success extends Action
  3. {
  4. public function GetManConclusion($concreteElementA)
  5. {
  6. echo basename(str_replace('\\', '/', get_class($concreteElementA))) . ' ' .
  7. basename(str_replace('\\', '/', get_class($this))) . ' 时,背后多半有一个伟大的女人' . PHP_EOL;
  8. }
  9. public function GetWomanConclusion($concreteElementB)
  10. {
  11. echo basename(str_replace('\\', '/', get_class($concreteElementB))) . ' ' .
  12. basename(str_replace('\\', '/', get_class($this))) . ' 时,背后大多有一个不成功的男人' . PHP_EOL;
  13. }
  14. }
  15. //失败
  16. class Failing extends Action
  17. {
  18. //与上面代码类同,省略
  19. }
  20. //恋爱
  21. class Amativeness extends Action
  22. {
  23. //与上面代码类同,省略
  24. }

“男人”类和“女人”类

  1. //男人
  2. class Man extends Person
  3. {
  4. public function Accept($visitor)
  5. {
  6. //首先在客户端程序中将具体状态作为参数传递给
  7. //“男人”类完成了一次分派,然后“男人”类调用
  8. //作为参数的“具体状态”中的方法“男人反应”,
  9. //同时将自己($this)作为参数传递进去。这便
  10. //完成了第二次分派
  11. $visitor->GetManConclusion($this);
  12. }
  13. }
  14. //女人
  15. class Woman extends Person
  16. {
  17. public function Accept($visitor)
  18. {
  19. $visitor->GetWomanConclusion($this);
  20. }
  21. }

这里需要提一下当中用到一种双分派的技术,首先在客户端程序中将具体状态作为参数传递给“男人”类完成了一次分派,然后“男人”类调用作为参数的“具体状态”中的方法“男人反应”,同时将自己($this)作为参数传递进去。这便完成了第二次分派。
双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型。‘接受’方法就是一个双分派的操作,它得到执行的操作不仅决定于‘状态’类的具体状态,还决定于它访问的‘人’的类别。
对象结构类 由于总是需要‘男人’与‘女人’在不同状态的对比,所以我们需要一个‘对象结构’类来针对不同的‘状态’遍历‘男人’与‘女人’,得到不同的反应。

  1. //对象结构
  2. class ObjectStructure
  3. {
  4. private $elements = array();
  5. //增加
  6. public function Attach($element)
  7. {
  8. $this->elements[] = $element;
  9. }
  10. //移除
  11. public function Detach($element)
  12. {
  13. foreach ($this->elements as $key => $value) {
  14. if ($value == $element) {
  15. unset($this->elements[$key]);
  16. }
  17. }
  18. }
  19. //查看显示
  20. public function Display($visitor)
  21. {
  22. foreach ($this->elements as $value) {
  23. $value->Accept($visitor);
  24. }
  25. }
  26. }

客户端代码

  1. public function visitorImp()
  2. {
  3. $o = new ObjectStructure();
  4. //在对象结构中加入要对比的“男人”和“女人”
  5. $o->Attach(new Man());
  6. $o->Attach(new Woman());
  7. //成功时的反应
  8. $v1 = new Success();
  9. $o->Display($v1);
  10. //失败时的反应
  11. $v2 = new Failing();
  12. $o->Display($v2);
  13. //恋爱时的反应
  14. $v3 = new Amativeness();
  15. $o->Display($v3);
  16. }

由于用了双分派,当需要增加‘结婚’的状态来考查‘男人’和‘女人’的反应,只需要增加一个‘状态’子类,就可以在客户端调用来查看,不需要改动其他任何类的代码。
结婚状态类

  1. class Marriage extends Action
  2. {
  3. public function GetManConclusion($concreteElementA)
  4. {
  5. echo basename(str_replace('\\', '/', get_class($concreteElementA))) . ' ' .
  6. basename(str_replace('\\', '/', get_class($this))) . ' 时,感慨道:恋爱游戏终结时,’有妻徒刑‘遥无期' . PHP_EOL;
  7. }
  8. public function GetWomanConclusion($concreteElementB)
  9. {
  10. echo basename(str_replace('\\', '/', get_class($concreteElementB))) . ' ' .
  11. basename(str_replace('\\', '/', get_class($this))) . ' 时,欣慰曰:爱情长跑路漫漫,婚姻保险保平安' . PHP_EOL;
  12. }
  13. }

客户端代码

  1. .........
  2. $v4 = new Marriage();
  3. $o->Display($v4);
  4. .........

这完美体现了开放-封闭原则。

28.5访问者模式

访问者模式(Visitor),表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式(Visitor)结构图 第二十八章 男人和女人——访问者模式 - 图2访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
访问者模式的目的:
访问者模式的目的是要把处理从数据结构分离出来。很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。反之,如果这样的系统的数据结构对象易于变化,经常要有新的数据对象增加进来,就不适合使用访问者模式。
访问者模式的优点:
访问者模式的优点就是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
访问者模式的缺点:
访问者模式的缺点其实也就是使增加新的数据结构变得困难了。

28.6访问者模式基本代码

Visitor类,为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作。

  1. abstract class Visitor
  2. {
  3. abstract function VisitConcreteElementA($concreteElementA);
  4. abstract function VisitConcreteElementB($concreteElementB);
  5. }

ConcreteVisitor1 和 ConcreteVisitor2 类,具体访问者,实现每个由 Visitor 声明的操作。每个操作实现算法的一部分,而该算法片断乃是对应于结构中的对象的类。

  1. class ConcreteVisitor1 extends Visitor
  2. {
  3. public function VisitConcreteElementA($concreteElementA)
  4. {
  5. echo basename(str_replace('\\', '/', get_class($concreteElementA))) . ' 被 ' .
  6. basename(str_replace('\\', '/', get_class($this))) . ' 访问' . PHP_EOL;
  7. }
  8. public function VisitConcreteElementB($concreteElementB)
  9. {
  10. echo basename(str_replace('\\', '/', get_class($concreteElementB))) . ' 被 ' .
  11. basename(str_replace('\\', '/', get_class($this))) . ' 访问' . PHP_EOL;
  12. }
  13. }
  14. class ConcreteVisitor2 extends Visitor
  15. {
  16. //代码与上类类似,省略
  17. }

Element类,定义了一个Accept操作,它以一个访问者为参数。

  1. abstract class Element
  2. {
  3. abstract function Accept($visitor);
  4. }

ConcreteElementA 和 ConcreteElementB 类,具体元素,实现Accept操作。

  1. class ConcreteElementA extends Element
  2. {
  3. public function Accept($visitor)
  4. {
  5. //充分利用双分派技术,实现处理与数据结构的分离
  6. $visitor->VisitConcreteElementA($this);
  7. }
  8. //其他的相关方法
  9. public function OperationA()
  10. {
  11. }
  12. }
  13. class ConcreteElementB extends Element
  14. {
  15. public function Accept($visitor)
  16. {
  17. $visitor->VisitConcreteElementB($this);
  18. }
  19. public function OperationB()
  20. {
  21. }
  22. }

ObjectStructure类,能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。

  1. class ObjectStructure
  2. {
  3. private $elements = array();
  4. public function Attach($element)
  5. {
  6. $this->elements[] = $element;
  7. }
  8. public function Detach($element)
  9. {
  10. foreach ($this->elements as $key => $value) {
  11. if ($value === $element) {
  12. unset($this->elements[$key]);
  13. }
  14. }
  15. }
  16. public function Accept($visitor)
  17. {
  18. foreach ($this->elements as $value) {
  19. $value->Accept($visitor);
  20. }
  21. }
  22. }

客户端代码

  1. public function visitorDemo()
  2. {
  3. $o = new ObjectStructureDemo();
  4. $o->Attach(new ConcreteElementA());
  5. $o->Attach(new ConcreteElementB());
  6. $v1 = new ConcreteVisitor1();
  7. $v2 = new ConcreteVisitor2();
  8. $o->Accept($v1);
  9. $o->Accept($v2);
  10. }

28.7比上不足,比下有余

访问者模式的能力和复杂性是把双刃剑,只有当你真正需要它的时候,才考虑使用它。