Objective-C中的访客模式

我一直在寻找在Objective-C中实现Visitor设计模式的最佳方法。 由于该语言不支持方法重载,因此在Java中可能发现的“传统”实现似乎是不可能的。

在我当前的实现中,我有一个Visitor协议,一个Visitor类,以及该Visitor类的几个子类,以及要访问的各种对象。 一旦访问对象接受访问者,他们就会调用访问者的访问方法,将自己作为参数传递。 visit方法接受一个id,然后对它进行类型转换和调用

[self performTasksOnObjectClass: (ObjectClass *)object]; 

作为if / elseif / else块的一部分。 这些调用由相关的Visitor子类拾取,访问者执行对象所需的任何任务。

有没有比这更好的实现访客模式的方法? 我不喜欢在if / elseif / else块中使用’isKindOfClass’或’isMemberOfClass’调用。 它看起来很笨重而且不优雅。 另外,以这种方式仍然“值得”实现Visitor方法吗? 访问过的对象仍然可以不知道访问者,但还有其他方法可以实现这一点。

已经有人建议,委托或类集群可能是访客模式的更合适的替代方案。 我有兴趣看看你们都在想什么!

编辑:我实际上在子类中调用了不同名称的方法,我已经更清楚了。

你可以使用一些内省/reflection来使它更清洁。 您不能重载方法名称,但可以避免编写如下的switch语句:

 - (void)performTasks:(id)object { Class class = [object class]; while (class && class != [NSObject class]) { NSString *methodName = [NSString stringWithFormat:@"perform%@Tasks:", class]; SEL selector = NSSelectorFromString(methodName); if ([self respondsToSelector:selector]) { [self performSelector:selector withObject:object]; return; } class = [class superclass]; } [NSException raise:@"Visitor %@ doesn't have a performTasks method for %@", [self class], [object class]]; } 

您的实际performTasks方法将按如下方式命名:

 - (void)performFooTasks:(Foo *)foo { //tasks for objects of class Foo } - (void)performBarTasks:(Bar *)bar { //tasks for objects of class Bar } etc... 

注意:如果您正在使用ARC,则会通过以这种方式从字符串创建选择器来获得虚假警告,因为它无法在编译时告诉方法参数应该保留规则。 您可以使用#pragma使这些警告静音,如下所示:

 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self performSelector:selector withObject:object]; #pragma clang diagnostic pop 

您可以采用以下方法将选择器映射到objc类型,然后让此实现执行“动态重载”的方法查找。 这样的实现可以消除实际使用中的大部分噪声(请参阅演示 – 8页向下):

我们的包括:

 #import  #import  

我们的基本类型:

MONVisitorEntry将类关联到选择器:

MONVisitorEntry.h:

 @interface MONVisitorEntry : NSObject + (MONVisitorEntry *)newVisitorEntryWithType:(Class)inType selector:(SEL)inSelector; - (id)visit:(id)target parameter:(id)parameter; - (Class)type; @end 

MONVisitorEntry.m:

 @implementation MONVisitorEntry { @private Class type; SEL selector; } - (id)initWithType:(Class)inType selector:(SEL)inSelector { self = [super init]; if (0 != self) { type = inType; selector = inSelector; } return self; } - (NSString *)description { return [NSString stringWithFormat:@"%@ - Type: %@ - SEL: %@", [super description], type, NSStringFromSelector(selector)]; } - (NSUInteger)hash { return (NSUInteger)type; } - (Class)type { return type; } + (MONVisitorEntry *)newVisitorEntryWithType:(Class)inType selector:(SEL)inSelector { return [[self alloc] initWithType:inType selector:inSelector]; } - (id)visit:(id)target parameter:(id)parameter { return ([target methodForSelector:selector])(target, selector, parameter); } @end 

MONVisitorMapMONVisitorEntry对象的地图。 这种类型没有类型安全 – 你应该重新引入它。

MONVisitorMap.h:

 @interface MONVisitorMap : NSObject - (void)addEntryWithType:(Class)inType selector:(SEL)inSelector; /* perhaps you would prefer that inTarget is also held by self? in that case, you could also cache the IMPs for faster lookups. */ - (id)visit:(id)inTarget parameter:(id)inParameter; @end 

