вівторок, 22 серпня 2017 р.

Adapter

Тяжело найти более красочно описание паттерна Адаптер, чем пример из жизни каждого, кто покупал технику из США. Розетка! Вот почему не сделать одинаковую розетку всюду? Но нет, в США розетка с квадратными дырками, в Европе с круглыми, а в некоторых странах вообще треугольные. Следовательно – потому вилки на зарядный устройствах, и других устройствах питания тоже различные.

Представьте, что Вы едете в коммандировку в США. У Вас есть, допустим, ноутбук купленный в Европе – следовательно вилка на проводе от блока питания имеет круглые окончания. Что делать? Покупать зарядку для американского типа розетки? А когда вы вернетесь домой – она будет лежать у Вас мертвым грузом?

Потому, вероятнее всего, Вы приобретете один из адаптеров, которые надеваются на вилку, и которая позволяет Вам использовать старую зарядку и заряжаться от совершенно другой розетки.

Так и с Адаптером – он конвертит интерфейс класса – на такой, который ожидается.

Сам паттерн состоит из трех частей: Цели (target), Адаптера (adapter), и адаптируемого (adaptee).

В нашей с вами проблеме:

  1. Target – ноутбук со старой зарядкой.
  2. Adapter – переходник.
  3. Adaptee – розетка с квадртаными дырками.

Имплементации паттерна Адаптер в Objective-C может быть 2 (вероятно даже больше, но я вижу две):

Итак, первая – это простенькая имплементация. Пускай у нас есть объект ASBird, который реализует протокол ASBirdProtocol:

@protocol ASBirdProtocol <NSObject>

- (void)sing;

- (void)fly;

@end


@interface ASBird : NSObject <ASBirdProtocol>

@end
@implementation ASBird

#pragma mark - ASBirdProtocol

- (void)sing {
    NSLog(@"Tew-tew-tew");
}

- (void)fly {
    NSLog(@"OMG! I am flying!");
}

@end

И пускай у нас есть объект ASRaven, у которого есть свой интерфейс:

@interface ASRaven : NSObject

- (void)flySearchAndDestroy;

- (void)voice;

@end
@implementation ASRaven

- (void)flySearchAndDestroy {
    NSLog(@"I am flying and seak for killing!");
}

- (void)voice {
    NSLog(@"Kaaaar-kaaaaar-kaaaaaaar!");
}

@end

Чтобы использовать ворону в методах которые ждут птицу:) стоит создать так называемый адаптер:

@class ASRaven;

@interface ASRavenAdapter : NSObject <ASBirdProtocol>

- (instancetype)initWithRaven:(ASRaven *)raven;

@end

И сама реализация. Как видим мы завернули ворона в обертку протоколом ASBirdProtocol.

@interface ASRavenAdapter ()

@property (nonatomic, strong) ASRaven *raven;

@end

@implementation ASRavenAdapter

- (instancetype)initWithRaven:(ASRaven *)raven {
    self = [super init];
    
    if (self) {
        _raven = raven;
    }
    
    return self;
}

#pragma mark - ASBirdProtocol

- (void)sing {
    [self.raven voice];
}

- (void)fly {
    [self.raven flySearchAndDestroy];
}

@end

Ну и конечно же тест:

- (void)makeTheBirdTest:(id<ASBirdProtocol>)bird {
    [bird fly];
    [bird sing];
}

И тестовый код:

    ASBird *bird = [ASBird new];
    ASRaven *raven = [ASRaven new];
    
    ASRavenAdapter *ravenAdapter = [[ASRavenAdapter alloc] initWithRaven:raven];
    
    [self makeTheBirdTest:bird];
    [self makeTheBirdTest:ravenAdapter];

Результат можно легко увидеть в логе:

2017-08-22 22:08:43.307 PatternsObjC[2072:94182] OMG! I am flying!
2017-08-22 22:08:43.307 PatternsObjC[2072:94182] Tew-tew-tew
2017-08-22 22:08:43.307 PatternsObjC[2072:94182] I am flying and seak for killing!
2017-08-22 22:08:43.307 PatternsObjC[2072:94182] Kaaaar-kaaaaar-kaaaaaaar!

Теперь более сложная реализация, которая все еще зависит от протоколов, но уже использует делегацию. Вернемся к нашему несчастному ноутбуку и зарядке: Допустим у нас есть базовый класс ASCharger:

@interface ASCharger : NSObject

- (void)charge;

@end
@implementation ASCharger

- (void)charge {
    NSLog(@"C'mon I am charging");
}

@end

И есть протокол для европейской зарядки:

@class ASCharger;

@protocol ASEuropeanNotebookChargerDelegate <NSObject>

- (void)chargetNotebookRoundHoles:(ASCharger *)charger;

@end

