Chapter 19. Strategy

Do you remember the last time you stuffed a bunch of different algorithms in the same block of code and used spaghetti of if-else / switch-case conditional statements to determine which one to use? The algorithms could be a bunch of functions/methods of similar classes that solve related problems. For example, I have a routine that validates some input data. The data itself can be of any data type (e.g., CGFloat, NSString, NSInteger, etc.). Each of the data types requires a different validation algorithm. If we can encapsulate each algorithm as an object, then we can eliminate a bunch of if-else / switch-case statements for data type checking in order to determine what algorithm to use.

In object-oriented software design, we can segregate related algorithms into different classes as strategies. A design pattern related to the practice is called the Strategy pattern. In this chapter, we are going to discuss the concepts and key features of the Strategy pattern. We will also design and implement some data validating classes as different strategies to validate the input of UITextField objects later in the chapter.

What Is the Strategy Pattern?

One of the key roles in the Strategy pattern is a strategy class that declares an interface common to all supported or related algorithms. There are also concrete strategy classes that implement the related algorithm using the strategy interface. An object of a context class is configured with an instance of a concrete strategy object. The context object uses the strategy interface to call the algorithm defined by the concrete strategy class. Their static relationships are illustrated in a class diagram shown in Figure 19-1.

A class structure of the Strategy pattern

Figure 19-1. A class structure of the Strategy pattern

A group or hierarchy of related algorithms in a form of ConcreteStrategy (A, B, and C) classes is sharing the common algorithmInterface so Context can access variants of the algorithm with the same interface.

Note

THE STRATEGY PATTERN: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.[17]

An instance of Context can be configured with different ConcreteStrategy objects at runtime. It can be considered changing the "guts" of the Context object as the changes appear inside the object. Decorators (see the Decorator pattern, Chapter 16), on the other hand, change the "skin" of the object, as the modifications are stacked up from outside. Please see the "Changing the 'Skin' vs. 'Guts' of an Object" section in Chapter 16 for more detailed discussion about the differences.

When Would You Use the Strategy Pattern?

You'd naturally think about using the pattern when

  • A class uses multiple conditional statements in its operations to define many behaviors. You can move related conditional branches into their own strategy class.

  • You need different variants of an algorithm.

  • You need to avoid exposing complex and algorithm-specific data structures to clients.

Applying Validation Strategies in UITextField

Let's make a simple example of implementing the Strategy pattern in an app. Assume we need to have some UITextField in our app that takes a user's input; then we will use the input later down in the application process. We have a text field that takes only letters, i.e., a–z or A–Z, as well as a field that takes only numerical values, i.e., 0–9. In order to make sure an input is valid in each of the fields, each of them needs to have some validation in place after the user is finished editing the text fields.

We can put some data validation in a UITextField delegate method, textFieldDidEndEditing:. An instance of UITextField invokes that method every time its focus is lost. In that method, we can check that the input of a numeric text field contains only numeric values as well as only letters in an alpha-character text field. The delegate method provides an input reference to the active text field (as textField) in question, but how do we know that one is for numeric or letter inputs?

Without the Strategy pattern in place, we would end up with the code shown in Listing 19-1.

Example 19-1. A Typical Scenario of Examining the Content of UITextField in Its textFieldDidEndEditing Delegate Method

- (void)textFieldDidEndEditing:(UITextField *)textField
{
  if (textField == numericTextField)
  {
    // validate [textField text] and make sure
    // the value is numeric
  }
  else if (textField == alphaTextField)
  {
    // validate [textField text] and make sure
    // the value contains only letters
  }
}

The conditionals can go on if we have more text fields for different types of inputs. Our code could be more manageable if we can eliminate all those conditional statements, which would make our lives a lot easier in the long run when we maintain the code later.

Note

HINT: If there are many conditional statements in your code, then it may indicate that they need to be refactored into different Strategy objects.

Now our goal is to yank those validation checks into different Strategy classes so they can be reused in the delegate and other methods as well. Each one of our validations takes an input value from a text field, then validates it based on the required strategy, and finally returns a BOOL value and an instance of NSError if it fails the validation. A returned NSError can help explain what exactly failed the validation. Since both the numeric and alpha input checks are somewhat related (they share the same input/output types), they can be factored into a common interface. Our class design is shown in a class diagram in Figure 19-2.

A class diagram shows the static relationships between CustomTextField and its related InputValidator strategies.

Figure 19-2. A class diagram shows the static relationships between CustomTextField and its related InputValidator strategies.

We will not declare that interface in a protocol but in an abstract base class. An abstract base class is more convenient for this solution because it is easier to refactor some common behavior among different concrete strategy subclasses later. Our abstract base class of InputValidator should look something like Listing 19-2.

Example 19-2. A Class Declaration of Abstract InputValidator in InputValidator.h

@interface InputValidator : NSObject
{
}

// A stub for any actual validation strategy
- (BOOL) validateInput:(UITextField *)input error:(NSError **) error;

@end

The validateInput: error: method takes a reference to UITextField as an input so it can validate whatever is in the text field and returns a BOOL value to indicate the outcome of a validation. The method also accepts a reference to a pointer of NSError. When there is an error (i.e., it fails to validate the field), the method will construct an instance of NSError and assign it to the pointer so whatever context is using the validator can have more elaborated error handling from it.

The default implementation of the method only sets the error pointer to nil and returns NO to a caller, as shown in Listing 19-3.

Example 19-3. A Default Implementation of Abstract InputValidator in InputValidator.m

#import "InputValidator.h"

@implementation InputValidator

// A stub for any actual validation strategy
- (BOOL) validateInput:(UITextField *)input error:(NSError **) error
{
  if (error)
  {
    *error = nil;
  }
  return NO;
}

@end

Why don't we use NSString as an input parameter instead? In this case, if it takes only an NSString value, then any action taken inside a strategy object will be one-way. It means that a validator will just do the check and return a result without modifying the original value. With the input parameter set to UITextField, we will have the best of both worlds. Our validators can have an option to change the original value of a text field (e.g., removing invalid values) or just examine the value without modifying it.

Another question is, why don't we just raise an instance of NSException if the input value failed? It is because raising your own exception and catching it in a try-catch block in the Cocoa Touch framework is very resource-intensive and it's not recommended (but try-catch system–raised exceptions are a different story). It's relatively cheaper to return an NSError object, which is recommended in the Cocoa Touch Developer's Guide. When we take a look at documentation of the Cocoa Touch framework, we will notice that there are a lot of APIs that return an instance of NSError when anomalies occur. A common example is one of NSFileManager's instance methods,(BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error. If there is an error occurring when NSFileManager tries to move a file from here to there for you, it will create a new instance of NSError that describes the problem. The calling method can use the information embedded in the returned NSError object for any further error handling. So the purpose of the NSError object output in our stubbed method provides information about the failure situation.

Now we've defined how a good input validator should behave. Then we can move on to make a real input validator. Let's make a numeric one, as shown in Listing 19-4.

Example 19-4. The Class Declaration of NumericInputValidator in NumericInputValidator.h

#import "InputValidator.h"

@interface NumericInputValidator : InputValidator
{
}

// A validation method that makes sure the input contains only
// numbers, i.e., 0-9
- (BOOL) validateInput:(UITextField *)input error:(NSError **) error;

@end

NumericInputValidator subclasses the abstract InputValidator base class and overrides its validateInput: error: method. We are re-declaring the method here to emphasize what this subclass implements or overrides. It's not necessary but it's a good practice.

Its implementation of the method is shown in Listing 19-5.

Example 19-5. The Implementation of NumericInputValidator 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:&regError];

  NSUInteger numberOfMatches = [regex
                                numberOfMatchesInString:[input text]
                                options:NSMatchingAnchored
                                range:NSMakeRange(0, [[input text] length])];

  // if there is not a single match
  // then return an error and 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 validateInput:error: method does mainly two things:

  1. It checks the number of matches of numerical values in the text field with a configured NSRegularExpression object. The regular expression we use there is "^[0–9]*$". It means from the beginning of a whole line (denoted as "^") till the end of it (denoted as "$"), there should be zero or more characters (denoted as "*") of a set that contains only digits (denoted as [0–9]).

  2. If there is no match at all, then it creates a new NSError object that contains a message that says, "The input can contain only numerical values" and assigns it to the input NSError pointer. Then it finally returns a BOOL value that indicates the outcome of the validation. The error is associated with a custom error code of 1001 and a custom error domain value defined in the header of InputValidator as follows:

    static NSString * const InputValidationErrorDomain =
    @"InputValidationErrorDomain";

A brother of NumericInputValidator that validates only letters in the input, called AlphaInputValidator, has a similar algorithm to validate the content of the input field. AlphaInputValidator overrides the same method as NumericInputValidator does. Apparently, its validation algorithm checks only if the input string contains only letters, as shown in its implementation file in Listing 19-6.

Example 19-6. The Implementation of AlphaInputValidator 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:&regError];
  NSUInteger numberOfMatches = [regex
                                numberOfMatchesInString:[input text]
                                options:NSMatchingAnchored
                                range:NSMakeRange(0, [[input text] length])];
  // If there is not a single match
// then return an error and 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 AlphaInputValidator also is a type of InputValidator that implements its validateInput: method. It has a similar coding structure and algorithm as its sibling, NumericInputValidator, except it uses a different regular expression in the NSRegularExpression object and the error code and message are specific to letters-only validation. The regular expression we use for checking letters is "^[a-zA-Z]*$". It is similar to the one for its brother for numeric validation, except that a set of valid characters contains both lower- and uppercase letters. As we can see, there is a lot of duplicated code in both versions of the method. Both algorithms share a common structure; you can refactor the structure into a template method (see Chapter 18) in the abstract parent class. Concrete subclasses of InputValidator can override primitive operations defined in InputValidator to return unique information to the template algorithm—for example, a regular expression and various attributes for constructing an NSError object, etc. I will leave this part for you as an exercise.

Up to this point, we have our input validators in place, ready to be used in our client app. However, UITextField doesn't know them, so we need to have our own version of UITextField that understands. We will create a subclass of UITextField that has a reference to InputValidator as well as a method—namely, validate, as shown in Listing 19-7.

Example 19-7. A Class Declaration for CustomTextField in CustomTextField.h

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

CustomTextField has a property that retains a reference to any InputValidator. When its validate method is invoked, it will use the InputValidator reference to start an actual validation process. We can see how they can be put together in an implementation shown in Listing 19-8.

Example 19-8. An Implementation of CustomTextField 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

In the validate method, it sends a message of [inputValidator_ validateInput:self error:&error] to the inputValidator_ reference. The beauty of the pattern is that the CustomTextField doesn't need to know what type of InputValidator it is using as well as any detail of the algorithm. So in the future if we add some new InputValidator, a CustomTextField object will use the new InputValidator the same way.

All of the plumbing is connected. Let's assume the client is a UIViewController that implements UITextFieldDelegate and has two IBOutlets of CustomTextField, as shown in Listing 19-9.

Example 19-9. A Class Declaration of StrategyViewController in 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

We have decided to let the controller implement one of the delegate's methods, (void)textFieldDidEndEditing:(UITextField *)textField, and put the check in that area. That method will be invoked every time the value of a text field is changed and its focus is lost. When a user input is done, our CustomTextField will call that method against its delegate, as shown in Listing 19-10.

Example 19-10. Client Code Defined in the textFieldDidEndEditing: Method That Validates an Instance of CustomTextField with Its Own Embedded Strategy Object (InputValidator)

@implementation StrategyViewController
@synthesize numericTextField, alphaTextField;
// ...
// other methods in the view controller
// ...
#pragma mark -
#pragma mark UITextFieldDelegate methods
- (void)textFieldDidEndEditing:(UITextField *)textField
{
  if ([textField isKindOfClass:[CustomTextField class]])
  {
    [(CustomTextField*)textField validate];
  }
}
@end

When the textFieldDidEndEditing: is hit when one of the text fields is done editing, it checks if the textField is our CustomTextField class. If so, it sends a validate message to it to invoke a validation process against the text input field. As we can see, we don't need those conditional statements anymore. Instead, we have a much simpler statement to do the same data validation. Except for an extra check above it to make sure the type of the textField object is CustomTextField, there shouldn't be anything more complicated.

Hey, wait a minute. Something doesn't look right. How we could assign correct concrete InputValidator instances to numericTextField_ and alphaTextField_ defined in StrategyViewController? Both *TextFields are declared as IBOutlet in Listing 19-9. We can hook them up with the view controller in the Interface Builder through the IBOutlets like we do with other buttons and whatnot. Likewise, in the class declaration of CustomTextField in Listing 19-7, its inputValidator property is also an IBOutlet, which means that we can assign an instance of InputValidator to the *TextField in the Interface Builder the same way. So everything can be actually constructed through the use of reference connections in the Interface Builder if you declare certain attributes or properties of class as IBOutlet. For a more detailed discussion on how to use custom objects in Interface Builder, see "Using the CoordinatingController in the Interface Builder" in Chapter 11, about the Mediator pattern.

Summary

In this chapter, we have discussed the concepts of the Strategy pattern and how we can utilize the pattern to have client (context) classes use variants of related algorithms. The example of implementing input validators for a custom UITextField showcases how various validators can change the "guts" of the custom UITextField. The Strategy pattern is somewhat similar to the Decorator pattern (Chapter 16). Decorators extend the behavior of an object from outside while different strategies are encapsulated inside an object. So it is said that decorators change the "skin" of the object while strategies change its "guts."

In the next chapter, we are going to see another pattern that is related to algorithm encapsulation. An encapsulated algorithm is mainly used for deferring an execution of a command as an object.



[17] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.189.199