MONVisitorMap.m:

 @implementation MONVisitorMap { @private NSMutableSet * entries; } - (id)init { self = [super init]; if (0 != self) { entries = [NSMutableSet new]; } return self; } - (NSString *)description { return [[super description] stringByAppendingString:[entries description]]; } - (void)addEntryWithType:(Class)inType selector:(SEL)inSelector { [entries addObject:[MONVisitorEntry newVisitorEntryWithType:inType selector:inSelector]]; } - (id)visit:(id)inTarget parameter:(id)inParameter parameterClass:(Class)inParameterClass { MONVisitorEntry * entry = 0; for (MONVisitorEntry * at in entries) { if (inParameterClass == at.type) { entry = at; } } if (0 != entry) { return [entry visit:inTarget parameter:inParameter]; } Class superclass = class_getSuperclass(inParameterClass); if (0 == superclass) { assert(0 && "exhausted class hierarchy!"); return 0; } return [self visit:inTarget parameter:inParameter parameterClass:superclass]; } - (id)visit:(id)inTarget parameter:(id)inParameter { return [self visit:inTarget parameter:inParameter parameterClass:[inParameter class]]; } @end 

演示:

创建一些测试类型(在这里添加一些.m文件):

 @interface Animal : NSObject @end @implementation Animal @end @interface Dog : Animal @end @implementation Dog @end @interface Greyhound : Dog @end @implementation Greyhound @end @interface Boxer : Dog @end @implementation Boxer @end @interface Squirrel : Animal @end @implementation Squirrel @end @interface Tapir : Animal @end @implementation Tapir @end 

创建访问者:

MONZoo.h:

 @interface MONZoo : NSObject /* our abstract "visit" entry, which introduces type safety: */ - (void)exhibit:(Animal *)inAnimal; @end 

MONZoo.m:

 @implementation MONZoo { @private MONVisitorMap * visitorMap; } static NSString * Message(NSString * inMessage, id inInstance) { return [NSString stringWithFormat:@"Message: \"%@\" -- Instance: %@", inMessage, inInstance]; } // Here's where you implement a method for an animal: - (id)visitAnimal:(Animal *)p { return Message(@"What animal is this?", p); } - (id)visitDog:(Dog *)p { return Message(@"What's up, Dog!", p); } - (id)visitGreyhound:(Greyhound *)p { return Message(@"Ohhhhh a Greyhound!!", p); } - (id)visitTapir:(Tapir *)p { return Message(@"What does it cost to feed this?", p); } // Here's where you map methods to animals: + (MONVisitorMap *)newVisitorMap { MONVisitorMap * map = [MONVisitorMap new]; [map addEntryWithType:[Dog class] selector:@selector(visitDog:)]; [map addEntryWithType:[Greyhound class] selector:@selector(visitGreyhound:)]; [map addEntryWithType:[Tapir class] selector:@selector(visitTapir:)]; [map addEntryWithType:[Animal class] selector:@selector(visitAnimal:)]; /* omitting the Boxer (Dog) to demonstrate pseudo-overload */ return map; } - (id)init { self = [super init]; if (0 != self) { visitorMap = [[self class] newVisitorMap]; } return self; } - (NSString *)description { return [[super description] stringByAppendingString:[visitorMap description]]; } - (void)exhibit:(Animal *)inAnimal { NSLog(@"Visiting Exhibit: %@", [visitorMap visit:self parameter:inAnimal]); } @end 

现在尝试一下:

 int main(int argc, const char * argv[]) { @autoreleasepool { MONZoo * zoo = [MONZoo new]; NSLog(@"Hello, Zoo! -- %@", zoo); [zoo exhibit:[Dog new]]; [zoo exhibit:[Greyhound new]]; [zoo exhibit:[Squirrel new]]; [zoo exhibit:[Tapir new]]; [zoo exhibit:[Boxer new]]; } return 0; } 

这让我们的动物园之旅:

 2012-02-21 04:38:58.360 Visitor[2195:403] Hello, Zoo! -- {(  - Type: Dog - SEL: visitDog:,  - Type: Greyhound - SEL: visitGreyhound:,  - Type: Animal - SEL: visitAnimal:,  - Type: Tapir - SEL: visitTapir: )} 2012-02-21 04:38:58.363 Visitor[2195:403] Visiting Exhibit: Message: "What's up, Dog!" -- Instance:  2012-02-21 04:38:58.363 Visitor[2195:403] Visiting Exhibit: Message: "Ohhhhh a Greyhound!!" -- Instance:  2012-02-21 04:38:58.364 Visitor[2195:403] Visiting Exhibit: Message: "What animal is this?" -- Instance:  2012-02-21 04:38:58.364 Visitor[2195:403] Visiting Exhibit: Message: "What does it cost to feed this?" -- Instance:  2012-02-21 04:38:58.364 Visitor[2195:403] Visiting Exhibit: Message: "What's up, Dog!" -- Instance:  

笔记:

  • 自带错误检测;)
  • 请原谅快速写下来
  • 请原谅缺乏文档
  • 用ARC编译
  • 当然,您可以根据自己的需求进行变化。
  • 这可能不是模式的最真实forms,但您可以通过使用此程序添加一个或两个方法来轻松地引入它,如果这是您喜欢的方法。