繁体   English   中英

Objective-C中的访客模式

[英]Visitor Pattern in 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方法还值得吗? 访问过的对象仍然可以不知道访问者,但还有其他方法可以实现这一点。

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

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

你可以使用一些内省/反射来使它更清洁。 您不能重载方法名称,但可以避免编写如下的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类型,然后让此实现执行“动态重载”的方法查找。 这样的实现可以消除实际使用中的大部分噪声(参见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

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! -- <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>

笔记:

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

暂无
暂无

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

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