@qidiandasheng
2021-01-12T17:04:39.000000Z
字数 12851
阅读 1109
架构
行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此可在进行这些交流活动时增强弹性。
在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。比如BubbleSort
、SelectionSort
都是为了实现排序的,只不过一个是用冒泡排序算法来实现的,另一个是用选择排序算法来实现的。而在命令模式中,不同的命令具有不同的目的,对 应不同的处理逻辑,并且互相之间不可替换。
观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。
在状态模式(State Pattern):允许一个对象在其内部状态改变时,改变它的行为。
状态模式是状态机的一种实现方式。状态机又叫有限状态机,它有3个部分组成:状态、事件、动作。其中事件也称为转移条件。事件触发状态的转移及动作的执行。不过动作不是必须的,也可能只转移状态,不执行任何动作。
以下为三种状态机的实现方式:
第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移 图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简 单、最直接,是首选。
第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。
不是所有状态相关的业务场景都可以使用状态模式的,比如如下的业务场景:
游戏这种比较复杂的状态机,包含的状态比较多,优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现。
状态模式一共只有四个成员:
模拟一个程序员一天的生活,他有四个状态:
1.醒着
2.睡觉中
3.写代码中
4.吃饭中
这个程序员有四个状态,但是有些状态之间是无法切换的:比如从睡觉是无法切换到写代码的(因为需要切换到醒着,然后才能到写代码);从吃饭中是无法切换到醒着的,因为已经醒着了。
如果我们不使用状态模式,在切换状态的时候可能会写不少if-else
判断,而且随着状态的增多,这些分支会变得更多,难以维护。而如果我们使用状态模式,则可以将每个状态封装到一个类中,便于管理;而且在增加或减少状态时也会很方便。
动作协议:
/*
定义了程序员的一些动作,这些动作是程序员的日常活动,
也是触发状态切换的动作,因此抽象状态类也需要遵循这个协议,
因为它的子类需要实现这些操作
*/
@protocol ActionProtocol <NSObject>
@optional;
- (void)wakeUp;
- (void)fallAsleep;
- (void)startCoding;
- (void)startEating;
@end
抽象状态类:
//================== State.h ==================
@interface State : NSObject<ActionProtocol>
{
@protected Coder *_coder;
}
- (instancetype)initWithCoder:(Coder *)coder;
@end
//================== State.m ==================
@implementation State
- (instancetype)initWithCoder:(Coder *)coder{
self = [super init];
if (self) {
_coder = coder;
}
return self;
}
@end
具体状态类
//================== 醒着状态 ==================
@interface StateAwake : State
@end
@implementation StateAwake
- (void)wakeUp{
NSLog(@"Already awake, can not change state to awake again");
}
- (void)startCoding{
NSLog(@"Change state from awake to coding");
[_coder setState:(State *)[_coder stateCoding]];
}
- (void)startEating{
NSLog(@"Change state from awake to eating");
[_coder setState:(State *)[_coder stateEating]];
}
- (void)fallAsleep{
NSLog(@"Change state from awake to sleeping");
[_coder setState:(State *)[_coder stateSleeping]];
}
@end
//================== 睡觉中状态 ==================
@interface StateSleeping : State
@end
@implementation StateSleeping
- (void)wakeUp{
NSLog(@"Change state from sleeping to awake");
[_coder setState:(State *)[_coder stateAwake]];
}
- (void)startCoding{
NSLog(@"Already sleeping, can not change state to coding");
}
- (void)startEating{
NSLog(@"Already sleeping, can change state to eating");
}
- (void)fallAsleep{
NSLog(@"Already sleeping, can not change state to sleeping again");
}
@end
//================== 吃饭中状态 ==================
@interface StateEating : State
@end
@implementation StateEating
- (void)wakeUp{
NSLog(@"Already awake, can not change state to awake again");
}
- (void)startCoding{
NSLog(@"New idea came out! change state from eating to coding");
[_coder setState:(State *)[_coder stateCoding]];
}
- (void)startEating{
NSLog(@"Already eating, can not change state to eating again");
}
- (void)fallAsleep{
NSLog(@"Too tired, change state from eating to sleeping");
[_coder setState:(State *)[_coder stateSleeping]];
}
@end
//================== 写代码中状态 ==================
@interface StateCoding : State
@end
@implementation StateCoding
- (void)wakeUp{
NSLog(@"Already awake, can not change state to awake again");
}
- (void)startCoding{
NSLog(@"Already coding, can not change state to coding again");
}
- (void)startEating{
NSLog(@"Too hungry, change state from coding to eating");
[_coder setState:(State *)[_coder stateEating]];
}
- (void)fallAsleep{
NSLog(@"Too tired, change state from coding to sleeping");
[_coder setState:(State *)[_coder stateSleeping]];
}
@end
从上面的类可以看出,在有些状态之间的转换是失效的,有些是可以的。比如相同状态的切换是无效的;从 sleeping
无法切换到coding
,但是反过来可以,因为可能写代码累了就直接睡了。
程序员类:
//================== Coder.h ==================
@interface Coder : NSObject<ActionProtocol>
@property (nonatomic, strong) StateAwake *stateAwake;
@property (nonatomic, strong) StateCoding *stateCoding;
@property (nonatomic, strong) StateEating *stateEating;
@property (nonatomic, strong) StateSleeping *stateSleeping;
- (void)setState:(State *)state;
@end
//================== Coder.m ==================
@implementation Coder
{
State *_currentState;
}
- (instancetype)init{
self = [super init];
if (self) {
_stateAwake = [[StateAwake alloc] initWithCoder:self];
_stateCoding = [[StateCoding alloc] initWithCoder:self];
_stateEating = [[StateEating alloc] initWithCoder:self];
_stateSleeping = [[StateSleeping alloc] initWithCoder:self];
_currentState = _stateAwake;
}
return self;
}
- (void)setState:(State *)state{
_currentState = state;
}
- (void)wakeUp{
[_currentState wakeUp];
}
- (void)startCoding{
[_currentState startCoding];
}
- (void)startEating{
[_currentState startEating];
}
- (void)fallAsleep{
[_currentState fallAsleep];
}
@end
客户端使用:
Coder *coder = [[Coder alloc] init];
//change to awake.. failed
[coder wakeUp];//Already awake, can not change state to awake again
//change to coding
[coder startCoding];//Change state from awake to coding
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
//change to eat...failed
[coder startEating];//Already sleeping, can change state to eating
//change to wake up
[coder wakeUp];//Change state from sleeping to awake
//change wake up...failed
[coder wakeUp];//Already awake, can not change state to awake again
//change to eating
[coder startEating];//Change state from awake to eating
//change to coding
[coder startCoding];//New idea came out! change state from eating to coding
//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping
从上面的例子可以看出,使用状态模式不需要去写if-else
,而且如果今后想添加一个状态,只需要再创建一个状态子类,并在新的状态子类添加好对所有状态的处理,并在之前的状态子类中添加上对新状态的处理即可。即便我们修改了之前定义好的状态子类,但是这样也总比使用庞大的if-else
要方便多。
命令模式(Command Pattern):命令(或请求)被封装成对象。客户端将命令(或请求)对象先传递给调用对象。调用对象再把该命令(或请求)对象传给合适的,可处理该命令(或请求)的对象来做处理。
由定义可以看出,在命令模式中,命令被封装成了对象,而发送命令的客户端与处理命令的接收者中间被调用对象隔开了,这种设计的原因或者适用的场景是什么样的呢?
在有些场景下,任务的处理可能不是需要立即执行的:可能需要记录(日至),撤销或重试(网络请求)。那么在这些场景下,如果任务的请求者和执行者是紧耦合状态下的话就可能会将很多其他执行策略的代码和立即执行的代码混合到一起。
这些其他执行策略,我们暂时称之为控制和管理策略,而如果我们如果想控制和管理请求,就需要:
1.把请求抽象出来
2.让另外一个角色来负责控制和管理请求的任务
因此命令模式就是为此场景量身打造的,它通过:
1.把请求封装成对象
2.使用调用者在客户端和请求处理者之间来做一个“拦截”,方便对请求对象做控制和管理。
在市中心逛了很久的街后, 你找到了一家不错的餐厅, 坐在了临窗的座位上。 一名友善的服务员走近你, 迅速记下你点的食物, 写在一张纸上。服务员来到厨房,把订单贴在墙上。过了一段时间,厨师拿到了订单, 他根据订单来准备食物。 厨师将做好的食物和订单一起放在托盘上。 服务员看到托盘后对订单进行检查, 确保所有食物都是你要的, 然后将食物放到了你的桌上。
那张纸就是一个命令, 它在厨师开始烹饪前一直位于队列中。命令中包含与烹饪这些食物相关的所有信息。 厨师能够根据它马上开始烹饪, 而无需跑来直接和你确认订单详情。
不包括请求的发起者(客户端),命令模式共有四个成员:
模拟一个使用遥控器开灯和关灯的例子。
在这个例子中,使用遥控器的人就是客户端,TA发起开启或关闭灯的命令给遥控器(调用者)。然后调用者将命令传递给接收者(灯)。
在这里人是不直接接触灯的,开启和关闭的命令是通过遥控器来做的转发,最后传达给灯来执行。
接收者(灯类):
//================== Light.h ==================
@interface Light : NSObject
//开灯
- (void)lightOn;
//关灯
- (void)lightOff;
@end
//================== Light.m ==================
@implementation Light
- (void)lightOn{
NSLog(@"Light on");
}
- (void)lightOff{
NSLog(@"Light off");
}
@end
抽象命令类:
//================== Command.h ==================
@interface Command : NSObject
- (void)excute;
@end
//================== Command.m ==================
@implementation Command
@end
具体命令类:
//================== 开灯命令类 ==================
@interface CommandLightOn : Command
- (instancetype)initWithLight:(Light *)light;
@end
@implementation CommandLightOn
{
Light *_light;
}
- (instancetype)initWithLight:(Light *)light{
self = [super init];
if (self) {
_light = light;
}
return self;
}
- (void)excute{
[_light lightOn];
}
//================== 关灯命令类 ==================
@interface CommandLightOff : Command
- (instancetype)initWithLight:(Light *)light;
@end
@implementation CommandLightOff
{
Light *_light;
}
- (instancetype)initWithLight:(Light *)light{
self = [super init];
if (self) {
_light = light;
}
return self;
}
- (void)excute{
[_light lightOff];
}
调用者类(遥控器):
//================== RemoteControl.h ==================
@interface RemoteControl : NSObject
- (void)setCommand:(Command *)command;
- (void)pressButton;
@end
//================== RemoteControl.m ==================
@implementation RemoteControl
{
Command *_command;
}
- (void)setCommand:(Command *)command{
_command = command;
}
- (void)pressButton{
[_command excute];
}
@end
客户端调用:
//================== client ==================
//init Light and Command instance
//inject light instance into command instance
Light *light = [[Light alloc] init];
CommandLightOn *co = [[CommandLightOn alloc] initWithLight:light];
//set command on instance into remote control instance
RemoteControl *rm = [[RemoteControl alloc] init];
[rm setCommand:co];
//excute command(light on command)
[rm pressButton];
//inject light instance into command off instance
CommandLightOff *cf = [[CommandLightOff alloc] initWithLight:light];
//change to off command
[rm setCommand:cf];
//excute command(light close command)
[rm pressButton];
日至输出:
[11851:1190777] Light on
[11851:1190777] Light off
从上面的代码可以看到,我们首先准备好具体命令类的实例,然后将其传递给遥控器类,最后触发遥控器的pressButton
方法来间接触发light
对象的相应操作。
中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互,中介者使各对象之间不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟n个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。
系统结构可能会日益变得复杂,对象之间存在大量的相互关联和调用,系统的整体结构容易变为网状结构。在这种情况下,如果需要修改某一个对象,则可能会要跟踪和该对象关联的其他所有对象,并进行处理。耦合越多,修改的地方就会越多。
如果我们使用中介者对象,则可以将系统的网状结构变成以中介者为中心的星型结构。中介者承担了中转作用和协调作用,简化了对象之间的交互,而且还可以给对象间的交互进行进一步的控制。
组件因过于依赖其他组件而无法在不同应用中复用时,可使用中介者模式。
类似于我们的组件化方式,提供一个中间路由层
中介者模式一共有四个成员:
模拟一个多人对话的场景:当一个人发出消息后,另外的那些人可以收到该消息。
假设一共有A,B,C三个人,那么当A发出消息后,需要分别传递给B,C二人。如果三个人直接相互通信,可能伪代码会是这样的:
A sent message to B
A sent message to C
而且随着人数的增多,代码行数也会变多,这显然是不合理的。
因此在这种场景下,我们需要使用中介者模式,在所有人中间来做一个消息的多路转发:当A发出消息后,由中介者来发送给B和C:
A sent message to Mediator ;
Mediator sent message to B & C
用户类:
//================== User.h ==================
@interface User : NSObject
- (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator;
- (void)sendMessage:(NSString *)message;
- (void)receivedMessage:(NSString *)message;
@end
//================== User.m ==================
@implementation User
{
NSString *_name;
ChatMediator *_chatMediator;
}
- (instancetype)initWithName:(NSString *)name mediator:(ChatMediator *)mediator{
self = [super init];
if (self) {
_name = name;
_chatMediator = mediator;
}
return self;
}
- (void)sendMessage:(NSString *)message{
NSLog(@"================");
NSLog(@"%@ sent message:%@",_name,message);
[_chatMediator sendMessage:message fromUser:self];
}
- (void)receivedMessage:(NSString *)message{
NSLog(@"%@ has received message:%@",_name,message);
}
@end
中介者类:
//================== ChatMediator.h ==================
@interface ChatMediator : NSObject
- (void)addUser:(User *)user;
- (void)sendMessage:(NSString *)message fromUser:(User *)user;
@end
//================== ChatMediator.m ==================
@implementation ChatMediator
{
NSMutableArray <User *>*_userList;
}
- (instancetype)init{
self = [super init];
if (self) {
_userList = [NSMutableArray array];
}
return self;
}
- (void)addUser:(User *)user{
[_userList addObject:user];
}
- (void)sendMessage:(NSString *)message fromUser:(User *)user{
[_userList enumerateObjectsUsingBlock:^(User * _Nonnull iterUser, NSUInteger idx, BOOL * _Nonnull stop) {
if (iterUser != user) {
[iterUser receivedMessage:message];
}
}];
}
@end
中介者类提供了addUser:
的方法,因此我们可以不断将用户添加到这个中介者里面(可以看做是注册行为或是“加入群聊”)。在每次加入一个User
实例后,都将这个实例添加到中介者持有的这个可变数组里。于是在将来中介者就可以通过遍历数组的方式来做消息的多路转发,具体实现可以看sendMessage:fromUser:
这个方法。
客户端使用:
ChatMediator *cm = [[ChatMediator alloc] init];
User *user1 = [[User alloc] initWithName:@"Jack" mediator:cm];
User *user2 = [[User alloc] initWithName:@"Bruce" mediator:cm];
User *user3 = [[User alloc] initWithName:@"Lucy" mediator:cm];
[cm addUser:user1];
[cm addUser:user2];
[cm addUser:user3];
[user1 sendMessage:@"happy"];
[user2 sendMessage:@"new"];
[user3 sendMessage:@"year"];
日志输出:
[13806:1284059] ================
[13806:1284059] Jack sent message:happy
[13806:1284059] Bruce has received message:happy
[13806:1284059] Lucy has received message:happy
[13806:1284059] ================
[13806:1284059] Bruce sent message:new
[13806:1284059] Jack has received message:new
[13806:1284059] Lucy has received message:new
[13806:1284059] ================
[13806:1284059] Lucy sent message:year
[13806:1284059] Jack has received message:year
[13806:1284059] Bruce has received message:year