Если сделать просто реализацию – то получится тоже самое, что и в прошлом примере:) Потому, давайте добавим делегат:

@interface ASEuropeanNotebookCharger : ASCharger <ASEuropeanNotebookChargerDelegate>

@property (nonatomic, weak) id<ASEuropeanNotebookChargerDelegate> delegate;

@end
@implementation ASEuropeanNotebookCharger

- (instancetype)init {
    self = [super init];
    
    if (self) {
        _delegate = self;
    }
    
    return self;
}

#pragma mark - ASCharger

- (void)charge {
    [self.delegate chargetNotebookRoundHoles:self];
    [super charge];
}

#pragma mark - ASEuropeanNotebookChargerDelegate

- (void)chargetNotebookRoundHoles:(ASCharger *)charger {
    NSLog(@"Charging with 220 and round holes!");
}

@end

Как видим, у нашего класса есть свойство которое реализует тип EuropeanNotebookChargerDelegate. Так как, наш класс этот протокол реализует, он может свойсту присвоить себя, потому когда происходит вызов:

    [self.delegate chargetNotebookRoundHoles:self];

просто вызывается свой же метод. Вы увидите дальше, для чего это сделано.

Теперь, давайте глянем что ж за зверь такой – американская зарядка:

@interface ASUSANotebookCharger : ASCharger

- (void)chargeNotebookRectHoles:(ASCharger *)charger;

@end
@implementation ASUSANotebookCharger

- (void)chargeNotebookRectHoles:(ASCharger *)charger {
    NSLog(@"Charge Notebook Rect Holes");
}

@end

Как видим, в американской зарядке совсем другой метод и мировозрение. Давайте, создадим адаптер для зарядки:

@class ASUSANotebookCharger;

@interface ASUSANotebookEuropeanAdapter : ASCharger <ASEuropeanNotebookChargerDelegate>

- (instancetype)initWithUSANotebookCharger:(ASUSANotebookCharger *)charger;

@end
@interface ASUSANotebookEuropeanAdapter ()

@property (nonatomic, strong) ASUSANotebookCharger *usaCharger;

@end

@implementation ASUSANotebookEuropeanAdapter

- (instancetype)initWithUSANotebookCharger:(ASUSANotebookCharger *)charger {
    self = [super init];
    
    if (self) {
        _usaCharger = charger;
    }
    
    return self;
}

#pragma mark - ASCharger

- (void)charge {
    ASEuropeanNotebookCharger *euroCharge = [ASEuropeanNotebookCharger new];
    euroCharge.delegate = self;
    [euroCharge charge];
}

#pragma mark - ASEuropeanNotebookChargerDelegate

- (void)chargetNotebookRoundHoles:(ASCharger *)charger {
    [self.usaCharger chargeNotebookRectHoles:charger];
}

@end

Как видим, наш адптер реализует интерфейс ASEuropeanNotebookChargerDelegate и его метод chargetNotebookRoundHoles. Потому, когда вызывается метод charge – на самом деле создается тип европейской зарядки, ей присвается наш адаптер как делегат, и вызывается ее метод charge. Так как делегатом присвоен наш адаптер, при вызове метода chargetNotebookRoundHoles, будет вызыван этот метод нашего адаптера, который в свою очередь вызывает метод зардяки США:)

Давайте посмотрим тест код и вывод лога:

- (void)makeTheNotebookCharge:(ASCharger *)charger {
    [charger charge];
}

И тестовый код:

    ASEuropeanNotebookCharger *euroCharger = [ASEuropeanNotebookCharger new];
    [self makeTheNotebookCharge:euroCharger];
    
    ASUSANotebookCharger *useCharger = [ASUSANotebookCharger new];
    ASUSANotebookEuropeanAdapter *adapter = [[ASUSANotebookEuropeanAdapter alloc] initWithUSANotebookCharger:useCharger];
    [self makeTheNotebookCharge:adapter];

Лог нам выведет:

2017-08-22 22:51:17.450 PatternsObjC[2506:178392] Charging with 220 and round holes!
2017-08-22 22:51:17.451 PatternsObjC[2506:178392] C'mon I am charging
2017-08-22 22:51:17.451 PatternsObjC[2506:178392] Charge Notebook Rect Holes
2017-08-22 22:51:17.451 PatternsObjC[2506:178392] C'mon I am charging

Apple реализует Adapter своеобразно (примерно как и требование Еврокомиссии перейти на MicroUSB). Они это делают с помощью протоколов. Вы, можете быть, знакомы с протоколами UITableViewDelegate, UIScrollViewDelegate, NSCoding, NSCopying. К примеру, протокол NSCopying предоставляет любому классу стандартный метод copy.

Немає коментарів:

Дописати коментар

HyperComments for Blogger

comments powered by HyperComments