iOS applies a development example of the Strategy policy pattern from the design pattern

  • 2020-05-30 21:06:00
  • OfStack

When writing a program, we often encounter situations where we cram 1 heap of algorithms into the same piece of code, and then use if-else or switch-case conditional statements to determine which algorithm to use. These algorithms may be 1 heap of similar class functions or methods used to solve related problems. For example, a routine that validates input data, the data itself can be any data type (NSString, CGFloat, etc.), and each data type requires a different validation algorithm. If you can encapsulate each algorithm into an object, you can eliminate a heap of if-else or switch-case statements that determine which algorithm to use based on the data type.


We separate related algorithms into different classes, called policy patterns. Policy pattern: define a series of 1 algorithms, encapsulate each of them, and make them interchangeable. This pattern allows the algorithm to change independently of the client that is using it.

We should consider using the policy pattern in the following situations.
@ : 1 class USES multiple conditional statements to define many behaviors in its operations, and we can move the related conditional branches to their own policy classes.
@ : various variations of the algorithm are required.
You need to avoid exposing complex algorithmic data structures to clients.

Let's use a simple example to illustrate how the policy pattern is used. Suppose there are two UITextField, one UITextField can only enter letters, and the other UITextField can only enter Numbers. In order to ensure the validity of the input, we need to verify it when the user finishes editing the text box. We put data validation in the proxy method textFieldDidEndEdting.

If we did not use the policy pattern, our code would look like this:


- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (textField == self.numberTF) {
        // Verify that the value contains only Numbers
       
    }else if (textField == self.alphaTF) {
        // Verify that the value contains only letters
       
    }
}

If there were more different types of text boxes, the conditional would continue. If you could get rid of these conditional statements, the code would be easier to manage and maintain in the future.

The goal now is to bring these validation checks into the various policy classes so that they can be reused in proxy methods and other methods. Each validation takes the input value from the text box, validates it against the policy required by the H, and returns an BOOL value. If the return fails, one instance of NSError is also returned. The return of NSError can explain the reason for the failure.

We designed an abstract base class, InputValidator, with an validateInput:input: error:error method. There are two subclasses, NumberInputValidator and AlphaInputValidator. The specific code is as follows:

Class declarations for abstract InputValidator in InputValidator.h


static NSString *const InputValidationErrorDomain = @"InputValidationErrorDomain";
 
@interface InputValidator : NSObject
/**
 *  The stub method that actually validates the policy
 */
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)error;
 
@end

This method also has a reference to an NSError pointer, and when an error occurs (that is, the validation fails), the method constructs an NSError instance and assigns a value to the pointer so that the use of validation can do detailed error handling.

The default implementation of abstract InputValidator in InputValidator.m

#import "InputValidator.h"


@implementation InputValidator
 
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)error {
    if (error) {
        *error = nil;
    }
    return NO;
}
 
@end

We have defined the behavior of the input validator, and now we are going to write the actual input validator. Let's write the numeric one first, as follows:

Class definition for NumberInputValidator in NumberInputValidator.h


#import "InputValidator.h"
 
@interface NumberInputValidator : InputValidator
 
/**
 *  This method is redeclared here to emphasize what the subclass implements or overloads, which is not required, but is a good practice.
 */
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)error;
@end

Implementation of NumberInputValidator in NumberInputValidator.m

#import "NumberInputValidator.h"
 
@implementation NumberInputValidator
 
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)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 no match, there will be an error sum NO.
    if (numberOfMatches == 0) {
        if (error != nil) {
            // To determine error Objects exist
            NSString *description = NSLocalizedString(@" Validation fails ", @"");
            NSString *reason = NSLocalizedString(@" The input can contain only Numbers ", @"");
            NSArray *objArray = [NSArray arrayWithObjects:description, reason, nil];
            NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, nil];
            
            NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
            // The error is associated with the custom error code 1001 And in the InputValidator In the header file.
            *error = [NSError errorWithDomain:InputValidationErrorDomain code:1001 userInfo:userInfo];
        }
        
        return NO;
    }
    
    return YES;
}
 
@end

Now, let's write the letter verification implementation, the code is as follows:

Class definition for AlphaInputValidator in AlphaInputValidator.h


#import "InputValidator.h"
@interface AlphaInputValidator : InputValidator
 
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)error;
 
@end

Implementation of AlphaInputValidator in AlphaInputValidator. m:

#import "AlphaInputValidator.h"


@implementation AlphaInputValidator
 
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)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 no match, there will be an error sum NO.
    if (numberOfMatches == 0) {
        if (error != nil) {
            // To determine error Objects exist
            NSString *description = NSLocalizedString(@" Validation fails ", @"");
            NSString *reason = NSLocalizedString(@" Input can only pack 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]; // The error is associated with the custom error code 1002 And in the InputValidator In the header file.
        }
        
        return NO;
    }
    
    return YES;
}
 
@end

