访问者模式(Visitor Pattern)
2022-09-19 本文已影响0人
long弟弟
访问者模式
意图:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式分离对象的数据和行为,使用访问者模式,可以不修改已有类的情况下,增加新的操作角色和职责。
访问者模式.jpg角色和职责
- Visitor 抽象访问者
声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口
为该对象结构中ConcreteElement的每一个类声明一个Visit操作。该操作的名字和特征标识了发送Visit请求给该访问者的那个类。这使得访问者可以确定正被访问元素的具体的类。这样访问者就可以通过该元素的特定接口直接访问它 - ConCreteVisitor 具体访问者
实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作 - Element 抽象节点,元素
声明一个接受操作,接受一个访问者对象作为一个参量 - ConCreteElement 具体节点
实现了抽象元素所规定的接受操作,该操作以一个访问者作为参数 - ObjectStructure 结构对象,高层的数据结构(集合)
有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个符合对象或者一个集合,如数组(NSArray)或集合(NSSet)
代码示例
不使用高层的数据结构
#import <Foundation/Foundation.h>
@class Visitor;
@interface ParkElement : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation ParkElement
- (instancetype)initWithName:(NSString *)name {
return nil;
}
- (void)accept:(Visitor *)visitor {
}
@end
@interface Visitor : NSObject
- (void)visit:(ParkElement *)park;
@end
@implementation Visitor
- (void)visit:(ParkElement *)park {
}
@end
//公园A部分
@interface ParkA : ParkElement
@end
@implementation ParkA
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.name = name;
}
return self;
}
- (void)accept:(Visitor *)visitor {
//公园接受访问者访问 让访问者操作
[visitor visit:self];
}
@end
//公园B部分
@interface ParkB : ParkElement
@end
@implementation ParkB
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.name = name;
}
return self;
}
- (void)accept:(Visitor *)visitor {
[visitor visit:self];
}
@end
//访问者:清洁工A
@interface VisitorA : Visitor
@end
@implementation VisitorA
- (void)visit:(ParkElement *)park {
NSLog(@"清洁工A 打扫公园A %@部分", park.name);
}
@end
//访问者:清洁工B
@interface VisitorB : Visitor
@end
@implementation VisitorB
- (void)visit:(ParkElement *)park {
NSLog(@"清洁工B 打扫公园B %@部分", park.name);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Visitor *vA = [[VisitorA alloc] init];
Visitor *vB = [[VisitorB alloc] init];
ParkElement *pA = [[ParkA alloc] initWithName:@"广场"];
ParkElement *pB = [[ParkB alloc] initWithName:@"湖边"];
[pA accept:vA];
[pB accept:vB];
}
return 0;
}
/*
清洁工A 打扫公园A 广场部分
清洁工B 打扫公园B 湖边部分
*/
使用高层的数据结构
#import <Foundation/Foundation.h>
@class Visitor;
@interface ParkElement : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation ParkElement
- (instancetype)initWithName:(NSString *)name {
return nil;
}
- (void)accept:(Visitor *)visitor {
}
@end
@interface Visitor : NSObject
- (void)visit:(ParkElement *)park;
@end
@implementation Visitor
- (void)visit:(ParkElement *)park {
}
@end
//公园A部分
@interface ParkA : ParkElement
@end
@implementation ParkA
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.name = name;
}
return self;
}
- (void)accept:(Visitor *)visitor {
//公园接受访问者访问 让访问者操作
[visitor visit:self];
}
@end
//公园B部分
@interface ParkB : ParkElement
@end
@implementation ParkB
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.name = name;
}
return self;
}
- (void)accept:(Visitor *)visitor {
[visitor visit:self];
}
@end
//访问者:清洁工A
@interface VisitorA : Visitor
@end
@implementation VisitorA
- (void)visit:(ParkElement *)park {
NSLog(@"清洁工A 打扫公园A 部分");
}
@end
//访问者:清洁工B
@interface VisitorB : Visitor
@end
@implementation VisitorB
- (void)visit:(ParkElement *)park {
NSLog(@"清洁工B 打扫公园B 部分");
}
@end
//整个公园
@interface Park : ParkElement
//集合里存放公园的每一部分
@property (nonatomic, strong) NSMutableArray *parks;
@end
@implementation Park
- (instancetype)init {
if (self = [super init]) {
self.parks = [NSMutableArray array];
}
return self;
}
- (void)setPark:(ParkElement *)park {
[self.parks addObject:park];
}
- (void)accept:(Visitor *)visitor {
for (ParkElement *park in self.parks) {
//公园A,公园B,接受管理者 访问
[park accept:visitor];
}
}
@end
@interface ManagerVisitor : Visitor
@end
@implementation ManagerVisitor
- (void)visit:(ParkElement *)park {
NSLog(@"管理者 访问 公园的各个部分:%@", park.name);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Visitor *manager = [[ManagerVisitor alloc] init];
Park *park = [[Park alloc] init];
ParkElement *pA = [[ParkA alloc] initWithName:@"广场"];
ParkElement *pB = [[ParkB alloc] initWithName:@"湖边"];
[park setPark:pA];
[park setPark:pB];
//公园接受管理员访问
[park accept:manager];
}
return 0;
}
/*
管理者 访问 公园的各个部分:广场
管理者 访问 公园的各个部分:湖边
*/
用处
解耦:允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
目标:不同的动作访问数据结构不同的部分
把数据结构
和作用于数据结构上的操作
进行解耦,适用于数据结构比较稳定的场合
优点
- 增加新的操作很容易
因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。 - 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
- 访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。迭代子只能访问属于同一个类型等级结构的成员对象,而不能访问属于不同等级结构的对象。访问者模式可以做到这一点。
- 积累状态。每一个单独的访问者对象都接种了相关的行为,从而也就可以在访问的过程中将执行操作的状态积累在自己内部,而不是分散到很多的节点对象中。这是有益于系统维护的优点。
缺点
- 增加新的节点类变得很困难。
每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。 - 破坏封装
访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有节点对象的要求:它们必须暴露一些自己的操作和内部状态。不然,访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这也是破坏封装的。
题外话
- 单分派:single dispatch
是指执行那个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的的编译时类型来决定。 - 双分派:double dispatch
是指执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。 - dispatch
在面向对象编程语言中,我们可以把方法调用理解为一种消息传递,也就是dispatch
。一个对象调用另一个对象的方法,就相当于给它发送一条消息。这条消息起码要包含对象名、方法名、方法参数。 - single、double
指的是执行哪个对象的哪个方法,跟几个因素的运行时类型有关。Single Dispatch
之所以称为“Single”,是因为执行哪个对象的哪个方法,只跟“对象”的运行时类型有关。Double Dispatch
之所以称为“Double”,是因为执行哪个对象的哪个方法,跟“对象”和“方法参数”两者的运行时类型有关。
想要了解更访问者模式的童鞋可以参考Objective-C设计模式解析-访问者。