我一直在寻找在Objective-C中实现访问者设计模式的最佳方式。由于该语言不支持方法重载,因此在Java中可能发现的“传统”实现似乎是不可能的。
在我当前的实现中,我有一个Visitor协议,一个Visitor类,以及该Visitor类的几个子类,以及要访问的各种对象。一旦访问对象接受访问者,他们就会调用访问者的访问方法,将自己作为参数传递。 visit方法接受一个id,然后键入它并调用
[self performTasksOnObjectClass: (ObjectClass *)object];
作为if / elseif / else块的一部分。这些调用由相关的Visitor子类拾取,访问者执行对象所需的任何任务。
有没有比这更好的实现访问者模式的方法?我不喜欢在if / elseif / else块中使用'isKindOfClass'或'isMemberOfClass'调用。它看起来很笨重而且不够优雅。另外,以这种方式实现Visitor方法还值得吗?访问过的对象仍然可以不知道访问者,但还有其他方法可以实现这一点。
已经有人建议,委托或类集群可能是访客模式的更合适的替代方案。我有兴趣看看你们都在想什么!
编辑:我实际上在子类中调用了不同名称的方法,我已经更清楚了。
答案 0 :(得分:12)
你可以使用一些内省/反射来使它更清洁。您不能重载方法名称,但可以避免编写如下的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
答案 1 :(得分:4)
您可以采用以下方法将选择器映射到objc类型,然后让此实现执行“动态重载”的方法查找。这样的实现可以消除实际使用中的大部分噪声(参见Demo - 8页):
我们的包括:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
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
MONVisitorMap 是MONVisitorEntry
个对象的地图。这种类型没有类型安全 - 你应该重新引入它。
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! -- <MONZoo: 0x10440ed80><MONVisitorMap: 0x1044143f0>{(
<MONVisitorEntry: 0x104414e00> - Type: Dog - SEL: visitDog:,
<MONVisitorEntry: 0x104410840> - Type: Greyhound - SEL: visitGreyhound:,
<MONVisitorEntry: 0x104415150> - Type: Animal - SEL: visitAnimal:,
<MONVisitorEntry: 0x104415130> - Type: Tapir - SEL: visitTapir:
)}
2012-02-21 04:38:58.363 Visitor[2195:403] Visiting Exhibit: Message: "What's up, Dog!" -- Instance: <Dog: 0x7f9a29d00120>
2012-02-21 04:38:58.363 Visitor[2195:403] Visiting Exhibit: Message: "Ohhhhh a Greyhound!!" -- Instance: <Greyhound: 0x7f9a29e002c0>
2012-02-21 04:38:58.364 Visitor[2195:403] Visiting Exhibit: Message: "What animal is this?" -- Instance: <Squirrel: 0x104416470>
2012-02-21 04:38:58.364 Visitor[2195:403] Visiting Exhibit: Message: "What does it cost to feed this?" -- Instance: <Tapir: 0x7f9a29d00120>
2012-02-21 04:38:58.364 Visitor[2195:403] Visiting Exhibit: Message: "What's up, Dog!" -- Instance: <Boxer: 0x1044140a0>
备注:强>