类的状态:备忘录模式、状态模式
通过中间类:访问者模式、中介者模式、解释器模式
备忘录模式(Memento)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态
经常需要保存对象的中间状态,当需要的时候,可以恢复到这个状态。类似编辑器的ctrl+z回退操作,ctrl+y恢复操作。
备忘录模式的结构
-
发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
-
备忘录:负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
-
管理角色:对备忘录进行管理,保存和提供备忘录。
代码示例
class Originator {
private $state = "";
public function getState() {
return $this->state;
}
public function setState( $state ) {
$this->state = $state;
}
public function createMemento(){
return new Memento( $this->state );
}
public function restoreMemento( $memento ) {
$this->setState( $memento->getState() );
}
}
class Memento {
private $state = "";
public function Memento( $state ) {
$this->state = $state;
}
public function getState() {
return $this->state;
}
public function setState( $state ) {
$this->state = $state;
}
}
class Caretaker {
private $memento;
public function getMemento() {
return $this->memento;
}
public function setMemento( $memento ) {
$this->memento = $memento;
}
}
class Client {
public static function main() {
$originator = new Originator();
$originator->setState( "状态1" );
echo( "初始状态:".$originator->getState()."<br>" );
$caretaker = new Caretaker();
$caretaker->setMemento( $originator->createMemento() );
$originator->setState( "状态2" );
echo( "改变后状态:".$originator->getState()."<br>" );
$originator->restoreMemento( $caretaker->getMemento() );
echo( "恢复后状态:".$originator->getState()."<br>" );
}
}
Client::main();
单状态单备份,Originator类中的state变量需要备份,以便在需要的时候恢复;Memento类中,也有一个state变量,用来存储Originator类中state变量的临时状态;而Caretaker类就是用来管理备忘录类的,用来向备忘录对象中写入状态或者取回状态。
备忘录模式的优点有:
-
当发起人角色中的状态改变时,有可能这是个错误的改变,我们使用备忘录模式就可以把这个错误的改变还原。
-
备份的状态是保存在发起人角色之外的,这样,发起人角色就不需要对各个备份的状态进行管理。
备忘录模式的缺点:
在实际应用中,备忘录模式都是多状态和多备份的,发起人角色的状态需要存储到备忘录对象中,对资源的消耗是比较严重的。
状态模式(State)
当对象的状态改变时,同时改变其行为
状态模式结构
-
State类是个状态类
-
Context类可以实现切换
代码示例
class State {
private $value;
public function getValue() {
return $this->value;
}
public function setValue( $value ) {
$this->value = $value;
}
public function method1(){
echo("execute the first opt!"."<br>");
}
public function method2(){
echo("execute the second opt!"."<br>");
}
}
class Context {
private $state;
public function Context( $state ) {
$this->state = $state;
}
public function getState() {
return $this->state;
}
public function setState( $state ) {
$this->state = $state;
}
public function method() {
if ( $this->state->getValue() == "state1" ) {
$this->state->method1();
} else if ( $this->state->getValue() == "state2" ) {
$this->state->method2();
}
}
}
class Client {
public static function main() {
$state = new State();
$context = new Context( $state );
//设置第一种状态
$state->setValue( "state1" );
$context->method();
//设置第二种状态
$state->setValue( "state2" );
$context->method();
}
}
Client::main();
访问者模式(Visitor)
封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
class A {
public function method1() {
echo("我是A");
}
public function method2( $b ) {
$b->showA( $this );
}
}
class B {
public function showA( $a ) {
$a->method1();
}
}
class Client {
public static function main() {
$a = new A();
$a->method1();
$a->method2( new B() );
}
}
Client::main();
对于类A来说,类B就是一个访问者。这个例子可扩展性比较差,并不是访问者模式的全部。
访问者模式的结构
-
抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素
-
访问者:实现抽象访问者所声明的方法
-
抽象元素类:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
-
元素类:实现抽象元素类所声明的accept方法
-
结构对象:一个元素的容器,一般包含一个容纳多个不同类、不同接口的容器
代码示例
//抽象访问者
abstract class Element {
public abstract function accept( $visitor );
public abstract function doSomething();
}
//抽象元素类
interface IVisitor {
public function visit1( $el1 );
public function visit2( $el2 );
}
//访问者
class ConcreteElement1 extends Element {
public function doSomething() {
echo("这是元素1"."<br>");
}
public function accept( $visitor ) {
$visitor->visit1( $this );
}
}
class ConcreteElement2 extends Element {
public function doSomething() {
echo("这是元素2"."<br>");
}
public function accept( $visitor ) {
$visitor->visit2( $this );
}
}
//元素类
class Visitor implements IVisitor {
public function visit1( $el1 ) {
$el1->doSomething();
}
public function visit2( $el2 ) {
$el2->doSomething();
}
}
//结构对象
class ObjectStruture {
public static function getList() {
$list = array();
list($msec, $sec) = explode( ' ', microtime() );
srand((float) $sec);
for( $i = 0; $i < 10; $i++ ) {
$a = rand( 10,100 );
if( $a > 50 ) {
array_push($list, new ConcreteElement1());
} else {
array_push($list, new ConcreteElement2());
}
}
return $list;
}
}
class Client {
public static function main(){
$lists = ObjectStruture::getList();
foreach ( $lists as $key => $list ) {
$list->accept( new Visitor() );
}
}
}
Client::main();
访问者模式的优点
-
符合单一职责原则
-
扩展性好
访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。
中介者模式(Mediator)
用一个中介者对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。
中介者模式又称为调停者模式,减少系统耦合,任何一个类的变动,只会影响的类本身,以及中介者。
-
抽象中介者:定义好同事类对象到中介者对象的接口,用于各个同事类之间的通信。
-
中介者实现类:从抽象中介者继承而来,实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同时类。
-
同事类:如果一个对象会影响其他的对象,同时也会被其他对象影响,那么这两个对象称为同事类。同事类一般由多个组成,他们之间相互影响,相互依赖。同事类越多,关系越复杂。在中介者模式中,同事类之间必须通过中介者才能进行消息传递。
注:修改自java代码,PHP不支持重载实现多态,将setNumber改为changeNumber
abstract class AbstractColleague {
protected $number;
public function getNumber() {
return $this->number;
}
public function setNumber( $number ) {
$this->number = $number;
}
//注意这里的参数不再是同事类,而是一个中介者
public abstract function changeNumber($number, $abstractMediator);
}
class ColleagueA extends AbstractColleague{
public function changeNumber( $number, $abstractMediator ) {
$this->number = $number;
$abstractMediator->AaffectB();
}
}
class ColleagueB extends AbstractColleague{
public function changeNumber( $number, $abstractMediator ) {
$this->number = $number;
$abstractMediator->BaffectA();
}
}
abstract class AbstractMediator {
protected $A;
protected $B;
public function AbstractMediator( $a, $b ) {
$this->A = $a;
$this->B = $b;
}
public abstract function AaffectB();
public abstract function BaffectA();
}
class Mediator extends AbstractMediator {
//处理A对B的影响
public function AaffectB() {
$number = $this->A->getNumber();
$this->B->setNumber( $number * 100 );
}
//处理B对A的影响
public function BaffectA() {
$number = $this->B->getNumber();
$this->A->setNumber( $number / 100 );
}
}
class Client {
public static function main() {
$collA = new ColleagueA();
$collB = new ColleagueB();
$am = new Mediator( $collA, $collB );
echo("==========通过设置A影响B=========="."<br>");
$collA->changeNumber( 1000, $am );
echo("collA的number值为:".$collA->getNumber()."<br>");
echo("collB的number值为A的100倍:".$collB->getNumber()."<br>");
echo("==========通过设置B影响A=========="."<br>");
$collB->changeNumber( 1000, $am );
echo("collB的number值为:".$collB->getNumber()."<br>");
echo("collA的number值为B的0.01倍:".$collA->getNumber()."<br>");
}
}
Client::main();
处理对象关系的代码重新封装到一个中介类中,通过这个中介类来处理对象间的关系。
中介者模式的优点
-
适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。
-
使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。
-
使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。
解释器模式(Interpreter)
给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。
解释器模式的结构:
-
抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作。具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成。
-
终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。终结符一半是文法中的运算单元,比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
-
非终结符表达式:文法中的每条规则对应于一个非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
-
环境角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。
代码示例
interface Expression {
public function interpret( $context );
}
class Plus implements Expression {
public function interpret( $context ) {
return $context->getNum1() + $context->getNum2();
}
}
class Minus implements Expression {
public function interpret( $context ) {
return $context->getNum1() - $context->getNum2();
}
}
class Context {
private $num1;
private $num2;
public function Context( $num1, $num2 ) {
$this->num1 = $num1;
$this->num2 = $num2;
}
public function getNum1() {
return $this->num1;
}
public function setNum1( $num1 ) {
$this->num1 = $num1;
}
public function getNum2() {
return $this->num2;
}
public function setNum2( $num2 ) {
$this->num2 = $num2;
}
}
class Client {
public static function main() {
// 计算9+2-8的值
$plus = new Plus();
$minus = new Minus();
$context = new Context( 9, 2 );
$result = $plus->interpret( $context );
$context = new Context( $result, 8 );
$result = $minus->interpret( $context );
echo($result);
}
}
Client::main();
解释器是一个简单的语法分析工具。
公式千变万化,但是都是由加减乘除四个非终结符来连接的,可以使用解释器模式。