简体   繁体   English

Objective-C中的访客模式

[英]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>

Our basic types: 我们的基本类型:

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. MONVisitorMapMONVisitorEntry对象的地图。 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

Demo: 演示:

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: 笔记:

  • Bring your own error detection ;) 带您自己的错误检测;)
  • Excuse the quick write up 请原谅快速写下来
  • Excuse the lack of docs 请原谅缺乏文档
  • Compiled with ARC 用ARC编译
  • Of course, there are variations you can make to suit your needs. 当然,您可以根据自己的需求进行变化。
  • This may not be the truest form of the pattern, but you can easily introduce that by adding one or two methods using this program, if that is the approach you favor. 这可能不是模式的最真实形式,但您可以通过使用此程序添加一个或两个方法来轻松引入,如果这是您喜欢的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM