行为型模式(二)

类的状态:备忘录模式、状态模式

通过中间类:访问者模式、中介者模式、解释器模式

备忘录模式(Memento)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态

经常需要保存对象的中间状态,当需要的时候,可以恢复到这个状态。类似编辑器的ctrl+z回退操作,ctrl+y恢复操作。

备忘录模式的结构

  • 发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。

  • 备忘录:负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。

  • 管理角色:对备忘录进行管理,保存和提供备忘录。

代码示例

  1. class Originator {
  2. private $state = "";
  3. public function getState() {
  4. return $this->state;
  5. }
  6. public function setState( $state ) {
  7. $this->state = $state;
  8. }
  9. public function createMemento(){
  10. return new Memento( $this->state );
  11. }
  12. public function restoreMemento( $memento ) {
  13. $this->setState( $memento->getState() );
  14. }
  15. }
  16. class Memento {
  17. private $state = "";
  18. public function Memento( $state ) {
  19. $this->state = $state;
  20. }
  21. public function getState() {
  22. return $this->state;
  23. }
  24. public function setState( $state ) {
  25. $this->state = $state;
  26. }
  27. }
  28. class Caretaker {
  29. private $memento;
  30. public function getMemento() {
  31. return $this->memento;
  32. }
  33. public function setMemento( $memento ) {
  34. $this->memento = $memento;
  35. }
  36. }
  37. class Client {
  38. public static function main() {
  39. $originator = new Originator();
  40. $originator->setState( "状态1" );
  41. echo( "初始状态:".$originator->getState()."<br>" );
  42. $caretaker = new Caretaker();
  43. $caretaker->setMemento( $originator->createMemento() );
  44. $originator->setState( "状态2" );
  45. echo( "改变后状态:".$originator->getState()."<br>" );
  46. $originator->restoreMemento( $caretaker->getMemento() );
  47. echo( "恢复后状态:".$originator->getState()."<br>" );
  48. }
  49. }
  50. Client::main();

单状态单备份,Originator类中的state变量需要备份,以便在需要的时候恢复;Memento类中,也有一个state变量,用来存储Originator类中state变量的临时状态;而Caretaker类就是用来管理备忘录类的,用来向备忘录对象中写入状态或者取回状态。

备忘录模式的优点有:

  • 当发起人角色中的状态改变时,有可能这是个错误的改变,我们使用备忘录模式就可以把这个错误的改变还原。

  • 备份的状态是保存在发起人角色之外的,这样,发起人角色就不需要对各个备份的状态进行管理。

备忘录模式的缺点:

在实际应用中,备忘录模式都是多状态和多备份的,发起人角色的状态需要存储到备忘录对象中,对资源的消耗是比较严重的。

状态模式(State)

当对象的状态改变时,同时改变其行为

状态模式结构

  • State类是个状态类

  • Context类可以实现切换

代码示例

  1. class State {
  2. private $value;
  3. public function getValue() {
  4. return $this->value;
  5. }
  6. public function setValue( $value ) {
  7. $this->value = $value;
  8. }
  9. public function method1(){
  10. echo("execute the first opt!"."<br>");
  11. }
  12. public function method2(){
  13. echo("execute the second opt!"."<br>");
  14. }
  15. }
  16. class Context {
  17. private $state;
  18. public function Context( $state ) {
  19. $this->state = $state;
  20. }
  21. public function getState() {
  22. return $this->state;
  23. }
  24. public function setState( $state ) {
  25. $this->state = $state;
  26. }
  27. public function method() {
  28. if ( $this->state->getValue() == "state1" ) {
  29. $this->state->method1();
  30. } else if ( $this->state->getValue() == "state2" ) {
  31. $this->state->method2();
  32. }
  33. }
  34. }
  35. class Client {
  36. public static function main() {
  37. $state = new State();
  38. $context = new Context( $state );
  39. //设置第一种状态
  40. $state->setValue( "state1" );
  41. $context->method();
  42. //设置第二种状态
  43. $state->setValue( "state2" );
  44. $context->method();
  45. }
  46. }
  47. Client::main();

