PHP 23种设计模式介绍(三)---- 行为模式

策略模式

介绍

意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换,用户还不需要知道其具体的实现

主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。

何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:将这些算法封装成一个一个的类,任意地替换。

关键代码:实现同一个接口。

应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

/**

 * 策略模式

 */

interface Strategy { // 抽象策略角色,以接口实现

    public function do_method(); // 算法接口

}

class ConcreteStrategyA implements Strategy { // 具体策略角色A

    public function do_method() {

        echo 'do method A';

    }

}

class ConcreteStrategyB implements Strategy { // 具体策略角色B

    public function do_method() {

        echo 'do method B';

    }

}

class ConcreteStrategyC implements Strategy { // 具体策略角色C

    public function do_method() {

        echo 'do method C';

    }

}

class Question{ // 环境角色

    private $_strategy;

    public function __construct(Strategy $strategy) {

        $this->_strategy = $strategy;

    }

    public function handle_question() {

        $this->_strategy->do_method();

    }

}

// client

$strategyA new ConcreteStrategyA();

$question new Question($strategyA);

$question->handle_question();//输出do method A

$strategyB new ConcreteStrategyB();

$question new Question($strategyB);

$question->handle_question();//输出do method B

$strategyC new ConcreteStrategyC();

$question new Question($strategyC);

$question->handle_question();//输出do method C

模板模式

模板模式准备一个抽象类,将部分逻辑以具体方法以及具体构造形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现。

介绍

意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

何时使用:有一些通用的方法。

如何解决:将这些通用算法抽象出来。

关键代码:在抽象类实现,其他步骤在子类实现。

应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

abstract class AbstractClass { // 抽象模板角色

    public final function templateMethod() { // 模板方法 调用基本方法组装顶层逻辑

        $this->primitiveOperation1();

        $this->primitiveOperation2();

    }

    abstract protected function primitiveOperation1(); // 基本方法

    abstract protected function primitiveOperation2();

}

class ConcreteClass extends AbstractClass { // 具体模板角色

    protected function primitiveOperation1() {

        echo '具体实现1';

    }

    protected function primitiveOperation2(){

        echo '具体实现2';

    }

}

$class new ConcreteClass();

$class->templateMethod();

观察者模式

某个对象可以被设置为是可观察的,只要通过某种方式允许其他对象注册为观察者。每当被观察的对象改变时,会发送信息给观察者。

介绍

意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

interface IObserver{

    function onSendMsg( $sender$args );

    function getName();

}

interface IObservable{

    function addObserver( $observer );

}

class UserList implements IObservable{

    private $_observers array();

    public function sendMsg( $name ){

        foreach$this->_observers as $obs ){

            $obs->onSendMsg( $this$name );

        }

    }

    public function addObserver( $observer ){

        $this->_observers[]= $observer;

    }

    public function removeObserver($observer_name) {

        foreach($this->_observers as $index => $observer) {

            if ($observer->getName() === $observer_name) {

                array_splice($this->_observers, $index, 1);

                return;

            }

        }

    }

}

class UserListLogger implements IObserver{

    public function onSendMsg( $sender$args ){

        echo"'$args' send to UserListLogger\n" );

    }

    public function getName(){

        return 'UserListLogger';

    }

}

class OtherObserver implements IObserver{

    public function onSendMsg( $sender$args ){

        echo"'$args' send to OtherObserver\n" );

    }

    public function getName(){

        return 'OtherObserver';

    }

}

$ul new UserList();//被观察者

$ul->addObserver( new UserListLogger() );//增加观察者

$ul->addObserver( new OtherObserver() );//增加观察者

$ul->sendMsg( "Jack" );//发送消息到观察者

$ul->removeObserver('UserListLogger');//移除观察者

$ul->sendMsg("hello");//发送消息到观察者

/*    输出:'Jack' send to UserListLogger 'Jack' send to OtherObserver 'hello' send to OtherObserver */

迭代器模式

迭代器模式 (Iterator),又叫做游标(Cursor)模式。提供一种方法访问一个容器(Container)对象中各个元素,而又不需暴露该对象的内部细节。

