Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Тактика (Перевод с английского главы «Strategy» из книги «Pro Objective-C Design Patterns for iOS» Carlo Chung)

Anna | 2.07.2014 | нет комментариев

Помните ли вы, когда вы в конечный раз начиняли блок кода большинством различных алгорифмов и применяли спагетти из условий if-else / switch-case, Дабы определить, какой именно из них применять. Алгорифмы могли представлять собой комплект функций/методов схожих классов, которые решают аналогичные задачи. К примеру, у вас есть процедура для проверки входных данных. Сами данные могут быть всяких типов (скажем, CGFloatNSStringNSInteger и другое). Всякий из типов данных требует разных алгорифмов проверки. Если бы вы могли инкапсулировать всякий алгорифм в виде объекта, то дозволено было бы не применять группу операторов if-else / switch-case для проверки данных и определения, какой из алгорифмов необходим.

В объектно-ориентированном программировании вы можете выделить связанные алгорифмы в разные классы стратегий. Паттерн проектирования, тот, что используется в таких случаях, именуется Тактика. В этой главе мы обсудим доктрины и ключевые вероятности паттерна Тактика. Мы также спроектируем и реализуем несколько классов для проверки данных в виде стратегий для валидации ввода объекта текстового поляUITextField позднее в этой главе.

Что собой представляет паттерн Тактика?

Одну из ключевых ролей в этом паттерне играет класс стратегии, тот, что объявляет всеобщий интерфейс для всех поддерживаемых алгорифмов. Есть также определенные классы стратегий, которые реализуют алгорифмы, применяя интерфейс стратегии. Объект контекста конфигурируется с поддержкой экземпляра определенного объекта стратегии. Объект контекста использует интерфейс стратегии для вызова алгорифма, определенного в определенном классе стратегии. Их отношения проиллюстрированы на диаграмме классов на рисунке 19–1.

image
Рисунок 19–1. Конструкция классов паттерна Тактика

Группа либо иерархия связанных алгорифмов в форме классов ConcreteStrategy (A, B и C) разделяют всеобщий algorithmInterface, следственно Context может получить доступ к различным вариантам алгорифмов с поддержкой одного и того же интерфейса.

Примечание. Паттерн Тактика: определяет семейство алгорифмов, инкапсулирует всякий из них и делает их взаимозаменяемыми. Тактика разрешает алгорифмам меняться самостоятельно от заказчиков, которые их применяют.*

Начальное определение, данное в книге «Паттерны проектирования» GoF (Addison-Wesley, 1994).

Экземпляр Context может быть сконфигурирован с поддержкой разных объектов ConcreteStrategy во время выполнения. Дозволено это рассматривать как метаморфоза «внутренностей» объекта Context, так как метаморфозы происходят изнутри. Декораторы (смотри главу 16, паттерн Декоратор и мою предыдущую статью), в противовес, изменяют «шкуру» объекта, так как модификации пристыковываются извне. Пожалуйста, обращайтесь к разделу «Метаморфоза «шкуры» объекта в сопоставлении с изменением «внутренностей»» в главе 16 (предыдущая статья) за больше детальной информацией о отличиях.

Паттерн Тактика в Модель-Вид-Контроллер

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

Когда целесообразно применение паттерна Тактика?

Применение этого паттерна уместно в следующих случаях:

  • В логике класса применяется уйма условных операторов для выбора надобного поведения. Вы можете переместить воображаемый код в обособленный класс стратегии.
  • Вам необходимы различные варианты алгорифма.
  • Вам бы не хотелось выставлять наружу трудные и тесно специфичные конструкции данных (заказчикам).

Использование стратегий проверки данных на примере класса UITextField

Давайте сделаем примитивный пример реализации паттерна Тактика в приложении. Представим, что нам необходим некоторый объект UITextField в нашем приложении, тот, что принимает ввод пользователя; мы будем применять итоги ввода в нашем приложении позднее. У нас есть поле текстового ввода, которое принимает только буквы, то есть a–z либо A–Z, а также у нас есть поле, которое принимает только числовые данные, то есть 0–9. Дабы удостовериться, что ввод в полях правилен, всякому из них необходимо иметь какую-то процедуру проверки данных на месте, запускаемую позже того, как пользователь заканчивает редактирование.