访问者模式(Visitor)

封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

  1. class A {
  2. public function method1() {
  3. echo("我是A");
  4. }
  5. public function method2( $b ) {
  6. $b->showA( $this );
  7. }
  8. }
  9. class B {
  10. public function showA( $a ) {
  11. $a->method1();
  12. }
  13. }
  14. class Client {
  15. public static function main() {
  16. $a = new A();
  17. $a->method1();
  18. $a->method2( new B() );
  19. }
  20. }
  21. Client::main();

对于类A来说,类B就是一个访问者。这个例子可扩展性比较差,并不是访问者模式的全部。

访问者模式的结构

  • 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素

  • 访问者:实现抽象访问者所声明的方法

  • 抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。

  • 元素类:实现抽象元素类所声明的accept方法

  • 结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器

代码示例

  1. //抽象访问者
  2. abstract class Element {
  3. public abstract function accept( $visitor );
  4. public abstract function doSomething();
  5. }
  6. //抽象元素类
  7. interface IVisitor {
  8. public function visit1( $el1 );
  9. public function visit2( $el2 );
  10. }
  11. //访问者
  12. class ConcreteElement1 extends Element {
  13. public function doSomething() {
  14. echo("这是元素1"."<br>");
  15. }
  16. public function accept( $visitor ) {
  17. $visitor->visit1( $this );
  18. }
  19. }
  20. class ConcreteElement2 extends Element {
  21. public function doSomething() {
  22. echo("这是元素2"."<br>");
  23. }
  24. public function accept( $visitor ) {
  25. $visitor->visit2( $this );
  26. }
  27. }
  28. //元素类
  29. class Visitor implements IVisitor {
  30. public function visit1( $el1 ) {
  31. $el1->doSomething();
  32. }
  33. public function visit2( $el2 ) {
  34. $el2->doSomething();
  35. }
  36. }
  37. //结构对象
  38. class ObjectStruture {
  39. public static function getList() {
  40. $list = array();
  41. list($msec, $sec) = explode( ' ', microtime() );
  42. srand((float) $sec);
  43. for( $i = 0; $i < 10; $i++ ) {
  44. $a = rand( 10,100 );
  45. if( $a > 50 ) {
  46. array_push($list, new ConcreteElement1());
  47. } else {
  48. array_push($list, new ConcreteElement2());
  49. }
  50. }
  51. return $list;
  52. }
  53. }
  54. class Client {
  55. public static function main(){
  56. $lists = ObjectStruture::getList();
  57. foreach ( $lists as $key => $list ) {
  58. $list->accept( new Visitor() );
  59. }
  60. }
  61. }
  62. Client::main();

访问者模式的优点

  • 符合单一职责原则

  • 扩展性好

访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。

中介者模式(Mediator)

用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。

中介者模式又称为调停者模式,减少系统耦合,任何一个类的变动,只会影响的类本身,以及中介者。

  • 抽象中介者:定义好同事类对象到中介者对象的接口,用于各个同事类之间的通信。

  • 中介者实现类:从抽象中介者继承而来,实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同时类。

  • 同事类:如果一个对象会影响其他的对象,同时也会被其他对象影响,那么这两个对象称为同事类。同事类一般由多个组成,他们之间相互影响,相互依赖。同事类越多,关系越复杂。在中介者模式中,同事类之间必须通过中介者才能进行消息传递。

