[关闭]
@qidiandasheng 2021-01-12T09:04:39.000000Z 字数 12851 阅读 601

设计模式(六):行为型模式之状态、命令、中介者(😁)

架构


三种行为型模式

介绍

行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此可在进行这些交流活动时增强弹性。

命令模式和策略模式的区别

在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。比如BubbleSortSelectionSort都是为了实现排序的,只不过一个是用冒泡排序算法来实现的,另一个是用选择排序算法来实现的。而在命令模式中,不同的命令具有不同的目的,对 应不同的处理逻辑,并且互相之间不可替换。

中介者模式跟观察者模式的区别

观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。

❤状态模式

定义

在状态模式(State Pattern):允许一个对象在其内部状态改变时,改变它的行为。

状态模式是状态机的一种实现方式。状态机又叫有限状态机,它有3个部分组成:状态、事件、动作。其中事件也称为转移条件。事件触发状态的转移及动作的执行。不过动作不是必须的,也可能只转移状态,不执行任何动作。

以下为三种状态机的实现方式:

适用场景

不是所有状态相关的业务场景都可以使用状态模式的,比如如下的业务场景:

游戏这种比较复杂的状态机,包含的状态比较多,优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现。

成员与类图

成员

状态模式一共只有四个成员:

模式类图

代码示例

场景概述

模拟一个程序员一天的生活,他有四个状态:

1.醒着
2.睡觉中
3.写代码中
4.吃饭中

这个程序员有四个状态,但是有些状态之间是无法切换的:比如从睡觉是无法切换到写代码的(因为需要切换到醒着,然后才能到写代码);从吃饭中是无法切换到醒着的,因为已经醒着了。

如果我们不使用状态模式,在切换状态的时候可能会写不少if-else判断,而且随着状态的增多,这些分支会变得更多,难以维护。而如果我们使用状态模式,则可以将每个状态封装到一个类中,便于管理;而且在增加或减少状态时也会很方便。

代码实现

动作协议:

  1. /*
  2. 定义了程序员的一些动作,这些动作是程序员的日常活动,
  3. 也是触发状态切换的动作,因此抽象状态类也需要遵循这个协议,
  4. 因为它的子类需要实现这些操作
  5. */
  6. @protocol ActionProtocol <NSObject>
  7. @optional;
  8. - (void)wakeUp;
  9. - (void)fallAsleep;
  10. - (void)startCoding;
  11. - (void)startEating;
  12. @end

抽象状态类:

  1. //================== State.h ==================
  2. @interface State : NSObject<ActionProtocol>
  3. {
  4. @protected Coder *_coder;
  5. }
  6. - (instancetype)initWithCoder:(Coder *)coder;
  7. @end
  8. //================== State.m ==================
  9. @implementation State
  10. - (instancetype)initWithCoder:(Coder *)coder{
  11. self = [super init];
  12. if (self) {
  13. _coder = coder;
  14. }
  15. return self;
  16. }
  17. @end

具体状态类

  1. //================== 醒着状态 ==================
  2. @interface StateAwake : State
  3. @end
  4. @implementation StateAwake
  5. - (void)wakeUp{
  6. NSLog(@"Already awake, can not change state to awake again");
  7. }
  8. - (void)startCoding{
  9. NSLog(@"Change state from awake to coding");
  10. [_coder setState:(State *)[_coder stateCoding]];
  11. }
  12. - (void)startEating{
  13. NSLog(@"Change state from awake to eating");
  14. [_coder setState:(State *)[_coder stateEating]];
  15. }
  16. - (void)fallAsleep{
  17. NSLog(@"Change state from awake to sleeping");
  18. [_coder setState:(State *)[_coder stateSleeping]];
  19. }
  20. @end
  21. //================== 睡觉中状态 ==================
  22. @interface StateSleeping : State
  23. @end
  24. @implementation StateSleeping
  25. - (void)wakeUp{
  26. NSLog(@"Change state from sleeping to awake");
  27. [_coder setState:(State *)[_coder stateAwake]];
  28. }
  29. - (void)startCoding{
  30. NSLog(@"Already sleeping, can not change state to coding");
  31. }
  32. - (void)startEating{
  33. NSLog(@"Already sleeping, can change state to eating");
  34. }
  35. - (void)fallAsleep{
  36. NSLog(@"Already sleeping, can not change state to sleeping again");
  37. }
  38. @end
  39. //================== 吃饭中状态 ==================
  40. @interface StateEating : State
  41. @end
  42. @implementation StateEating
  43. - (void)wakeUp{
  44. NSLog(@"Already awake, can not change state to awake again");
  45. }
  46. - (void)startCoding{
  47. NSLog(@"New idea came out! change state from eating to coding");
  48. [_coder setState:(State *)[_coder stateCoding]];
  49. }
  50. - (void)startEating{
  51. NSLog(@"Already eating, can not change state to eating again");
  52. }
  53. - (void)fallAsleep{
  54. NSLog(@"Too tired, change state from eating to sleeping");
  55. [_coder setState:(State *)[_coder stateSleeping]];
  56. }
  57. @end
  58. //================== 写代码中状态 ==================
  59. @interface StateCoding : State
  60. @end
  61. @implementation StateCoding
  62. - (void)wakeUp{
  63. NSLog(@"Already awake, can not change state to awake again");
  64. }
  65. - (void)startCoding{
  66. NSLog(@"Already coding, can not change state to coding again");
  67. }
  68. - (void)startEating{
  69. NSLog(@"Too hungry, change state from coding to eating");
  70. [_coder setState:(State *)[_coder stateEating]];
  71. }
  72. - (void)fallAsleep{
  73. NSLog(@"Too tired, change state from coding to sleeping");
  74. [_coder setState:(State *)[_coder stateSleeping]];
  75. }
  76. @end