Мы можем разместить нужную проверку данных в способ объекта делегата UITextField,textFieldDidEndEditing:. Экземпляр UITextField вызывает данный способ всякий раз, когда теряет фокус. В этом способе мы можем удостовериться в том, что в цифровом поле введены только цифры, а в буквенном — только буквы. Данный способ принимает на входе ссылку на нынешний объект поля ввода (в виде параметра textField), но какой именно это из 2-х объектов?

Без паттерна Страгии мы бы пришли к коду, сходственному показанному в листинге 19–1.

Листинг 19–1. Нормальный сценарий проверки содержимого UITextField в способе делегата textFieldDidEndEditing

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    if (textField == numericTextField)
    {
        // проверяем [textField text] и убеждаемся,
        // что значение цифровое
    }
    else if (textField == alphaTextField)
    {
        // проверяем [textField text] и убеждаемся,
        // что значение содержит только буквы
    }
}

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

Совет: Если в вашем коде много условных операторов, то это может обозначать, что их необходимо отрефакторить и выделить в отдельные объекты Стратегии.

Сейчас наша цель — взяться за данный код проверки и раскидать его по разным классам Стратегий, Дабы дозволено было его вторично применять в делегате и других способах. Всякий из наших классов берет строку из поля ввода, после этого проверяет его, базируясь на нужной стратегии, и в конце возвращает значение типа BOOL и экземпляр NSError, если проверка провалилась. Возвращенный объект NSError поможет определить, из-за чего именно проверка не была удачна. От того что проверка и цифрового, и буквенного ввода связаны друг с ином (у них идентичные типа на входе и выходе), их дозволено объединить одним интерфейсом. Наш комплект классов показан на диаграмме классов на рисунке 19–2.

image
Рисунок 19–2. Диаграмма классов показывает отношения между CustomTextField и связанными с ним стратегиями

Мы объявим данный интерфейс не в виде протокола, а в виде абстрактного базового класса. Отвлеченный базовый класс больше комфортен в данном случае, потому что проще рефакторить всеобщее для всех определенных классов стратегий поведение. Наш отвлеченный базовый класс будет выглядеть, как показано в листинге 19–2.

Листинг 19–2. Объявление класса InputValidator в InputValidator.h

@interface InputValidator : NSObject
{
}
// Заглушка для всякий стратегии проверки
- (BOOL) validateInput:(UITextField *)input error:(NSError **) error;

@end

Способ validateInput: error: принимает ссылку на UITextField в качестве входного параметра, следственно он может проверить все, что находится в поле ввода, и возвращает значение BOOL как итог проверки. Способ также принимает ссылку на указатель на NSError. Когда случилась какая-то оплошность (то есть способ не сумел проверить правильность ввода), способ сделает экземпляр NSError и присвоит его указателю, следственно, в каком бы контексте не применялся класс проверки, неизменно есть вероятность получить больше подробную информацию об ошибке из этого объекта.

Реализация этого способа по умолчанию только лишь устанавливает указатель на ошибку в nil и возвращаетNO, как показано в листинге 19–3.

Листинг 19–3. Реализация по умолчанию класса InputValidator в InputValidator.m

#import "InputValidator.h"

@implementation InputValidator

// Заглушка для всякий стратегии проверки
- (BOOL) validateInput:(UITextField *)input error:(NSError **) error
{
    if (error)
    {
        *error = nil;
    }
    return NO;
}
@end

Отчего мы не применяли NSString в качестве входного параметра? В этом случае всякое действие внутри объекта стратегии будет односторонним. Это значит, что валидатор легко сделает проверку и вернет итог без модификации начального значения. С входным параметром типа UITextField мы можем объединить два подхода. Наши объекты проверки будут иметь вероятность изменить начальное значение текстового поля (скажем, удалив неправильные символы) либо легко просмотреть значение без его метаморфозы.