注:修改自java代码,PHP不支持重载实现多态,将setNumber改为changeNumber

  1. abstract class AbstractColleague {
  2. protected $number;
  3. public function getNumber() {
  4. return $this->number;
  5. }
  6. public function setNumber( $number ) {
  7. $this->number = $number;
  8. }
  9. //注意这里的参数不再是同事类,而是一个中介者
  10. public abstract function changeNumber($number, $abstractMediator);
  11. }
  12. class ColleagueA extends AbstractColleague{
  13. public function changeNumber( $number, $abstractMediator ) {
  14. $this->number = $number;
  15. $abstractMediator->AaffectB();
  16. }
  17. }
  18. class ColleagueB extends AbstractColleague{
  19. public function changeNumber( $number, $abstractMediator ) {
  20. $this->number = $number;
  21. $abstractMediator->BaffectA();
  22. }
  23. }
  24. abstract class AbstractMediator {
  25. protected $A;
  26. protected $B;
  27. public function AbstractMediator( $a, $b ) {
  28. $this->A = $a;
  29. $this->B = $b;
  30. }
  31. public abstract function AaffectB();
  32. public abstract function BaffectA();
  33. }
  34. class Mediator extends AbstractMediator {
  35. //处理A对B的影响
  36. public function AaffectB() {
  37. $number = $this->A->getNumber();
  38. $this->B->setNumber( $number * 100 );
  39. }
  40. //处理B对A的影响
  41. public function BaffectA() {
  42. $number = $this->B->getNumber();
  43. $this->A->setNumber( $number / 100 );
  44. }
  45. }
  46. class Client {
  47. public static function main() {
  48. $collA = new ColleagueA();
  49. $collB = new ColleagueB();
  50. $am = new Mediator( $collA, $collB );
  51. echo("==========通过设置A影响B=========="."<br>");
  52. $collA->changeNumber( 1000, $am );
  53. echo("collA的number值为:".$collA->getNumber()."<br>");
  54. echo("collB的number值为A的100倍:".$collB->getNumber()."<br>");
  55. echo("==========通过设置B影响A=========="."<br>");
  56. $collB->changeNumber( 1000, $am );
  57. echo("collB的number值为:".$collB->getNumber()."<br>");
  58. echo("collA的number值为B的0.01倍:".$collA->getNumber()."<br>");
  59. }
  60. }
  61. Client::main();

处理对象关系的代码重新封装到一个中介类中,通过这个中介类来处理对象间的关系。

中介者模式的优点

  • 适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。

  • 使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。

  • 使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。

解释器模式(Interpreter)

给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。

解释器模式的结构:

  • 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作。具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成。

  • 终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一半是文法中的运算单元,比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。

  • 非终结符表达式:文法中的每条规则对应于一个非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。

  • 环境角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。

代码示例

  1. interface Expression {
  2. public function interpret( $context );
  3. }
  4. class Plus implements Expression {
  5. public function interpret( $context ) {
  6. return $context->getNum1() + $context->getNum2();
  7. }
  8. }
  9. class Minus implements Expression {
  10. public function interpret( $context ) {
  11. return $context->getNum1() - $context->getNum2();
  12. }
  13. }
  14. class Context {
  15. private $num1;
  16. private $num2;
  17. public function Context( $num1, $num2 ) {
  18. $this->num1 = $num1;
  19. $this->num2 = $num2;
  20. }
  21. public function getNum1() {
  22. return $this->num1;
  23. }
  24. public function setNum1( $num1 ) {
  25. $this->num1 = $num1;
  26. }
  27. public function getNum2() {
  28. return $this->num2;
  29. }
  30. public function setNum2( $num2 ) {
  31. $this->num2 = $num2;
  32. }
  33. }
  34. class Client {
  35. public static function main() {
  36. // 计算9+2-8的值
  37. $plus = new Plus();
  38. $minus = new Minus();
  39. $context = new Context( 9, 2 );
  40. $result = $plus->interpret( $context );
  41. $context = new Context( $result, 8 );
  42. $result = $minus->interpret( $context );
  43. echo($result);
  44. }
  45. }
  46. Client::main();

解释器是一个简单的语法分析工具。

公式千变万化,但是都是由加减乘除四个非终结符来连接的,可以使用解释器模式。

设计模式5 行为型模式2