当你需要访问一个聚合对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑使用迭代器模式。另外,当需要对聚集有多种方式遍历时,可以考虑去使用迭代器模式。迭代器模式为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。

php标准库(SPL)中提供了迭代器接口 Iterator,要实现迭代器模式,实现该接口即可。

介绍

意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

主要解决:不同的方式来遍历整个整合对象。

何时使用:遍历一个聚合对象。

如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。

关键代码:定义接口:hasNext, next。

优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。

注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

class sample implements Iterator {

    private $_items ;

    public function __construct(&$data) {

        $this->_items = $data;

    }

    public function current() {

        return current($this->_items);

    }

    public function next() {

        next($this->_items);  

    }

    public function key() {

        return key($this->_items);

    }

    public function rewind() {

        reset($this->_items);

    }

    public function valid() {                                                                             

        return ($this->current() !== FALSE);

    }

}

// client

$data array(1, 2, 3, 4, 5);

$sa new sample($data);

foreach ($sa AS $key => $row) {

    echo $key' '$row'<br />';

}

/* 输出:

0 1

1 2

2 3

3 4

4 5 */

//Yii FrameWork Demo

class CMapIterator implements Iterator {

/**

* @var array the data to be iterated through

*/

    private $_d;

/**

* @var array list of keys in the map

*/

    private $_keys;

/**

* @var mixed current key

*/

    private $_key;

/**

* Constructor.

* @param array the data to be iterated through

*/

    public function __construct(&$data) {

        $this->_d=&$data;

        $this->_keys=array_keys($data);

    }

/**

* Rewinds internal array pointer.

* This method is required by the interface Iterator.

*/

    public function rewind() {                                                                                

        $this->_key=reset($this->_keys);

    }

/**

* Returns the key of the current array element.

* This method is required by the interface Iterator.

* @return mixed the key of the current array element

*/

    public function key() {

        return $this->_key;

    }

/**

* Returns the current array element.

* This method is required by the interface Iterator.

* @return mixed the current array element

*/

    public function current() {

        return $this->_d[$this->_key];

    }

/**

* Moves the internal pointer to the next array element.

* This method is required by the interface Iterator.

*/

    public function next() {

        $this->_key=next($this->_keys);

    }

/**

* Returns whether there is an element at current position.

* This method is required by the interface Iterator.

* @return boolean

*/

    public function valid() {

        return $this->_key!==false;

    }

}

$data array('s1' => 11, 's2' => 22, 's3' => 33);

$it new CMapIterator($data);

foreach ($it as $row) {

    echo $row'<br />';

}

/* 输出:

11

22

33 */

责任链模式

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

介绍

意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

 

实例

短信内容过滤的功能。大家都知道,我们对广告有着严格的规定,许多词都在广告法中被标记为禁止使用的词汇,更有些严重的词汇可能会引来不必要的麻烦。这时候,我们就需要一套过滤机制来进行词汇的过滤。针对不同类型的词汇,我们可以通过责任链来进行过滤,比如严重违法的词汇当然是这条信息都不能通过。一些比较严重但可以绕过的词,我们可以进行替换或者加星处理,这样,客户端不需要一大堆的if..else..来进行逻辑判断,使用责任链让他们一步步的进行审批就好啦!!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

// 词汇过滤链条

abstract class FilterChain

{

    protected $next;

    public function setNext($next)

    {

        $this->next = $next;

    }

    abstract public function filter($message);

}

// 严禁词汇

class FilterStrict extends FilterChain

{

    public function filter($message)