Иной вопрос – отчего бы нам легко не кинуть исключение NSException, если проверка провалилась? Это потому, что выброс собственного исключения и перехват его в блоке try-catch во фреймворке Cocoa Touch является дюже ресурсоемкой операцией и не рекомендуется (но try-catch системные исключения – это вовсе другое дело). Касательно дешевле воротить объект NSError, что рекомендовано в Cocoa Touch Developer’s Guide. Если мы посмотрим на документацию фреймворка Cocoa Touch, мы подметим, что есть уйма API, которые возвращают экземпляр NSError, когда появляется какая-то ненормальная обстановка. Общеизвестный пример – это один из способов NSFileManager(BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error. Если появляется оплошность, когда NSFileManagerпытается переместить файл из одного места в другое, он сделает новейший экземпляр NSError, тот, что описывает задачу. Дерзкий способ может применять информацию, содержащуюся в возвращенном объектеNSError для последующей обработки ошибок. Таким образом, цель объекта NSError в нашем способе – это обеспечение информации об отказе в работе.

Сейчас мы определили, как должен вести себя отличный класс проверки ввода. Теперь мы можем заняться созданием подлинного проверяющего. Давайте сделаем вначале тот, что для ввода чисел, как показано в листинге 19–4.

Листинг 19–4. Объявление класса NumericInputValidator в NumericInputValidator.h

#import "InputValidator.h"

@interface NumericInputValidator : InputValidator
{
}

// Способ проверки, тот, что убеждается, что ввод содержит только
// цифры, то есть 0-9
- (BOOL) validateInput:(UITextField *)input error:(NSError **) error;

@end

NumericInputValidator наследует от абстрактного базового класса InputValidator и переопределяет его способ validateInput: error:. Мы объявляем способ снова, Дабы подчеркнуть, что данный подкласс реализует либо переопределяет его. Это не непременно, но является отличной практикой.

Реализация способа дана в листинге 19–5.

Листинг 19–5. Реализация класса NumericInputValidator в NumericInputValidator.m

#import "NumericInputValidator.h"

@implementation NumericInputValidator

- (BOOL) validateInput:(UITextField *)input error:(NSError**) error
{
    NSError *regError = nil;
    NSRegularExpression *regex = [NSRegularExpression
                                                          regularExpressionWithPattern:@"^[0-9]*$"
                                                         options:NSRegularExpressionAnchorsMatchLines
                                                         error:&regError];
    NSUInteger numberOfMatches = [regex
    numberOfMatchesInString:[input text]
    options:NSMatchingAnchored
    range:NSMakeRange(0, [[input text] length])];

    // если нет совпадений, 
    // то возвращаем ошибку и NO
    if (numberOfMatches == 0)
    {
         if (error != nil)
         {
             NSString *description = NSLocalizedString(@"Input Validation Failed", @"");
             NSString *reason = NSLocalizedString(@"The input can contain only numerical
             values", @"");
             NSArray *objArray = [NSArray arrayWithObjects:description, reason, nil];
             NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey,
             NSLocalizedFailureReasonErrorKey, nil];
             NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objArray
             forKeys:keyArray];
             *error = [NSError errorWithDomain:InputValidationErrorDomain
             code:1001
             userInfo:userInfo];
        }
        return NO;
    }
    return YES;
}
@end

Реализация способа validateInput:error: фокусируется основным образом на 2-х аспектах:

  1. Он проверяет число совпадений численных данных в поле ввода с заранее сделанным объектомNSRegularExpression. Регулярное выражение, которое мы применяли, — это «^[0–9]*$». Он обозначает, что с начала каждой строки (обозначено «^») и конца (обозначено «$»), должно быть 0 и больше символов (обозначено «*») из комплекта, тот, что содержит только цифры (обозначено «[0–9]»).
  2. Если совпадений нет вообще, то он создает новейший объект NSError, тот, что содержит сообщение «The input can contain only numerical values» и присваивает его входному указателю на NSError. После этого он наконец возвращает значение типа BOOL, указывающее на триумф либо неуспех операции. Оплошность ассоциирована с специальным кодом 1001 и специальным значением домена ошибки, определенным в заголовочном файле класса InputValidator приблизительно так, как показано ниже:
    static NSString * const InputValidationErrorDomain = @"InputValidationErrorDomain";
    

