When writing a program, we often run into scenarios where we plug a bunch 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 a bunch of similar class functions or methods to solve related problems. For example, a routine that validates the input data itself can be any data type (such as NSString, cgfloat, etc.), and each data type requires a different validation algorithm. If you can encapsulate each algorithm as an object, you can eliminate a bunch of if-else or switch-case statements that determine what algorithms to use based on the data type.
We separate the related algorithms into different classes, called policy patterns. Policy pattern: Define a series of algorithms, encapsulate them each, and make them interchangeable. This mode allows the algorithm to vary independently from the client that uses it.
We should consider using the policy pattern in the following situations.
@: A class in its operation, using multiple conditional statements to define many behaviors, we can move the relevant conditional branches into their own policy classes.
@: Various variants of the algorithm are required.
@: You need to avoid a complex, algorithmic-related data structure that is leaking to the client.
We use a simple example to illustrate the following, how the policy pattern is used. Assuming 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 do the validation when the user finishes editing the text box. We put the data validation in the proxy method textfielddidendedting.
If you do not use a policy pattern, our code will write this:
Copy Code code as follows:
-(void) textfielddidendediting: (Uitextfield *) TextField {
if (TextField = = self.numbertf) {
Verify that its value contains only numbers
}else if (TextField = = SELF.ALPHATF) {
Verify that its value contains only letters
}
}
If there are more different types of text boxes, the conditional statements will continue. If you can remove these conditional statements, the code will be easier to manage, and future maintenance of the code will be much easier.
The goal now is to refer these validation checks to various policy classes so that they can be reused in proxy methods and other methods. Each validation takes the input value out of the text box, validates it according to the policy that is required by H, and returns a bool value. If the return fails, a Nserror instance is also returned. The returned nserror can explain the cause of the failure.
We design an abstract base class Inputvalidator, which has a validateinput:input error:error method. There are two subclasses of Numberinputvalidator and Alphainputvalidator respectively. The specific code looks like this:
class declaration of abstract inputvalidator in InputValidator.h
Copy Code code as follows:
static NSString *const Inputvalidationerrordomain = @ "Inputvalidationerrordomain";
@interface Inputvalidator:nsobject
/**
* Stub method of the actual verification strategy
*/
-(BOOL) ValidateInput: (Uitextfield *) input error: (nserror *__autoreleasing *) error;
@end
This method also has a reference to a nserror pointer, and when an error occurs (that is, the validation fails), the method constructs a Nserror instance and assigns it to the pointer, so that the place where the validation is used can do detailed error handling.
Default implementation of abstract inputvalidator in INPUTVALIDATOR.M
#import "InputValidator.h"
Copy Code code as follows:
@implementation Inputvalidator
-(BOOL) ValidateInput: (Uitextfield *) input error: (nserror *__autoreleasing *) Error {
if (Error) {
*error = nil;
}
return NO;
}
@end
We've defined the behavior of the input validator, and then we're going to write the real input validator and write the numeric type first, as follows:
Class definition of Numberinputvalidator in NumberInputValidator.h
Copy Code code as follows:
#import "InputValidator.h"
@interface Numberinputvalidator:inputvalidator
/**
* This method is redefined to emphasize what this subclass implements or overloads, which is not necessary, but is a good habit.
*/
-(BOOL) ValidateInput: (Uitextfield *) input error: (nserror *__autoreleasing *) error;
@end
Realization of Numberinputvalidator in NUMBERINPUTVALIDATOR.M
Copy Code code as follows:
#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:®error];
Nsuinteger numberofmatches = [Regex numberOfMatchesInString:input.text options:nsmatchinganchored Range:nsmakerange ( 0, Input.text.length)];
If there is no match, it will be wrong and no.
if (numberofmatches = = 0) {
if (Error!= nil) {
Let's first judge if the error object exists.
NSString *description = nslocalizedstring (@ "validation failed", @ "");
NSString *reason = nslocalizedstring (@ "input can contain numbers only", @ "");
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 header file of Inputvalidator.
*error = [Nserror errorwithdomain:inputvalidationerrordomain code:1001 Userinfo:userinfo];
}
return NO;
}
return YES;
}
@end
Now, let's write the implementation of the letter verification, which is as follows:
Class definition of Alphainputvalidator in AlphaInputValidator.h
Copy Code code as follows:
#import "InputValidator.h"
@interface Alphainputvalidator:inputvalidator
-(BOOL) ValidateInput: (Uitextfield *) input error: (nserror *__autoreleasing *) error;
@end
Implementation of Alphainputvalidator in ALPHAINPUTVALIDATOR.M:
#import "AlphaInputValidator.h"
Copy Code code as follows:
@implementation Alphainputvalidator
-(BOOL) ValidateInput: (Uitextfield *) input error: (nserror *__autoreleasing *) 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)];
If there is no match, it will be wrong and no.
if (numberofmatches = = 0) {
if (Error!= nil) {
Let's first judge if the error object exists.
NSString *description = nslocalizedstring (@ "validation failed", @ "");
NSString *reason = nslocalizedstring (@ "input can only package 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 header file of Inputvalidator.
}
return NO;
}
return YES;
}
@end
Alphainputvalidator is also the Inputvalidator type that implements the ValidateInput method. Its code structure and algorithms are similar to Numberinputvalidator, using only different regular expressions, different error codes, and messages. You can see that two versions of code have many duplicates. Two algorithms have the same structure, we can put this structure, we can put this structure to form the abstract parent class template method (will be implemented in the next blog).
At this point, we have written the input validator, can be used on the client, but Uitextfield do not know them, so we need our own Uitextfield version. We're going to create Uitextfield subclasses, which have a inputvalidator reference, and a method validate. The code is as follows:
Class declarations for Customtextfield in CustomTextField.h
Copy Code code as follows:
#import <UIKit/UIKit.h>
#import "InputValidator.h"
@interface Customtextfield:uitextfield
@property (nonatomic, strong) Inputvalidator *inputvalidator; Maintains a reference to the Inputvalidator with a property.
-(BOOL) validate;
@end
Customtextfield has a property that maintains a reference to Inputvalidator. When it calls its Validate method, it uses this inputvalidator reference to begin the actual validation process.
Realization of Customtextfield in CUSTOMTEXTFIELD.M
Copy Code code as follows:
#import "CustomTextField.h"
@implementation Customtextfield
-(BOOL) Validate {
Nserror *error = nil;
BOOL Validationresult = [_inputvalidator validateinput:self error:&error];
if (!validationresult) {
Through this example also let oneself understand, the specific use of nserror.
Uialertview *alertview = [[Uialertview alloc]initwithtitle:[error localizeddescription] Message:[error Localizedfailurereason] Delegate:nil cancelbuttontitle:@ "OK" otherbuttontitles:nil, nil];
[Alertview show];
}
return validationresult;
}
@end
The Validate method sends a [_inputvalidator validateinput:self error:&error] message to the Inputvalidator reference. Customtextfield need not know what type of inputvalidator to use and any details of the algorithm, which is the benefit of the policy model. For client use, it is only necessary to invoke the Validate method. So in the future, if a new Inputvalidator is added, the client does not need to make any changes.
Below, we look at how the client is used, the code is as follows.
Copy Code code 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
Copy Code code as follows:
-(void) Viewdidload {
[Super Viewdidload];
Inputvalidator *numbervalidator = [[Numberinputvalidator alloc] init];
Inputvalidator *alphavalidator = [[Alphainputvalidator alloc] init];
_numbertf.inputvalidator = Numbervalidator;
_alphatf.inputvalidator = Alphavalidator;
}
-(void) didreceivememorywarning {
[Super didreceivememorywarning];
Dispose of any of the can is recreated.
}
#pragma mark-uitextfielddelegate
-(void) textfielddidendediting: (Uitextfield *) TextField {
if ([TextField Iskindofclass:[customtextfield class]]) {
[(Customtextfield *) TextField validate];
}
}
@end
As you can see, we do not need those conditional statements, instead, we use a much simpler statement to achieve the same data validation. There should be nothing more complicated than the extra checks to ensure that the TextField object type is CustomField.
The strategy model has some of the following advantages:
1 Correlation Algorithm Series strategy class hierarchy defines a series of reusable algorithms or behaviors for the context. Inheritance helps to extract public functionality from these algorithms.
2 provides a way to replace an inheritance relationship: inheritance provides another way to support multiple algorithms or behaviors. You can directly generate a subclass of a context class to give it a different behavior. But this will be the behavior of the hard line into the context, and the implementation of the algorithm and the implementation of the state mix, so that the context is difficult to understand, difficult to maintain and difficult to expand, and can not dynamically change the algorithm. Finally you get a bunch of related classes, and the only difference between them is the algorithm or behavior they use. Encapsulating the algorithm in a separate strategy class allows you to change it independently of its context, making it easy to switch, easy to understand, and easy to extend.
3 eliminates some if else conditional statements: The strategy pattern provides an alternative to selecting the desired behavior with a conditional statement. When different behaviors are piled into a class, it is difficult to avoid using conditional statements to select the appropriate behavior. These conditional statements are eliminated by encapsulating the behavior in a separate strategy class. Code that contains many conditional statements usually means that you need to use the strategy pattern.
4 implementation of the selection strategy mode can provide different implementations of the same behavior. Customers can choose from different policies based on different time/space trade-offs.
Strategy Mode Disadvantage:
1) The client must know all the policy classes and decide which policy class to use: This model has a potential drawback that a customer needs to choose a suitable strategy to know how these strategy differ. You may have to expose specific implementation issues to your customers at this point. Therefore, you need to use the Strategy mode only when these different behavior variants are related to customer behavior.
2) The communication overhead between the strategy and the context: whether the algorithms implemented by each concretestrategy are simple or complex, they share strategy defined interfaces. It is likely that some concretestrategy will not all use all the information passed to them through this interface; The simple concretestrategy may not use any of the information! This means that sometimes the context creates and initializes some parameters that will never be used. If there is such a problem, it will need to be more tightly coupled between the strategy and the context. The
3) policy pattern results in a number of policy classes: You can reduce the number of objects to some extent by using the usage pattern. Increased number of objects strategy increases the number of objects in an application. Sometimes you can implement strategy as a stateless object that can be shared by each context to reduce this overhead. Any remaining state is maintained by the context. The context passes this state in every request to the Strategy object. Shared strategy should not maintain state between invocations.