CGFloat
, NSString
, NSInteger
, etc.). Each data type requires different validation algorithms. If you could encapsulate each algorithm as an object, then you could not use a group of if-else / switch-case statements to check the data and determine which algorithm is needed.UITextField
text field UITextField
later in this chapter.ConcreteStrategy
classes (A, B, and C) share a common algorithmInterface
, so the Context
can access different variants of the algorithms using the same interface.Context
Instance can be configured using various ConcreteStrategy
objects at run time. This can be viewed as a change in the "insides" of the Context
object, since changes occur from within. Decorators (see Chapter 16, the Decorator pattern and my previous article), in contrast, change the "skin" of the object, since the modifications dock from the outside. Please refer to the section “Changing the" skin "of an object compared to changing the" viscera "in Chapter 16 (previous article) for more detailed information about the differences.UITextField
object in our application that accepts user input; we will use the input results in our application later. We have a text entry 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 validation procedure that runs after the user finishes editing.UITextField
delegate object method, textFieldDidEndEditing:
An instance of UITextField
calls this method every time it loses focus. In this method we can make sure that only numbers are entered in the numeric field, and only letters are entered in the letter field. This method accepts as input a link to the current input field object (as a textField
parameter), but which of these two objects exactly? - (void)textFieldDidEndEditing:(UITextField *)textField { if (textField == numericTextField) { // [textField text] , // } else if (textField == alphaTextField) { // [textField text] , // } }
BOOL
and an instance of NSError
if the check failed. The returned NSError
object will help determine why the check was not successful. Since the verification of both numeric and alphabetic inputs are related to each other (they have the same type 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. @interface InputValidator : NSObject { } // - (BOOL) validateInput:(UITextField *)input error:(NSError **) error; @end
validateInput: error:
method takes a link to a UITextField
as an input parameter, so it can check everything in the input field and return the BOOL
value as a result of the check. The method also accepts a reference to a pointer to an NSError
. When an error occurs (that is, the method could not verify the input), the method will create an instance of NSError
and assign it to a pointer, therefore, in whatever context the verification class is used, it is always possible to get more detailed error information from this object.nil
and returns NO
, as shown in Listing 19-3. #import "InputValidator.h" @implementation InputValidator // - (BOOL) validateInput:(UITextField *)input error:(NSError **) error { if (error) { *error = nil; } return NO; } @end
NSString
as an input parameter? In this case, any action inside the strategy object will be one-sided. This means that the validator will simply do the check and return the result without modifying the original value. With an input parameter of type UITextField
we can combine the two approaches. Our scan objects will be able to change the original value of the text field (for example, by deleting the wrong characters) or simply view the value without changing it.NSException
exception if the check fails? This is because throwing your own exception and intercepting 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 another matter). It is relatively cheaper to return an NSError
object, 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 of NSError
when an abnormal situation occurs. A common example is one of the NSFileManager
methods, (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error
. If an error occurs, when NSFileManager
tries to move a file from one place to another, it will create a new instance of NSError
that describes the problem. The caller may use the information contained in the returned NSError
object for further error handling. Thus, the purpose of the NSError
object in our method is to provide information about a failure in operation. #import "InputValidator.h" @interface NumericInputValidator : InputValidator { } // , , // , 0-9 - (BOOL) validateInput:(UITextField *)input error:(NSError **) error; @end
NumericInputValidator
inherits from the abstract base class InputValidator
and overrides its validateInput: error:
method. We declare the method again to emphasize that this subclass implements or redefines it. This is not mandatory, but is good practice. #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
validateInput:error:
method focuses mainly on two aspects:NSRegularExpression
object. The regular expression we used is “^ [0–9] * $”. It means that from the beginning of the entire line (indicated by “^”) and the end (indicated by “$”), there must be more or more characters (indicated by “*”) from the set that contains only numbers (indicated by “[0–9] ").NSError
object that contains the message “The input can contain only numerical values” and assigns it to the input pointer to the NSError
. It then finally returns a BOOL
value indicating the success or failure of the operation. The error is associated with the special code 1001 and the specific value of the error domain, defined in the header file of the InputValidator
class InputValidator
approximately as shown below: static NSString * const InputValidationErrorDomain = @"InputValidationErrorDomain";
NumericInputValidator
class, which checks for the presence of only letters in the input, called AlphaInputValidator
, contains a similar algorithm for checking the content of the input field. AlphaInputValidator
overrides the same method as NumericInputValidator
. Obviously, this algorithm verifies that the input string contains only letters, as shown in Listing 19-6. #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
AlphaInputValidator
also a type of InputValidator
and implements the validateInput:
method. It has a brother- NumericInputValidator
, a code structure and algorithm, except that it uses another regular expression in the NSRegularExpression
object, and the error code and message are specific to literal checking. The regular expression we use to test the letters is “^ [a-zA-Z] * $”. It is similar to the expression for his fellow numeric checker, except that the set of valid characters contains letters of both lower and upper case. As we can see, in both versions a lot of duplicate code. Both algorithms have a similar structure; you can refactor a structure into a template method (see chapter 18) into an abstract base class. Specific subclasses of InputValidator
can override the primitive operations defined in InputValidator
to return unique information to the template algorithm — for example, a regular expression and various construction attributes of an NSError
object, etc. I will leave this to you as an exercise.UITextFiel
d does not know about them, so we need our own version of UITextField
, which understands everything. We will create a subclass of UITextField
that contains a reference to InputValidator
and a validate
method, as shown in Listing 19-7. #import "InputValidator.h" @interface CustomTextField : UITextField { @private InputValidator *inputValidator_; } @property (nonatomic, retain) IBOutlet InputValidator *inputValidator; - (BOOL) validate; @end
CustomTextField
contains a property that holds ( retain
) a reference to InputValidator
. When its validate
method is called, it uses a reference to InputValidator
to start the validation. We can see this in the implementation shown in Listing 19-8. #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
method, a message is sent [inputValidator_ validateInput:self
error:&error]
[inputValidator_ validateInput:self
error:&error]
[inputValidator_ validateInput:self
error:&error]
inputValidator_
link. The beauty of the pattern is that CustomTextField
does not need to know what type of InputValidator
it uses or any details of the algorithm. Therefore, if in the future we add some new InputValidator
, the CustomTextField
object will use the new InputValidator
as well.UIViewController
, which implements the UITextFieldDelegate
protocol and contains two IBOutlets
type CustomTextField
, as shown in Listing 19–9. #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
and put the check there. This method will be called each time the value in the input field changes and the focus is lost. When the user finishes input, our CustomTextField
class will call this delegate method, illustrated in Listing 19-10. @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:
when editing in one of the fields is completed, the method checks that the textField
object belongs to the CustomTextField
class. If so, he sends a validate
message to him to start the process of checking the entered text. As we can see, we no longer need these conditional statements. Instead, we have a much simpler code for the same purpose. Except for the additional verification that the textField
object is a CustomTextField
type, there is nothing more complicated.InputValidator numericTextField_
and alphaTextField_
defined in StrategyViewController
? Both input fields are declared as IBOutlet
in Listing 19–9. We can pick them up in the Interface Builder view controller through IBOutlet
, as we do with other buttons and so on. Similarly, in the declaration of the CustomTextField
class in Listing 19-7, its inputValidator
property inputValidator
also IBOutlet
, which means that we can assign an instance of InputValidator
to the *TextField
object in Interface Builder, too.Thus, everything can be constructed by using Interface Builder reference connections if you declare certain class properties as IBOutlet
. For a more detailed discussion of how to use custom Interface Builder objects, refer to “Using CoordinatingController
Interface Builder” in Chapter 11, which talks about the Mediator pattern.UITextField
shows how various validation classes can change the "insides" of an object. Pattern Strategy is somewhat similar to the Pattern Decorator (Chapter 16 and my previous article). Decorators extend the object's behavior from the outside while various strategies are encapsulated inside the object. As they say, decorators change the "skin" of the object, and strategies - "insides".Source: https://habr.com/ru/post/222355/
All Articles