从上面的类可以看出,在有些状态之间的转换是失效的,有些是可以的。比如相同状态的切换是无效的;从 sleeping无法切换到coding,但是反过来可以,因为可能写代码累了就直接睡了。

程序员类:

  1. //================== Coder.h ==================
  2. @interface Coder : NSObject<ActionProtocol>
  3. @property (nonatomic, strong) StateAwake *stateAwake;
  4. @property (nonatomic, strong) StateCoding *stateCoding;
  5. @property (nonatomic, strong) StateEating *stateEating;
  6. @property (nonatomic, strong) StateSleeping *stateSleeping;
  7. - (void)setState:(State *)state;
  8. @end
  9. //================== Coder.m ==================
  10. @implementation Coder
  11. {
  12. State *_currentState;
  13. }
  14. - (instancetype)init{
  15. self = [super init];
  16. if (self) {
  17. _stateAwake = [[StateAwake alloc] initWithCoder:self];
  18. _stateCoding = [[StateCoding alloc] initWithCoder:self];
  19. _stateEating = [[StateEating alloc] initWithCoder:self];
  20. _stateSleeping = [[StateSleeping alloc] initWithCoder:self];
  21. _currentState = _stateAwake;
  22. }
  23. return self;
  24. }
  25. - (void)setState:(State *)state{
  26. _currentState = state;
  27. }
  28. - (void)wakeUp{
  29. [_currentState wakeUp];
  30. }
  31. - (void)startCoding{
  32. [_currentState startCoding];
  33. }
  34. - (void)startEating{
  35. [_currentState startEating];
  36. }
  37. - (void)fallAsleep{
  38. [_currentState fallAsleep];
  39. }
  40. @end

客户端使用:

  1. Coder *coder = [[Coder alloc] init];
  2. //change to awake.. failed
  3. [coder wakeUp];//Already awake, can not change state to awake again
  4. //change to coding
  5. [coder startCoding];//Change state from awake to coding
  6. //change to sleep
  7. [coder fallAsleep];//Too tired, change state from coding to sleeping
  8. //change to eat...failed
  9. [coder startEating];//Already sleeping, can change state to eating
  10. //change to wake up
  11. [coder wakeUp];//Change state from sleeping to awake
  12. //change wake up...failed
  13. [coder wakeUp];//Already awake, can not change state to awake again
  14. //change to eating
  15. [coder startEating];//Change state from awake to eating
  16. //change to coding
  17. [coder startCoding];//New idea came out! change state from eating to coding
  18. //change to sleep
  19. [coder fallAsleep];//Too tired, change state from coding to sleeping

从上面的例子可以看出,使用状态模式不需要去写if-else,而且如果今后想添加一个状态,只需要再创建一个状态子类,并在新的状态子类添加好对所有状态的处理,并在之前的状态子类中添加上对新状态的处理即可。即便我们修改了之前定义好的状态子类,但是这样也总比使用庞大的if-else要方便多。

代码对应的类图

导出图片Mon Apr 13 2020 15_32_37 GMT+0800 (中国标准时间).png-233.2kB

优点

缺点

命令模式

定义

命令模式(Command Pattern):命令(或请求)被封装成对象。客户端将命令(或请求)对象先传递给调用对象。调用对象再把该命令(或请求)对象传给合适的,可处理该命令(或请求)的对象来做处理。

由定义可以看出,在命令模式中,命令被封装成了对象,而发送命令的客户端与处理命令的接收者中间被调用对象隔开了,这种设计的原因或者适用的场景是什么样的呢?

适用场景

在有些场景下,任务的处理可能不是需要立即执行的:可能需要记录(日至),撤销或重试(网络请求)。那么在这些场景下,如果任务的请求者和执行者是紧耦合状态下的话就可能会将很多其他执行策略的代码和立即执行的代码混合到一起。

这些其他执行策略,我们暂时称之为控制和管理策略,而如果我们如果想控制和管理请求,就需要:

1.把请求抽象出来
2.让另外一个角色来负责控制和管理请求的任务

因此命令模式就是为此场景量身打造的,它通过:

1.把请求封装成对象
2.使用调用者在客户端和请求处理者之间来做一个“拦截”,方便对请求对象做控制和管理。

在市中心逛了很久的街后, 你找到了一家不错的餐厅, 坐在了临窗的座位上。 一名友善的服务员走近你, 迅速记下你点的食物, 写在一张纸上。服务员来到厨房,把订单贴在墙上。过了一段时间,厨师拿到了订单, 他根据订单来准备食物。 厨师将做好的食物和订单一起放在托盘上。 服务员看到托盘后对订单进行检查, 确保所有食物都是你要的, 然后将食物放到了你的桌上。

那张纸就是一个命令, 它在厨师开始烹饪前一直位于队列中。命令中包含与烹饪这些食物相关的所有信息。 厨师能够根据它马上开始烹饪, 而无需跑来直接和你确认订单详情。

成员与类图

成员

不包括请求的发起者(客户端),命令模式共有四个成员:

模式类图

导出图片Mon Apr 13 2020 15_32_50 GMT+0800 (中国标准时间).png-172.3kB

代码示例

场景概述

模拟一个使用遥控器开灯和关灯的例子。

在这个例子中,使用遥控器的人就是客户端,TA发起开启或关闭灯的命令给遥控器(调用者)。然后调用者将命令传递给接收者(灯)。

在这里人是不直接接触灯的,开启和关闭的命令是通过遥控器来做的转发,最后传达给灯来执行。

代码实现

接收者(灯类):

  1. //================== Light.h ==================
  2. @interface Light : NSObject
  3. //开灯
  4. - (void)lightOn;
  5. //关灯
  6. - (void)lightOff;
  7. @end
  8. //================== Light.m ==================
  9. @implementation Light
  10. - (void)lightOn{
  11. NSLog(@"Light on");
  12. }
  13. - (void)lightOff{
  14. NSLog(@"Light off");
  15. }
  16. @end

抽象命令类:

  1. //================== Command.h ==================
  2. @interface Command : NSObject
  3. - (void)excute;
  4. @end
  5. //================== Command.m ==================
  6. @implementation Command
  7. @end

具体命令类:

  1. //================== 开灯命令类 ==================
  2. @interface CommandLightOn : Command
  3. - (instancetype)initWithLight:(Light *)light;
  4. @end
  5. @implementation CommandLightOn
  6. {
  7. Light *_light;
  8. }
  9. - (instancetype)initWithLight:(Light *)light{
  10. self = [super init];
  11. if (self) {
  12. _light = light;
  13. }
  14. return self;
  15. }
  16. - (void)excute{
  17. [_light lightOn];
  18. }
  19. //================== 关灯命令类 ==================
  20. @interface CommandLightOff : Command
  21. - (instancetype)initWithLight:(Light *)light;
  22. @end
  23. @implementation CommandLightOff
  24. {
  25. Light *_light;
  26. }
  27. - (instancetype)initWithLight:(Light *)light{
  28. self = [super init];
  29. if (self) {
  30. _light = light;
  31. }
  32. return self;
  33. }
  34. - (void)excute{
  35. [_light lightOff];
  36. }

调用者类(遥控器):

  1. //================== RemoteControl.h ==================
  2. @interface RemoteControl : NSObject
  3. - (void)setCommand:(Command *)command;
  4. - (void)pressButton;
  5. @end
  6. //================== RemoteControl.m ==================
  7. @implementation RemoteControl
  8. {
  9. Command *_command;
  10. }
  11. - (void)setCommand:(Command *)command{
  12. _command = command;
  13. }
  14. - (void)pressButton{
  15. [_command excute];
  16. }
  17. @end

客户端调用:

  1. //================== client ==================
  2. //init Light and Command instance
  3. //inject light instance into command instance
  4. Light *light = [[Light alloc] init];
  5. CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];
  6. //set command on instance into remote control instance
  7. RemoteControl *rm = [[RemoteControl alloc] init];
  8. [rm setCommand:co];
  9. //excute command(light on command)
  10. [rm pressButton];
  11. //inject light instance into command off instance
  12. CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];
  13. //change to off command
  14. [rm setCommand:cf];
  15. //excute command(light close command)
  16. [rm pressButton];

日至输出:

  1. [11851:1190777] Light on
  2. [11851:1190777] Light off

从上面的代码可以看到,我们首先准备好具体命令类的实例,然后将其传递给遥控器类,最后触发遥控器的pressButton方法来间接触发light对象的相应操作。