Брат класса NumericInputValidator, тот, что проверяет присутствие только букв во вводе, называемыйAlphaInputValidator, содержит схожий алгорифм для проверки контента поля ввода. AlphaInputValidatorпереопределяет тот же способ, что и NumericInputValidator. Видимо, что данный алгорифм проверяет, что входная строка содержит только буквы, как показано в листинге 19–6.

Листинг 19–6. Реализация класса AlphaInputValidator в AlphaInputValidator.m

#import "AlphaInputValidator.h"

@implementation AlphaInputValidator

- (BOOL) validateInput:(UITextField *)input error:(NSError**) error
{
    NSError *regError = nil;
    NSRegularExpression *regex = [NSRegularExpression
    regularExpressionWithPattern:@"^[a-zA-Z]*$"
    options:NSRegularExpressionAnchorsMatchLines
    error:&regError];
    NSUInteger numberOfMatches = [regex
    numberOfMatchesInString:[input text]
    options:NSMatchingAnchored
    range:NSMakeRange(0, [[input text] length])];
    // если нет совпадений, 
    // то возвращаем ошибку и NO
    if (numberOfMatches == 0)
    {
        if (error != nil)
        {
            NSString *description = NSLocalizedString(@"Input Validation Failed", @"");
            NSString *reason = NSLocalizedString(@"The input can contain only letters", @"");
            NSArray *objArray = [NSArray arrayWithObjects:description, reason, nil];
            NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey,
            NSLocalizedFailureReasonErrorKey, nil];
            NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objArray
            forKeys:keyArray];
            *error = [NSError errorWithDomain:InputValidationErrorDomain
            code:1002
            userInfo:userInfo];
        }
        return NO;
    }
    return YES;
}
@end

Наш класс AlphaInputValidator также является разновидностью InputValidator и реализует способvalidateInput:. Он имеет схожие на брата, NumericInputValidator, конструкцию кода и алгорифм, за исключением того, что использует другое регулярное выражение в объекте NSRegularExpression, и код ошибки и сообщение специфичны для буквенной проверки. Регулярное выражение, которое мы используем для проверки букв, — «^[a-zA-Z]*$». Оно схоже на выражение для его собрата по числовой проверке, помимо того, что комплект возможных символов содержит буквы и нижнего, и верхнего регистра. Как мы видим, в обеих версиях много дублирующегося кода. У обоих алгорифмов схожая конструкция; вы можете отрефакторить конструкцию в шаблонный способ (смотри главу 18) в отвлеченный базовый класс. Определенные подклассы InputValidator могут переопределить простые операции, определенные вInputValidator, Дабы воротить неповторимую информацию шаблонному алгорифму – скажем, регулярное выражение и разные признаки конструирования объекта NSError и т. д. Я оставлю вам это в качестве упражнения.

Теперь у нас теснее есть классы проверки, готовые к применению в приложении. Впрочем UITextField не знает о них, следственно нам необходима собственная версия UITextField, которая все понимает. Мы сделаем подкласс UITextField, тот, что содержит ссылку на InputValidator и способ validate, это показано в листинге 19–7.

Листинг 19–7. Объявление класса CustomTextField в CustomTextField.h

#import "InputValidator.h"

@interface CustomTextField : UITextField
{
    @private
    InputValidator *inputValidator_;
}

@property (nonatomic, retain) IBOutlet InputValidator *inputValidator;

- (BOOL) validate;

@end

CustomTextField содержит качество, которое удерживает (retain) ссылку на InputValidator. Когда вызывается его способ validate, он использует ссылку на InputValidator, Дабы начать проверку. Мы можем увидеть это в реализации, показанной в листинге 19–8.

Листинг 19–8. Реализация класса CustomTextField в CustomTextField.m

#import "CustomTextField.h"

@implementation CustomTextField

@synthesize inputValidator=inputValidator_;

- (BOOL) validate
{
    NSError *error = nil;
    BOOL validationResult = [inputValidator_ validateInput:self error:&error];
    if (!validationResult)
    {
        UIAlertView *alertView = [[UIAlertView alloc]
        initWithTitle:[error localizedDescription]
        message:[error localizedFailureReason]
        delegate:nil
        cancelButtonTitle:NSLocalizedString(@"OK", @"")
        otherButtonTitles:nil];
        [alertView show];
        [alertView release];
    }
    return validationResult;
}

