[英]Visitor Pattern in Objective-C
I've been looking at the best way to implement the Visitor design pattern in Objective-C. 我一直在寻找在Objective-C中实现Visitor设计模式的最佳方法。 Since the language doesn't support method overloading, a 'traditional' implementation such as one might find in Java seems impossible. 由于该语言不支持方法重载,因此在Java中可能发现的“传统”实现似乎是不可能的。
In my current implementation, I have a Visitor protocol, a Visitor class, and several subclasses of that Visitor class, along with the various objects to visit. 在我当前的实现中,我有一个Visitor协议,一个Visitor类,以及该Visitor类的几个子类,以及要访问的各种对象。 Once a visited object accepts the Visitor, they call the visit method of the Visitor, passing themselves as an argument. 一旦访问对象接受访问者,他们就会调用访问者的访问方法,将自己作为参数传递。 The visit method takes an id, then type-casts it and calls visit方法接受一个id,然后键入它并调用它
[self performTasksOnObjectClass: (ObjectClass *)object];
as part of an if/elseif/else block. 作为if / elseif / else块的一部分。 These calls are them picked up by the relevant Visitor subclass and the Visitor performs whatever tasks it needs to on the object. 这些调用由相关的Visitor子类拾取,访问者执行对象所需的任何任务。
Is there a better way of implementing the Visitor pattern than this? 有没有比这更好的实现访客模式的方法? I dislike resorting to 'isKindOfClass' or 'isMemberOfClass' calls inside if/elseif/else blocks. 我不喜欢在if / elseif / else块中使用'isKindOfClass'或'isMemberOfClass'调用。 It just seems clunky and inelegant. 它看起来很笨重而且不够优雅。 Additionally, is it still 'worth' implementing a Visitor method in this way? 另外,以这种方式实现Visitor方法还值得吗? The visited objects can still remain ignorant of the Visitor, but there are other ways in which this can be achieved. 访问过的对象仍然可以不知道访问者,但还有其他方法可以实现这一点。
It has already been suggested that either delegation or class clusters might be more suitable alternatives to the Visitor pattern. 已经有人提出,委托或类集群可能是访客模式的更合适的替代方案。 I'd be interested to see what you all think! 我有兴趣看看你们都在想什么!
Edit: I actually had differently named methods being called in the subclass, I've made this clearer. 编辑:我实际上在子类中调用了不同的命名方法,我已经更清楚了。
You can use some introspection/reflection to make this a bit cleaner. 你可以使用一些内省/反射来使它更清洁。 You can't overload method names but you can avoid writing a switch statement like this: 您不能重载方法名称,但可以避免编写如下的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]];
}
Your actual performTasks methods would then be named as follows: 然后,您的实际performTasks方法将按如下方式命名:
- (void)performFooTasks:(Foo *)foo
{
//tasks for objects of class Foo
}
- (void)performBarTasks:(Bar *)bar
{
//tasks for objects of class Bar
}
etc...
Note: If you're using ARC, you'll get spurious warnings by creating selectors from strings in this way because it can't tell at compile time what the retain rules should be for the method parameters. 注意:如果您正在使用ARC,则会通过以这种方式从字符串创建选择器来获得虚假警告,因为它无法在编译时告诉方法参数应该保留规则。 You can silence these warnings using a #pragma, as follows: 您可以使用#pragma使这些警告静音,如下所示:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector withObject:object];
#pragma clang diagnostic pop
You could take the following approach to map selectors to objc types, and then let this implementation do the method lookups for the "dynamic overloading". 您可以采用以下方法将选择器映射到objc类型,然后让此实现执行“动态重载”的方法查找。 Such an implementation would remove the majority of the noise for your actual uses (see Demo -- 8 pages down): 这样的实现可以消除实际使用中的大部分噪声(参见Demo - 8页):
Our includes: 我们的包括:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
MONVisitorEntry associates a Class to a selector: MONVisitorEntry将类关联到选择器:
MONVisitorEntry.h: MONVisitorEntry.h:
@interface MONVisitorEntry : NSObject
+ (MONVisitorEntry *)newVisitorEntryWithType:(Class)inType selector:(SEL)inSelector;
- (id)visit:(id)target parameter:(id)parameter;
- (Class)type;
@end
MONVisitorEntry.m: 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 is a map of MONVisitorEntry
objects. MONVisitorMap是MONVisitorEntry
对象的地图。 this type has no type safety -- you should reintroduce it. 这种类型没有类型安全 - 你应该重新引入它。
MONVisitorMap.h: 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: 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
Create some test types (add some .m
files here): 创建一些测试类型(在这里添加一些.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
Create the visitor: 创建访问者:
MONZoo.h: MONZoo.h:
@interface MONZoo : NSObject
/* our abstract "visit" entry, which introduces type safety: */
- (void)exhibit:(Animal *)inAnimal;
@end
MONZoo.m: 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
Now try it out: 现在尝试一下:
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;
}
Which yields our trip to the zoo: 这让我们的动物园之旅:
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>
Notes: 笔记:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.