Подтвердить что ты не робот

Шаблон посетителя в Objective-C

Я искал лучший способ реализовать шаблон дизайна Visitor в Objective-C. Поскольку язык не поддерживает перегрузку метода, "традиционная" реализация, такая как, например, на Java, кажется невозможной.

В моей текущей реализации у меня есть протокол Visitor, класс Visitor и несколько подклассов этого класса Visitor, а также различные объекты для посещения. Когда посетивший объект принимает посетителя, он вызывает метод посещения посетителя, передавая себя в качестве аргумента. Метод посещения принимает идентификатор, затем вводит его тип и вызывает

[self performTasksOnObjectClass: (ObjectClass *)object];

как часть блока if/elseif/else. Эти вызовы выбираются соответствующим подклассом Visitor, и посетитель выполняет любые задачи, которые ему необходимы для объекта.

Есть ли лучший способ реализации шаблона посетителя, чем это? Мне не нравится прибегать к вызовам isKindOfClass или isMemberOfClass внутри if/elseif/else блоков. Это просто кажется неуклюжим и нелепым. Кроме того, все-таки "стоит" реализовать метод "Посетитель" таким образом? Посещенные объекты все еще могут оставаться не знакомыми с посетителем, но есть и другие способы, которыми это может быть достигнуто.

Уже было высказано предположение, что либо делегирование, либо кластер классов могут быть более подходящими альтернативами шаблону посетителя. Мне было бы интересно посмотреть, что вы все думаете!

Изменить: у меня на самом деле были разные методы, называемые в подклассе, я сделал это более ясным.

4b9b3361

Ответ 1

Вы можете использовать некоторую интроспекцию/отражение, чтобы сделать это немного чище. Вы не можете перегружать имена методов, но вы можете избежать написания оператора 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]];
}

Ваши фактические методы executeTasks затем будут называться следующим образом:

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

Ответ 2

Вы можете использовать следующий подход для выбора селекторов для типов 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

MONVisitorMap - это карта объектов MONVisitorEntry. этот тип не имеет безопасности типа - вы должны его повторно ввести.

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 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 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 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 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 up, Dog!" -- Instance: <Boxer: 0x1044140a0>

Примечания:

  • Принесите собственное обнаружение ошибок;)
  • Извините краткую запись
  • Извините за отсутствие документов
  • Скомпилировано с ARC
  • Конечно, есть варианты, которые вы можете сделать в соответствии с вашими потребностями.
  • Это может быть не самая верная форма шаблона, но вы можете легко ввести это, добавив один или два метода, используя эту программу, если это ваш подход.