- (void) dealloc
{
    [inputValidator_ release];
    [super dealloc];
}

@end

В способе validate посылается сообщение [inputValidator_ validateInput:self error:&error] ссылкеinputValidator_. Красота паттерна в том, что CustomTextField-у не необходимо знать, какого типаInputValidator он использует либо какие-либо детали алгорифма. Следственно если в грядущем мы добавим какой-то новейший InputValidator, объект CustomTextField будет применять новейший InputValidator так же.

Выходит, все подготовительные работы сделаны. Возможен, заказчиком является UIViewController, тот, что реализует протокол UITextFieldDelegate и содержит два IBOutlets типа CustomTextField, как показано в листинге 19–9.

Листинг 19–9. Объявление класса StrategyViewController в StrategyViewController.h

#import "NumericInputValidator.h"
#import "AlphaInputValidator.h"
#import "CustomTextField.h"

@interface StrategyViewController : UIViewController <UITextFieldDelegate>
{
    @private
    CustomTextField *numericTextField_;
    CustomTextField *alphaTextField_;
}

@property (nonatomic, retain) IBOutlet CustomTextField *numericTextField;
@property (nonatomic, retain) IBOutlet CustomTextField *alphaTextField;

@end

Мы решили дозволить контроллеру реализовывать способ делегата (void)textFieldDidEndEditing:(UITextField *)textField и разместить проверку туда. Данный способ будет вызываться всякий раз, когда значение в поле ввода будет изменяться, а фокус будет утрачен. Когда пользователь завершит ввод, наш класс CustomTextField вызовет данный способ делегата, проиллюстрировано в листинге 19–10.

Листинг 19–10. Клиентский код, определенный в способе делегата textFieldDidEndEditing:, тот, что проверяет экземпляр CustomTextField с поддержкой объекта стратегии (InputValidator)


@implementation StrategyViewController

@synthesize numericTextField, alphaTextField;

// ...
// другие способы вьюконтроллера
// ...
#pragma mark -
#pragma mark UITextFieldDelegate methods

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    if ([textField isKindOfClass:[CustomTextField class]])
    {
        [(CustomTextField*)textField validate];
    }
}
@end

При вызове textFieldDidEndEditing:, когда редактирование в одном из полей завершено, способ проверяет, что объект textField принадлежит к классу CustomTextField. Если так, то он посылает сообщение validateему для запуска процесса проверки введенного текста. Как мы можем видеть, нам огромнее не необходимы эти условные операторы. Взамен этого у нас есть значительно больше примитивный код для тех же целей. За исключением дополнительной проверки того, что объект textField является типом CustomTextField, огромнее ничего трудного нет.

Но подождите минуту. Кое-что выглядит не дюже отлично. Как бы мы могли присвоить правильные экземплярыInputValidator numericTextField_ и alphaTextField_, определенным в StrategyViewController? Оба поля ввода объявлены как IBOutlet в листинге 19–9. Мы можем подцепить их во вьюконтроллере Interface Builder через IBOutlet-ы, как мы делаем с другими кнопками и прочим. Подобно в объявлении классаCustomTextField в листинге 19–7, его качество inputValidator также IBOutlet, что обозначает, что мы можем присвоить экземпляр InputValidator объекту *TextField тоже в Interface Builder. Таким образом, все может быть сконструировано посредством применения ссылочных соединений Interface Builder, если вы объявите определенные свойства класса как IBOutlet. Для больше детального обсуждения, как применять кастомные объекты Interface Builder, обращайтесь к «Применение CoordinatingController в Interface Builder» в главе 11, где говорится о паттерне Медиатор.

Завершение

В этой главе мы обсудили доктрины паттерна Тактика и как дозволено задействовать данный паттерн для применения заказчиками разных связанных алгорифмов. Пример реализации проверок ввода для кастомногоUITextField показывает, как разные классы проверки могут изменить «внутренности» объекта. Паттерн Тактика чем-то схож на паттерн Декоратор (глава 16 и моя предыдущая статья). Декораторы расширяют поведение объекта извне в то время, как разные стратегии инкапсулируются внутри объекта. Как говорят, декораторы изменяют «шкуру» объекта, а стратегии – «внутренности».

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

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB