Objective-C 中的访问者模式

发布于 2025-01-07 05:13:28 字数 655 浏览 0 评论 0原文

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

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

[self performTasksOnObjectClass: (ObjectClass *)object];

作为 if/elseif/else 块的一部分进行调用。这些调用由相关的 Visitor 子类接收,并且 Visitor 在对象上执行它需要的任何任务。

还有比这更好的实现访客模式的方法吗?我不喜欢在 if/elseif/else 块内调用“isKindOfClass”或“isMemberOfClass”。它看起来笨重且不优雅。此外,以这种方式实现 Visitor 方法仍然“值得”吗?被访问的对象仍然可以保持对访问者一无所知,但是还有其他方法可以实现这一点。

已经有人建议委托或类集群可能是访问者模式更合适的替代方案。我很想知道大家的想法!

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

I've been looking at the best way to implement the Visitor design pattern in Objective-C. Since the language doesn't support method overloading, a 'traditional' implementation such as one might find in Java seems impossible.

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

[self performTasksOnObjectClass: (ObjectClass *)object];

as part of an if/elseif/else block. These calls are them picked up by the relevant Visitor subclass and the Visitor performs whatever tasks it needs to on the object.

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. It just seems clunky and inelegant. Additionally, is it still 'worth' implementing a Visitor method in this way? 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一念一轮回 2025-01-14 05:13:28

您可以使用一些内省/反思来使其更清晰一些。您不能重载方法名称,但可以避免编写如下所示的 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]];
}

您的实际执行任务方法将命名如下:

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

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:

- (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:

- (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. You can silence these warnings using a #pragma, as follows:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector:selector withObject:object];
#pragma clang diagnostic pop
不再见 2025-01-14 05:13:28

您可以采用以下方法将选择器映射到 objc 类型,然后让此实现执行“动态重载”的方法查找。这样的实现将消除您实际使用中的大部分噪音(请参见演示 - 向下 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 编译
  • 当然,您可以进行一些变体来满足您的需求。
  • 这可能不是该模式最真实的形式,但如果您喜欢这种方法,您可以通过使用该程序添加一两个方法来轻松引入该模式。

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". Such an implementation would remove the majority of the noise for your actual uses (see Demo -- 8 pages down):

Our includes:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

Our basic types:

MONVisitorEntry associates a Class to a selector:

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 is a map of MONVisitorEntry objects. this type has no type safety -- you should reintroduce it.

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

Demo:

Create some test types (add some .m files here):

@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:

@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

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
  • 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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文