代码对应的类图

导出图片Mon Apr 13 2020 15_33_11 GMT+0800 (中国标准时间).png-192.4kB

优点

缺点

中介者模式

定义

中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互,中介者使各对象之间不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟n个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。

下载.jpeg-177.4kB

适用场景

成员与类图

成员

中介者模式一共有四个成员:

模式类图

导出图片Mon Apr 13 2020 15_33_53 GMT+0800 (中国标准时间).png-225.6kB

代码示例

场景概述

模拟一个多人对话的场景:当一个人发出消息后,另外的那些人可以收到该消息。

假设一共有A,B,C三个人,那么当A发出消息后,需要分别传递给B,C二人。如果三个人直接相互通信,可能伪代码会是这样的:

  1. A sent message to B
  2. A sent message to C

而且随着人数的增多,代码行数也会变多,这显然是不合理的。

因此在这种场景下,我们需要使用中介者模式,在所有人中间来做一个消息的多路转发:当A发出消息后,由中介者来发送给B和C:

  1. A sent message to Mediator ;
  2. Mediator sent message to B & C

代码实现

用户类:

  1. //================== User.h ==================
  2. @interface User : NSObject
  3. - (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator;
  4. - (void)sendMessage:(NSString *)message;
  5. - (void)receivedMessage:(NSString *)message;
  6. @end
  7. //================== User.m ==================
  8. @implementation User
  9. {
  10. NSString *_name;
  11. ChatMediator *_chatMediator;
  12. }
  13. - (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator{
  14. self = [super init];
  15. if (self) {
  16. _name = name;
  17. _chatMediator = mediator;
  18. }
  19. return self;
  20. }
  21. - (void)sendMessage:(NSString *)message{
  22. NSLog(@"================");
  23. NSLog(@"%@ sent message:%@",_name,message);
  24. [_chatMediator sendMessage:message fromUser:self];
  25. }
  26. - (void)receivedMessage:(NSString *)message{
  27. NSLog(@"%@ has received message:%@",_name,message);
  28. }
  29. @end

中介者类:

  1. //================== ChatMediator.h ==================
  2. @interface ChatMediator : NSObject
  3. - (void)addUser:(User *)user;
  4. - (void)sendMessage:(NSString *)message fromUser:(User *)user;
  5. @end
  6. //================== ChatMediator.m ==================
  7. @implementation ChatMediator
  8. {
  9. NSMutableArray <User *>*_userList;
  10. }
  11. - (instancetype)init{
  12. self = [super init];
  13. if (self) {
  14. _userList = [NSMutableArray array];
  15. }
  16. return self;
  17. }
  18. - (void)addUser:(User *)user{
  19. [_userList addObject:user];
  20. }
  21. - (void)sendMessage:(NSString *)message fromUser:(User *)user{
  22. [_userList enumerateObjectsUsingBlock:^(User * _Nonnull iterUser, NSUInteger idx, BOOL * _Nonnull stop) {
  23. if (iterUser != user) {
  24. [iterUser receivedMessage:message];
  25. }
  26. }];
  27. }
  28. @end

中介者类提供了addUser:的方法,因此我们可以不断将用户添加到这个中介者里面(可以看做是注册行为或是“加入群聊”)。在每次加入一个User实例后,都将这个实例添加到中介者持有的这个可变数组里。于是在将来中介者就可以通过遍历数组的方式来做消息的多路转发,具体实现可以看sendMessage:fromUser:这个方法。

客户端使用:

  1. ChatMediator *cm = [[ChatMediator alloc] init];
  2. User *user1 = [[User alloc] initWithName:@"Jack" mediator:cm];
  3. User *user2 = [[User alloc] initWithName:@"Bruce" mediator:cm];
  4. User *user3 = [[User alloc] initWithName:@"Lucy" mediator:cm];
  5. [cm addUser:user1];
  6. [cm addUser:user2];
  7. [cm addUser:user3];
  8. [user1 sendMessage:@"happy"];
  9. [user2 sendMessage:@"new"];
  10. [user3 sendMessage:@"year"];

日志输出:

  1. [13806:1284059] ================
  2. [13806:1284059] Jack sent message:happy
  3. [13806:1284059] Bruce has received message:happy
  4. [13806:1284059] Lucy has received message:happy
  5. [13806:1284059] ================
  6. [13806:1284059] Bruce sent message:new
  7. [13806:1284059] Jack has received message:new
  8. [13806:1284059] Lucy has received message:new
  9. [13806:1284059] ================
  10. [13806:1284059] Lucy sent message:year
  11. [13806:1284059] Jack has received message:year
  12. [13806:1284059] Bruce has received message:year

优点

缺点

参考

面向对象设计的设计模式(三):行为型模式
行为模式

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注