    {

        foreach (['枪X''弹X''毒X'as $v) {

            if (strpos($message$v) !== false) {

                throw new \Exception('该信息包含敏感词汇!');

            }

        }

        if ($this->next) {

            return $this->next->filter($message);

        else {

            return $message;

        }

    }

}

// 警告词汇

class FilterWarning extends FilterChain

{

    public function filter($message)

    {

        $message str_replace(['打架''丰胸''偷税'], '*'$message);

        if ($this->next) {

            return $this->next->filter($message);

        else {

            return $message;

        }

    }

}

// 手机号加星

class FilterMobile extends FilterChain

{

    public function filter($message)

    {

        $message = preg_replace("/(1[3|5|7|8]\d)\d{4}(\d{4})/i""$1****$2"$message);

        if ($this->next) {

            return $this->next->filter($message);

        else {

            return $message;

        }

    }

}

$f1 new FilterStrict();

$f2 new FilterWarning();

$f3 new FilterMobile();

$f1->setNext($f2);

$f2->setNext($f3);

$m1 "现在开始测试链条1:语句中不包含敏感词,需要替换掉打架这种词,然后给手机号加上星:13333333333,这样的数据才可以对外展示哦";

echo $f1->filter($m1);

echo PHP_EOL;

$m2 "现在开始测试链条2:这条语句走不到后面,因为包含了毒X,直接就报错了!!!语句中不包含敏感词,需要替换掉打架这种词,然后给手机号加上星:13333333333,这样的数据才可以对外展示哦";

echo $f1->filter($m2);

echo PHP_EOL;

命令模式

将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化。对请求排队或记录请求日志,以及支持撤销的操作。

命令模式以松散耦合主题为基础,发送消息、命令和请求,或通过一组处理程序发送任意内容。每个处理程序都会自行判断自己能否处理请求。如果可以,该请求被处理,进程停止。您可以为系统添加或移除处理程序,而不影响其他处理程序。

命令模式的四种角色:

1. 接收者(Receiver)负责执行与请求相关的操作

2. 命令接口(Command)封装execute()、undo()等方法

3. 具体命令(ConcreteCommand)实现命令接口中的方法

4. 请求者(Invoker)包含Command接口变量

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

interface ICommand {

    function onCommand($name$args);

}

class CommandChain {

    private $_commands array();

    public function addCommand($cmd) {

        $this->_commands []= $cmd;

    }

    public function runCommand($name$args) {

        foreach($this->_commands as $cmd) {

            if ($cmd->onCommand($name$args)) return;

        }

    }

}

class UserCommand implements ICommand {

    public function onCommand($name$args) {

        if ($name != 'addUser'return false;

        echo("UserCommand handling 'addUser'\n");

        return true;

    }

}

class MailCommand implements ICommand {

    public function onCommand($name$args) {

        if ($name != 'mail'return false;

        echo("MailCommand handling 'mail'\n");

        return true;

    }

}

$cc new CommandChain();

$cc->addCommand(new UserCommand());

$cc->addCommand(new MailCommand());

$cc->runCommand('addUser', null);

$cc->runCommand('mail', null);

备忘录模式

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

介绍

意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

主要解决:所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

何时使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。

如何解决:通过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。

优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。

注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

class Originator { // 发起人(Originator)角色

    private $_state;

    public function __construct() {

        $this->_state = '';

    }

    public function createMemento() { // 创建备忘录

        return new Memento($this->_state);

    }

    public function restoreMemento(Memento $memento) { // 将发起人恢复到备忘录对象记录的状态上

        $this->_state = $memento->getState();

    }

    public function setState($state) { $this->_state = $state; }

    public function getState() { return $this->_state; }

    public function showState() {

        echo $this->_state;echo "<br>";

    }

}

class Memento { // 备忘录(Memento)角色

    private $_state;

    public function __construct($state) {

        $this->setState($state);

    }

    public function getState() { return $this->_state; }

    public function setState($state) { $this->_state = $state;}

}

class Caretaker { // 负责人(Caretaker)角色

    private $_memento;

    public function getMemento() { return $this->_memento; }

    public function setMemento(Memento $memento) { $this->_memento = $memento; }

}

// client

/* 创建目标对象 */

$org new Originator();

$org->setState('open');

$org->showState();

/* 创建备忘 */

$memento $org->createMemento();

/* 通过Caretaker保存此备忘 */

$caretaker new Caretaker();

$caretaker->setMemento($memento);

/* 改变目标对象的状态 */

$org->setState('close');

$org->showState();

$org->restoreMemento($memento);

$org->showState();

/* 改变目标对象的状态 */

$org->setState('close');

$org->showState();

/* 还原操作 */

$org->restoreMemento($caretaker->getMemento());

$org->showState();

/* 输出:

open

close

open

close

open */

状态模式

状态模式当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

角色:
上下文环境(Work):它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的具体对象来处理。
抽象状态(State):定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。
具体状态(AmState):实现抽象状态定义的接口。

优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

interface State { // 抽象状态角色

    public function handle(Context $context); // 方法示例

}

class ConcreteStateA implements State { // 具体状态角色A

    private static $_instance = null;

    private function __construct() {}

    public static function getInstance() { // 静态工厂方法,返还此类的唯一实例

        if (is_null(self::$_instance)) {

            self::$_instance new ConcreteStateA();

        }

        return self::$_instance;

    }

    public function handle(Context $context) {

        echo 'concrete_a'."<br>";

        $context->setState(ConcreteStateB::getInstance());

    }

}

class ConcreteStateB implements State { // 具体状态角色B

    private static $_instance = null;

    private function __construct() {}

    public static function getInstance() {

        if (is_null(self::$_instance)) {

            self::$_instance new ConcreteStateB();

        }

        return self::$_instance;

    }

    public function handle(Context $context) {

        echo 'concrete_b'."<br>";

        $context->setState(ConcreteStateA::getInstance());

    }

}

class Context { // 环境角色

    private $_state;

    public function __construct() { // 默认为stateA

        $this->_state = ConcreteStateA::getInstance();

    }

    public function setState(State $state) {

        $this->_state = $state;

    }

    public function request() {

        $this->_state->handle($this);

    }

}

// client

$context new Context();

$context->request();

$context->request();

$context->request();

$context->request();

/* 输出:

concrete_a

concrete_b

concrete_a

concrete_b */

php 23种设计模型 - 访问者模式

访问者模式是一种行为型模式,访问者表示一个作用于某对象结构中各元素的操作。它可以在不修改各元素类的前提下定义作用于这些元素的新操作,即动态的增加具体访问者角色。

访问者模式利用了双重分派。先将访问者传入元素对象的Accept方法中,然后元素对象再将自己传入访问者,之后访问者执行元素的相应方法。

主要角色

抽象访问者角色(Visitor):为该对象结构(ObjectStructure)中的每一个具体元素提供一个访问操作接口。该操作接口的名字和参数标识了 要访问的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
具体访问者角色(ConcreteVisitor):实现抽象访问者角色接口中针对各个具体元素角色声明的操作。
抽象节点(Node)角色:该接口定义一个accept操作接受具体的访问者。
具体节点(Node)角色:实现抽象节点角色中的accept操作。
对象结构角色(ObjectStructure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合(在PHP中我们使用数组代替,因为PHP中的数组本来就是一个可以放置任何类型数据的集合)

优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。

缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

使用场景: 1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

interface ServiceVisitor

{

    public function SendMsg(SendMessage $s);

    function PushMsg(PushMessage $p);

}

class AliYun implements ServiceVisitor

{

    public function SendMsg(SendMessage $s)

    {

        echo '阿里云发送短信!', PHP_EOL;

    }

    public function PushMsg(PushMessage $p)

    {

        echo '阿里云推送信息!', PHP_EOL;

    }

}

class JiGuang implements ServiceVisitor

{

    public function SendMsg(SendMessage $s)

    {

        echo '极光发送短信!', PHP_EOL;

    }

    public function PushMsg(PushMessage $p)

    {

        echo '极光推送短信!', PHP_EOL;

    }

}

interface Message

{

    public function Msg(ServiceVisitor $v);

}

class PushMessage implements Message

{

    public function Msg(ServiceVisitor $v)

    {

        echo '推送脚本启动:';

        $v->PushMsg($this);

    }

}

class SendMessage implements Message

{

    public function Msg(ServiceVisitor $v)

    {

        echo '短信脚本启动:';

        $v->SendMsg($this);

    }

}

class ObjectStructure

{

    private $elements = [];

    public function Attach(Message $element)

    {

        $this->elements[] = $element;

    }

    public function Detach(Message $element)

    {

        $position = 0;

        foreach ($this->elements as $e) {

            if ($e == $element) {

                unset($this->elements[$position]);

                break;

            }

            $position++;

        }

    }

    public function Accept(ServiceVisitor $visitor)

    {

        foreach ($this->elements as $e) {

            $e->Msg($visitor);

        }

    }

}

$o new ObjectStructure();

$o->Attach(new PushMessage());

$o->Attach(new SendMessage());

$v1 new AliYun();

$v2 new JiGuang();

$o->Accept($v1);

$o->Accept($v2);

中介者模式

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

主要角色

中介者模式用于开发一个对象,这个对象能够在类似对象相互之间不直接相互的情况下传送或者调解对这些对象的集合的修改。 一般处理具有类似属性,需要保持同步的非耦合对象时,最佳的做法就是中介者模式。PHP中不是特别常用的设计模式。

应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。

缺点:中介者会庞大,变得复杂难以维护。

使用场景: 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

abstract class Mediator

{

    abstract public function Send(String $message, Colleague $colleague);

}

class ConcreteMediator extends Mediator

{

    public $colleague1;

    public $colleague2;

    public function Send(String $message, Colleague $colleague)

    {

        if ($colleague == $this->colleague1) {

            $this->colleague2->Notify($message);

        else {

            $this->colleague1->Notify($message);

        }

    }

}

abstract class Colleague

{

    protected $mediator;

    public function __construct(Mediator $mediator)

    {

        $this->mediator = $mediator;

    }

}

class ConcreteColleague1 extends Colleague

{

    public function Send(String $message)

    {

        $this->mediator->Send($message$this);

    }

    public function Notify(String $message)

    {

        echo "同事1得到信息:" $message, PHP_EOL;

    }

}

class ConcreteColleague2 extends Colleague

{

    public function Send(String $message)

    {

        $this->mediator->Send($message$this);

    }

    public function Notify(String $message)

    {

        echo "同事2得到信息:" $message;

    }

}

$m new ConcreteMediator();

$c1 new ConcreteColleague1($m);

$c2 new ConcreteColleague2($m);

$m->colleague1 = $c1;

$m->colleague2 = $c2;

$c1->Send("吃过饭了吗?");

$c2->Send("没有呢,你打算请客?");

给定一个语言, 定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
角色:
环境角色(PlayContent):定义解释规则的全局信息。
抽象解释器(Empress):定义了部分解释具体实现,封装了一些由具体解释器实现的接口。
具体解释器(MusicNote):实现抽象解释器的接口,进行具体的解释执行。

应用实例:编译器、运算表达式计算。

优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。

缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。

使用场景: 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 2、一些重复出现的问题可以用一种简单的语言来进行表达。 3、一个简单语法需要解释的场景。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

class Expression { //抽象表示

    function interpreter($str) {

        return $str;

    }

}

class ExpressionNum extends Expression { //表示数字

    function interpreter($str) {

        switch($str) {

            case "0"return "零";

            case "1"return "一";

            case "2"return "二";

            case "3"return "三";

            case "4"return "四";

            case "5"return "五";

            case "6"return "六";

            case "7"return "七";

            case "8"return "八";

            case "9"return "九";

        }

    }

}

class ExpressionCharater extends Expression { //表示字符

    function interpreter($str) {

        return strtoupper($str);

    }

}

class Interpreter { //解释器

    function execute($string) {

        $expression = null;

        for($i = 0;$i<strlen($string);$i++) {

            $temp $string[$i];

            switch(true) {

                case is_numeric($temp): $expression new ExpressionNum(); break;

                default$expression new ExpressionCharater();

            }

            echo $expression->interpreter($temp);

            echo "<br>";

        }

    }

}

//client

$obj new Interpreter();

$obj->execute("123s45abc");

/* 输出:

S

A

B

C */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值