Strategy (Translated from the English chapter "Strategy" from the book "Pro Objective-C Design Patterns for iOS" by Carlo Chung)

    Do you remember the last time you started a block of code with a lot of different algorithms and used spaghetti from the if-else / switch-case conditions to determine which one to use. Algorithms could be a set of functions / methods of similar classes that solve similar problems. For example, you have a procedure for checking input. The data itself may be of any type (eg CGFloat, NSString, NSIntegeretc.). Each of the data types requires different verification algorithms. If you could encapsulate each algorithm as an object, then you could not use the group of if-else / switch-case statements to check the data and determine which algorithm is needed.

    In object-oriented programming, you can distinguish related algorithms into various classes of strategies. The design pattern that is used in such cases is called Strategy. In this chapter, we will discuss the concepts and key features of the Strategy pattern. We will also design and implement several classes for validating data in the form of strategies for validating the input of a text field object UITextFieldlater in this chapter.

    What is a Strategy pattern?


    One of the key roles in this pattern is played by the strategy class, which declares a common interface for all supported algorithms. There are also specific classes of strategies that implement algorithms using the strategy interface. A context object is configured using an instance of a particular strategy object. A context object uses the strategy interface to invoke an algorithm defined in a particular strategy class. Their relationship is illustrated in the class diagram in Figure 19–1.

    image
    Figure 19-1. The structure of the pattern classes Strategy

    A group or hierarchy of related algorithms in the form of classes ConcreteStrategy(A, B, and C) share a common one algorithmInterface, so they Contextcan access different versions of the algorithms using the same interface.

    Note.Strategy pattern : defines a family of algorithms, encapsulates each of them and makes them interchangeable. The strategy allows the algorithms to change independently of the customers who use them. *

    The initial definition given in GoF's Design Patterns (Addison-Wesley, 1994).

    An instance Contextcan be configured using various objects ConcreteStrategyat runtime. It can be considered as a change in the "internal" of the objectContextsince the changes occur from the inside. Decorators (see chapter 16, the Decorator pattern and my previous article), in contrast, change the "skin" of the object, since modifications are docked from the outside. Please refer to the section “Changing the Skin of an Object Compared to Changing the Insides” in chapter 16 (previous article) for more detailed information on the differences.

    Strategy Pattern in Model-View-Controller

    In the Model-View-Controller pattern, the controller determines when and how to display the data contained in the model. The view itself knows how to display something, but does not know that, until the controller indicates it. When working with another controller, but with the same form, the format of the output data may be the same, but the data types may be different in accordance with other conclusions from the new controller. The controller in this case is, as it were, a strategy for a view object. As we mentioned in previous chapters, the relationship between the controller and the view is based on the Strategy pattern.

    When is the use of the Strategy pattern appropriate?


    The use of this pattern is advisable in the following cases:

    • The class logic uses many conditional statements to select the desired behavior. You can move conditional code into a separate strategy class.
    • You need different versions of the algorithm.
    • You would not want to expose complex and narrowly specific data structures (to customers).

    Using data validation strategies using the UITextField class as an example


    Let's create a simple example of the implementation of the Strategy pattern in the application. Suppose we need some object UITextFieldin our application that accepts user input; we will use the input results in our application later. We have a text input field that accepts only letters, that is, a – z or A – Z, and we also have a field that accepts only numeric data, that is, 0–9. To make sure that the input in the fields is correct, each of them needs to have some kind of on-site data verification procedure that starts after the user finishes editing.

    We can put the necessary test data to the delegate object method UITextField, textFieldDidEndEditing:. InstanceUITextFieldcalls this method every time it loses focus. In this method, we can make sure that only numbers are entered in the digital field, and only letters are entered in the alphabetic field. This method accepts at the input a link to the current object of the input field (as a parameter textField), but which one of the two objects is this?

    Without the Tragia pattern, we would have come to a code like the one shown in Listing 19–1.

    Listing 19–1. Typical UITextField Content Validation Script in the delegate method textFieldDidEndEditing
    - (void)textFieldDidEndEditing:(UITextField *)textField
    {
        if (textField == numericTextField)
        {
            // проверяем [textField text] и убеждаемся,
            // что значение цифровое
        }
        else if (textField == alphaTextField)
        {
            // проверяем [textField text] и убеждаемся,
            // что значение содержит только буквы
        }
    }
    


    Of course, there can be more conditional statements if there are more input fields for different data. We could make the code more manageable if we got rid of all these conditional expressions, which could greatly simplify our life in the future with the support of the code.

    Tip: If there are many conditional statements in your code, this may mean that you need to refactor them and separate them into separate objects of the Strategy.

    Now our goal is to take up this verification code and scatter it across various classes of Strategies so that it can be reused in the delegate and other methods. Each of our classes takes a line from the input field, then checks it based on the desired strategy, and finally returns a type value BOOLand an instance NSErrorif the check failed. Returned objectNSErrorwill help determine why the verification was not successful. Since the verification of both digital and letter input are related to each other (they have the same types of input and output), they can be combined with one interface. Our set of classes is shown in the class diagram in Figure 19–2.

    image
    Figure 19–2. The class diagram shows the relationship between CustomTextField and its associated strategies.

    We will not declare this interface as a protocol, but as an abstract base class. The abstract base class is more convenient in this case, because it is easier to refactor the behavior common to all concrete classes of strategies. Our abstract base class will look like Listing 19–2.

    Listing 19–2. Declaring an InputValidator Class in InputValidator.h
    @interface InputValidator : NSObject
    {
    }
    // Заглушка для любой стратегии проверки
    - (BOOL) validateInput:(UITextField *)input error:(NSError **) error;
    @end
    


    The method validateInput: error:takes a reference to UITextFieldas an input parameter, so it can check everything that is in the input field and returns the value BOOLas a result of the check. The method also accepts a reference to a pointer to NSError. When some kind of error occurred (that is, the method could not verify the correctness of the input), the method will create an instance NSError and assign it to the pointer, therefore, in whatever context the verification class is used, there is always the opportunity to get more detailed information about the error from this object.

    The default implementation of this method only sets the error pointer to niland returns NO, as shown in Listing 19-3.

    Listing 19-3. The default implementation of the InputValidator class in InputValidator.m
    #import "InputValidator.h"
    @implementation InputValidator
    // Заглушка для любой стратегии проверки
    - (BOOL) validateInput:(UITextField *)input error:(NSError **) error
    {
        if (error)
        {
            *error = nil;
        }
        return NO;
    }
    @end
    


    Why didn’t we use it NSStringas an input parameter? In this case, any action inside the strategy object will be one-sided. This means that the validator will simply do a check and return the result without modifying the original value. With an input parameter of type UITextFieldwe can combine two approaches. Our scan objects will be able to change the initial value of the text field (for example, by deleting incorrect characters) or simply view the value without changing it.

    Another question is why don't we just throw an exceptionNSExceptionif check failed? This is because throwing your own exception and catching it in a try-catch block in the Cocoa Touch framework is a very resource-intensive operation and is not recommended (but try-catch system exceptions are a completely different matter). It is relatively cheaper to return an object NSError, as recommended in the Cocoa Touch Developer's Guide. If we look at the Cocoa Touch framework documentation, we notice that there are many APIs that return an instance NSErrorwhen some abnormal situation occurs. A common example - is one of the methods NSFileManager, (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error. If an error occurs when NSFileManagertrying to move a file from one place to another, it will create a new instanceNSErrorthat describes the problem. The calling method can use the information contained in the returned object NSErrorto further handle errors. Thus, the purpose of the object NSErrorin our method is to provide information about the denial of work.

    Now we have defined how a good input validation class should behave. Now we can start creating a real reviewer. Let's first create one for inputting numbers, as shown in Listing 19–4.

    Listing 19-4. Declaring a NumericInputValidator Class in NumericInputValidator.h
    #import "InputValidator.h"
    @interface NumericInputValidator : InputValidator
    {
    }
    // Метод проверки, который убеждается, что ввод содержит только
    // цифры, то есть 0-9
    - (BOOL) validateInput:(UITextField *)input error:(NSError **) error;
    @end
    


    NumericInputValidatorinherits from an abstract base class InputValidatorand overrides its method validateInput: error:. We declare the method again to emphasize that this subclass implements or redefines it. This is not necessary, but is good practice.

    The implementation of the method is shown in Listing 19–5.

    Listing 19-5. Implementation of the NumericInputValidator class in 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:®Error];
        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
    


    The implementation of the method validateInput:error:focuses mainly on two aspects:

    1. It checks the number of matches of the numerical data in the input field with the previously created object NSRegularExpression. The regular expression that we used is “^ [0–9] * $”. It means that from the beginning of the whole line (indicated by “^”) and the end (indicated by “$”), there must be 0 or more characters (indicated by “*”) from the set that contains only digits (indicated by “[0–9] ").
    2. If there are no matches at all, then he creates a new object NSErrorthat contains the message "The input can contain only numerical values" and assigns it to the input pointer to NSError. Then it finally returns a type value BOOLindicating the success or failure of the operation. The error is associated with a special code 1001 and a special error domain value defined in the class header file InputValidatorapproximately as shown below:
      static NSString * const InputValidationErrorDomain = @"InputValidationErrorDomain";
      

    The brother of the class NumericInputValidator, which checks for the presence of only letters in the input, called AlphaInputValidator, contains a similar algorithm for checking the contents of the input field. AlphaInputValidatoroverrides the same method as NumericInputValidator. Obviously, this algorithm checks that the input string contains only letters, as shown in Listing 19-6.

    Listing 19-6. Implementation of the AlphaInputValidator class in 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:®Error];
        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
    


    Our class is AlphaInputValidatoralso a variation InputValidatorand implements a method validateInput:. It has a brother-like NumericInputValidatorstructure, the code structure and algorithm, except that it uses a different regular expression in the object NSRegularExpression, and the error code and message are specific for letter verification. The regular expression that we use to test letters is “^ [a-zA-Z] * $”. It looks like an expression for his fellow numerical verification, except that the set of valid characters contains both lower and upper case letters. As we can see, in both versions there is a lot of duplicate code. Both algorithms have a similar structure; you can refactor the structure into a template method (see chapter 18) into an abstract base class. Specific subclasses InputValidatorcan override primitive operations defined inInputValidatorto return unique information to the template algorithm — for example, a regular expression and various attributes for constructing an object NSError, etc. I will leave this for you as an exercise.

    Now we already have validation classes ready for use in the application. However, UITextField does not know about them, so we need our own version UITextField, which understands everything. We will create a subclass UITextFieldthat contains a reference to InputValidatorand a method validate, as shown in Listing 19–7.

    Listing 19-7. CustomTextField class declaration in CustomTextField.h
    #import "InputValidator.h"
    @interface CustomTextField : UITextField
    {
        @private
        InputValidator *inputValidator_;
    }
    @property (nonatomic, retain) IBOutlet InputValidator *inputValidator;
    - (BOOL) validate;
    @end
    


    CustomTextFieldcontains a property that holds ( retain) a reference to InputValidator. When its method is called validate, it uses the link to InputValidatorto start the check. We can see this in the implementation shown in listing 19–8.

    Listing 19-8. CustomTextField class implementation in 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
    


    The method validatesends a message to the link . The beauty of the pattern is that you don’t need to know what type it uses or any details of the algorithm. Therefore, if in the future we add some new one , the object will use the new one in the same way. So, all the preparatory work is done. Suppose a client is one that implements the protocol and contains two types , as shown in Listing 19–9. Listing 19-9. StrategyViewController class declaration in StrategyViewController.h[inputValidator_ validateInput:self
    error:&error]
    inputValidator_CustomTextFieldInputValidatorInputValidatorCustomTextFieldInputValidator

    UIViewControllerUITextFieldDelegateIBOutletsCustomTextField


    #import "NumericInputValidator.h"
    #import "AlphaInputValidator.h"
    #import "CustomTextField.h"
    @interface StrategyViewController : UIViewController 
    {
        @private
        CustomTextField *numericTextField_;
        CustomTextField *alphaTextField_;
    }
    @property (nonatomic, retain) IBOutlet CustomTextField *numericTextField;
    @property (nonatomic, retain) IBOutlet CustomTextField *alphaTextField;
    @end
    


    We decided to let the controller implement the delegate method (void)textFieldDidEndEditing:(UITextField *)textFieldand put the check there. This method will be called every time the value in the input field changes and the focus is lost. When the user finishes typing, our class CustomTextFieldwill call this delegate method, as shown in Listing 19-10.

    Listing 19-10. Client code defined in the delegate method textFieldDidEndEditing:, which validates an instance of CustomTextField using a strategy object (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
    


    When called textFieldDidEndEditing:, when editing in one of the fields is completed, the method checks that the object textFieldbelongs to the class CustomTextField. If so, he sends a message to validatehim to start the process of checking the entered text. As we can see, we no longer need these conditional statements. Instead, we have much simpler code for the same purpose. With the exception of additional checking that the object textFieldis a type CustomTextField, there is nothing more complicated.

    But wait a moment. Something doesn't look very good. How could we appropriate the correct instances of InputValidator numericTextField_and alphaTextField_defined in StrategyViewController? Both input fields are declared as IBOutletin Listing 19–9. We can pick them up in the Interface Builder view controller viaIBOutlets, like we do with other buttons and things. Similarly, in the class declaration CustomTextFieldin Listing 19–7, its property is inputValidatoralso IBOutlet, which means that we can assign an instance to an InputValidatorobject *TextFieldin Interface Builder too. That way, everything can be constructed by using Interface Builder reference connections if you declare certain properties of the class as IBOutlet. For a more detailed discussion of how to use custom Interface Builder objects, refer to “Using CoordinatingControllerInterface Builder” in Chapter 11, which talks about the Mediator pattern.

    Conclusion


    In this chapter, we discussed the concepts of the Strategy pattern and how to use this pattern for clients to use various related algorithms. An example of implementation of input checks for custom UITextFieldshows how various classes of checks can change the "internals" of an object. The Strategy pattern is somewhat similar to the Decorator pattern (chapter 16 and my previous article). Decorators extend the behavior of an object from the outside while various strategies are encapsulated within the object. As they say, decorators change the "skin" of the object, and strategies - the "insides".

    In the next chapter, we will see another pattern, which is also associated with encapsulation of algorithms. The encapsulated algorithm is mainly used for deferred execution of a command as a separate object.

    Also popular now: