Visitor Pattern in Objective-C

前端 未结 2 1333
南旧
南旧 2021-02-03 10:10

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\' implementati

相关标签:
2条回答
  • 2021-02-03 10:58

    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
    
    0 讨论(0)
  • 2021-02-03 11:02

    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.
    0 讨论(0)
提交回复
热议问题