AlphaInputValidator is also the InputValidator type that implements the validateInput method. Its code structure and algorithm are similar to NumberInputValidator, except that it USES different regular expressions, different error codes and messages. You can see a lot of overlap between the two versions of the code. The two algorithms have the same structure, and we can take this structure, and we can reconstitute this structure into the template methods of the abstract parent class (which we'll implement in the next blog post).

At this point, we have written input validators that we can use on the client side, but UITextField doesn't recognize them, so we need our own version of UITextField. We will create a subclass of UITextField with a reference to InputValidator and a method validate. The code is as follows:

CustomTextField.h class declaration for CustomTextField


#import <UIKit/UIKit.h>
#import "InputValidator.h"
@interface CustomTextField : UITextField
 
@property (nonatomic, strong) InputValidator *inputValidator; // with 1 All properties remain correct InputValidator The reference.
 
- (BOOL)validate;
 
@end

CustomTextField has a property that holds a reference to InputValidator. When its validate method is called, it USES this InputValidator reference to start the actual validation process.

Implementation of CustomTextField in CustomTextField.m


#import "CustomTextField.h"
 
@implementation CustomTextField
 
- (BOOL)validate {
    NSError *error = nil;
    BOOL validationResult = [_inputValidator validateInput:self error:&error];
    
    if (!validationResult) {
        // And I've made myself understand by this example, NSError ".
        UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:[error localizedDescription] message:[error localizedFailureReason] delegate:nil cancelButtonTitle:@" determine " otherButtonTitles:nil, nil];
        [alertView show];
    }
    
    return validationResult;
}
 
@end

The validate method sends the inputValidator reference [_inputValidator validateInput:self error: & error] news. CustomTextField does not need to know what type of InputValidator is being used and any details of the algorithm, which is the benefit of the policy pattern. For client usage, just call the validate method. Therefore, if a new InputValidator is added in the future, the client does not need to make any changes.

Next, let's take a look at how the client is used. The code is as follows.


#import "ViewController.h"
#import "CustomTextField.h"
#import "InputValidator.h"
#import "NumberInputValidator.h"
#import "AlphaInputValidator.h"
@interface ViewController () <UITextFieldDelegate>
 
@property (weak, nonatomic) IBOutlet CustomTextField *numberTF;
@property (weak, nonatomic) IBOutlet CustomTextField *alphaTF;
 
 
@end
 

@implementation ViewController

static NSString *const InputValidationErrorDomain = @"InputValidationErrorDomain";
 
@interface InputValidator : NSObject
/**
 *  The stub method that actually validates the policy
 */
- (BOOL)validateInput:(UITextField *)input error:(NSError *__autoreleasing *)error;
 
@end
0
As you can see, we don't need those conditional statements anymore. Instead, we use a much simpler statement to achieve the same data validation. There should be nothing more complicated than an extra check above to ensure that the textField object is of type CustomField.

The Strategy model has the following advantages:
1) the Strategy class hierarchy defines the reusable algorithms or behaviors of the 1 series for Context. Inheritance helps extract common functionality from these algorithms.
2) provides a way to replace the inheritance relationship: inheritance provides another way to support multiple algorithms or behaviors. You can directly generate a subclass of Context to give it a different behavior. But this hardlines the behavior into Context, mixing the implementation of the algorithm with the implementation of Context, making Context difficult to understand, difficult to maintain, and difficult to extend, as well as unable to dynamically change the algorithm. You end up with a bunch of related classes whose only difference is the algorithm or behavior they use. Encapsulating an algorithm in a separate Strategy class allows you to change it independently of its Context, making it easy to switch, understand, and extend.
3) eliminate some of the if else conditional statements: the Strategy pattern provides an alternative to using conditional statements to select the desired behavior. When different behaviors are stacked in one class, it is difficult to avoid using conditional statements to select the appropriate behavior. Encapsulate the behavior in a separate Strategy class that eliminates these conditional statements. Code with many conditional statements usually means that you need to use Strategy mode.
4) implementation selection Strategy mode can provide different implementations of the same behavior. Customers can choose from different strategies according to different time/space trade-offs.

Disadvantages of Strategy mode:

1) the client must know all the policy classes and decide which one to use: one potential disadvantage of this pattern is that a client must know the difference between Strategy and Strategy in order to select a suitable Strategy. At this point you may have to expose specific implementation issues to the customer. Therefore, the Strategy pattern is only needed when these different behaviors are variations on customer-related behaviors.
2) communication overhead between Strategy and Context: whether the algorithm implemented by ConcreteStrategy is simple or complex, they all share the interface defined by Strategy. So it's likely that some ConcreteStrategy won't use all of the information passed to them through this interface; Simple ConcreteStrategy may not use any of this information! This means that sometimes Context will create and initialize 1 parameters that will never be used. If this were the case, a tighter coupling between Strategy and Context would be required.
3) the policy pattern will result in many policy classes: you can reduce the number of objects to some extent by using the enjoy pattern. Increased the number of objects Strategy increased the number of objects in an application. Sometimes you can reduce this overhead by implementing Strategy as a stateless object that can be Shared by each Context. Any remaining state is maintained by Context. Context passes this state on every request to the Strategy object. The Shared Strategy should not maintain state between calls.


Related articles: