策略模式
What 策略模式
策略模式:定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。此模式让算法的变化独立于使用它的客户。如下图:
![](https://img.haomeiwen.com/i1178839/335814d977a04e40.png)
一般使用接口(协议)的方式来实现策略模式,具体的算法实现交由不同的实体类,每个算法之间可以通过提供的方法进行替换。
例子1
首先我们定义一个 Duck
类,Duck
中会有 fly
、quack
、swim
、dispaly
等行为方法,我们了解到不同类型的鸭子🦆有些行为方法会有不同表现,所以我们需要对这些进行分类,区分出哪些是变化的部分,哪些是不会变化的。针对不会变化的我们可以使用继承来实现,对于可变( fly
、quack
)的就可以采取策略模式,封装算法,并且算法间可以相互替换。
🦆策略模式——GitHub源码
下面是部分源码:
/// 定义协议
@protocol QuackBehavior <NSObject>
- (void)quack;
@end
@protocol FlyBehavior <NSObject>
- (void)fly;
@end
#import "FlyBehavior.h"
#import "QuackBehavior.h"
@interface Duck : NSObject
/// 不变的部分
- (void)swim;
- (void)display;
/// 可变部分(子类在生成构造时设置)
@property (nonatomic, strong) id<FlyBehavior> flyBehavior;
@property (nonatomic, strong) id<QuackBehavior> quackBehavior;
- (void)performFly;
- (void)performQuack;
@end
// implementation
@implementation Duck
- (void)performFly {
[self.flyBehavior fly];
}
- (void)performQuack {
[self.quackBehavior quack];
}
@end
@interface MallardDuck : Duck
@end
@implementation MallardDuck
// 构造时指定具体的实现
- (instancetype)init {
if (self = [super init]) {
self.quackBehavior = [[Quack alloc] init];
self.flyBehavior = [[FlyWithWings alloc] init];
}
return self;
}
- (void)display {
NSLog(@"I'm a real Mallard Duck");
}
@end
上述代码采用组合的方式,将fly
、quack
的实现封装成类。将fly
的实现交由遵守FlyBehavior
的类来实现。quack
的实现交由遵守QuackBehavior
的类来实现。只需要遵守响应的协议我们就可以实现对应的行为,在不同的类中我们的实现不同,当我们需要对行为进行切换的时候,我们只需要将实现类进行替换就好。
Duck *mDuck = [[MallardDuck alloc] init];
[mDuck display];
[mDuck performQuack];
[mDuck performFly];
mDuck.flyBehavior = [[FlyWithNoWay alloc] init];
[mDuck performFly];
// 输出
I'm a real Mallard Duck
Quack
i'm flying
i can't fly
例子2
我们可以通过 iOS 的 UITextField
的输入验证的功能再来说明下策略模式。
日常开发中我们经常会对UITextField
的输入进行校验,提示用户输入是否合法,如果不使用设计模式,我们最先想到的做法是在 UITextField
的代理房中- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
中做判断
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
if (textField == numericTextField) {
// 验证输入的text是否为数值
}else if (textField == alphaTextField ){
// 验证输入的text是否为字母
}
}
当我们的输入框的输入格式越来越多 if
的判断语句也会越来越多。
ps:
一般来说当代码语句中含有很多条件语句,就意味着需要把它们重构成各种策略对象。
参照例1的想法,把变化的抽取出来,即输入验证的内容判断,我们可以判断是否是数字、字母、中文、手机号等。首先我们定义一个协议
@protocol InputValidateProtocol <NSObject>
- (BOOL)validateInput:(UITextField *)input error:(NSError **)error;
@end
协议的具体实现则交由验证判断的具体实现类。
@implementation NumericValidator
- (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 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
@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 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
然后在使用的时候设置相应的判断器。inputValidator
属性我们可以通过runtime
动态为 UITextField
绑定,或者通过子类自己添加。例子中采用的是子类的方式。
@interface CustomTextField : UITextField
@property (nonatomic, strong) id<InputValidateProtocol> inputValidator;
- (BOOL)vaild;
@end
@implementation CustomTextField
- (BOOL)vaild {
NSError *error;
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];
}
return validationResult;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.AlaphaTextField.inputValidator = [[AlphaInputValidator alloc] init];
self.numericTextField.inputValidator = [[NumericValidator alloc] init];
// Do any additional setup after loading the view.
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if ([textField isKindOfClass:[CustomTextField class]]) {
[(CustomTextField *)textField vaild];
}
}
这样子我们就不用关心textField
输入完后是如何检测输入内容是否有效,我们只需指定检测器validator
,判断如果是CustomTextField
类型就去执行一次检测。无需再添加更多的if
条件语句,而且也可以很好的复用定义好的validator
。
设计原则
- 找出代码中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 多用组合,少用继承