面向对象设计的设计模式(十四):策略模式

定义

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。

适用场景

有时候在实现某一个功能的时可能会有多个方案:我们需要让系统可以动态灵活地更换方案;而且也能够让开发者方便地增加新的方案或删除旧的方案。

如果我们将所有的方案硬编码在同一个类中,那么在今后修改,添加,删除某个方案的时候就会改动原有类,这是违反开闭原则的。

其实我们可以定义一些独立的类来封装不同的解决方案,每一个类封装一个具体的方案,这些不同的方案就是我们所说的策略。而且我们可以用一个抽象的策略类来保证这些策略的一致性,这就是策略模式的设计方案。

现在我们清楚了策略模式的适用场景,下面看一下策略模式的成员和类图。

成员与类图

成员

策略模式除了客户端之外共有三个成员:

  • 环境类(Context):环境类内部持有一个具体策略类的实例,这个实例就是当前的策略,可以供客户端使用
  • 抽象策略类(Strategy):抽象策略类声明具体策略类需要实现的接口,这个接口同时也是提供给客户端调用的接口
  • 具体策略类(Concrete Strategy):具体策略类实现抽象策略类声明的接口,每个具体策略类都有自己独有的实现方式,即代表不同策略

下面我们通过类图来看一下各个成员之间的关系。

模式类图

策略模式类图

代码示例

场景概述

模拟一个两个整数可以随意替换加减乘除算法的场景。

场景分析

在该场景中,传入的两个整数参数是不变的,但是对于这两个整数的具体操作可以灵活切换,那么我们可以使用策略模式:将每个操作(算法)封装起来,在需要替换的时候将Context类持有的具体策略实例更新即可。

代码实现

首先我们定义好抽象策略类和具体策略类:

因为是针对两个整数的操作,所以在抽象策略类中,我们只需定义一个传入两个整数的接口即可。

抽象策略类TwoIntOperation:

//================== TwoIntOperation.h ==================

@interface TwoIntOperation : NSObject

- (int)operationOfInt1:(int)int1 int2:(int)int2;

@end



//================== TwoIntOperation.m ==================

@implementation  TwoIntOperation

- (int)operationOfInt1:(int)int1 int2:(int)int2{

    //implenting by sub classes;
    return 0;
}

@end

接着我们根据加减乘除四种运算,来分别定义四个具体策略类:

加法TwoIntOperationAdd

//================== TwoIntOperationAdd.h ==================

@interface TwoIntOperationAdd : TwoIntOperation

@end



//================== TwoIntOperationAdd.m ==================

@implementation TwoIntOperationAdd

- (int)operationOfInt1:(int)int1 int2:(int)int2{

    NSLog(@"==== adding ====");

    return int1 + int2;
}

@end

减法TwoIntOperationSubstract

//================== TwoIntOperationSubstract.h ==================

@interface TwoIntOperationSubstract : TwoIntOperation

@end



//================== TwoIntOperationSubstract.m ==================

@implementation TwoIntOperationSubstract

- (int)operationOfInt1:(int)int1 int2:(int)int2{

    NSLog(@"==== Substract ====");
    return int1 - int2;
}
@end

乘法TwoIntOperationMultiply:

//================== TwoIntOperationMultiply.h ==================

@interface TwoIntOperationMultiply : TwoIntOperation

@end



//================== TwoIntOperationMultiply.m ==================

@implementation TwoIntOperationMultiply

- (int)operationOfInt1:(int)int1 int2:(int)int2{

    NSLog(@"==== multiply ====");

    return int1 * int2;
}

@end

除法TwoIntOperationDivision:

//================== TwoIntOperationDivision.h ==================

@interface TwoIntOperationDivision : TwoIntOperation

@end



//================== TwoIntOperationDivision.m ==================

@implementation TwoIntOperationDivision

- (int)operationOfInt1:(int)int1 int2:(int)int2{

    NSLog(@"==== division ====");
    return int1/int2;
}

@end

现在关于算法的类都声明好了,我们最后声明一下 Context 类:

//================== Context.h ==================

@interface Context : NSObject

- (instancetype)initWithOperation: (TwoIntOperation *)operation;

- (void)setOperation:(TwoIntOperation *)operation;

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2;

@end



//================== Context.m ==================

@implementation Context
{
    TwoIntOperation *_operation;
}

- (instancetype)initWithOperation: (TwoIntOperation *)operation{

    self = [super init];
    if (self) {
        //injection from instane initialization
        _operation = operation;
    }
    return self;
}

- (void)setOperation:(TwoIntOperation *)operation{

    //injection from setting method
    _operation = operation;
}

- (int)excuteOperationOfInt1:(int)int1 int2:(int)int2{

    //return value by constract strategy instane
    return [_operation operationOfInt1:int1 int2:int2];
}

@end

Context类在构造器(init方法)注入了一个具体策略实例并持有它,而且Context也提供了set方法,让外部注入进来具体策略类的实例。

而策略的具体执行是通过Context的接口excuteOperationOfInt1:int2。这个接口是提供给客户端调用的;而且在它的内部其实调用的是当前持有的策略实例的执行策略的方法。

所以如果想使用哪种策略,只要将具体策略的实例传入到Context实例即可。

现在所有的类都定义好了,下面我们看一下具体如何使用:

int int1 = 6;
int int2 = 3;

NSLog(@"int1: %d    int2: %d",int1,int2);

//Firstly, using add operation
TwoIntOperationAdd *addOperation = [[TwoIntOperationAdd alloc] init];
Context *ct = [[Context alloc] initWithOperation:addOperation];
int res1 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of adding : %d",res1);

//Changing to multiple operation
TwoIntOperationMultiply *multiplyOperation = [[TwoIntOperationMultiply alloc] init];
[ct setOperation:multiplyOperation];
int res2 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of multiplying : %d",res2);


//Changing to substraction operation
TwoIntOperationSubstract *subOperation = [[TwoIntOperationSubstract alloc] init];
[ct setOperation:subOperation];
int res3 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of substracting : %d",res3);


//Changing to division operation
TwoIntOperationDivision *divisionOperation = [[TwoIntOperationDivision alloc] init];
[ct setOperation:divisionOperation];
int res4 = [ct excuteOperationOfInt1:int1 int2:int2];
NSLog(@"result of dividing : %d",res4);

看一下日至输出:

[13431:1238320] int1: 6    int2: 3
[13431:1238320] ==== adding ====
[13431:1238320] result of adding : 9
[13431:1238320] ==== multiply ====
[13431:1238320] result of multiplying : 18
[13431:1238320] ==== Substract ====
[13431:1238320] result of substracting : 3
[13431:1238320] ==== division ====
[13431:1238320] result dividing : 2

在上面的例子中,首先我们要使用加法,所以 实例化了加法策略类并传入到了Context类的构造器中。

而后续的乘法,减法,除法的更换,则是分别将它们的策略实例传入到了Context的set方法中,并执行即可。

下面看一下上面代码对应的类图。

代码对应的类图

策略模式代码示例类图

优点

  • 策略模式遵循开闭原则,用户可以在不修改原有系统的前提下选择和更换算法
  • 避免使用多重条件判断
  • 可以灵活地增加新的算法或行为
  • 提高算法和策略的安全性:可以封装策略的具体实现,调用者只需要知道不同策略之间的区别就可以

缺点

  • 客户端必须知道当前所有的具体策略类,而且需要自行决定使用哪一个策略类
  • 如果可选的方案过多,会导致策略类数量激增。

iOS SDK 和 JDK中的应用

  • JDK中的Comparator是策略模式的实现,可以使用不同的子类,也就是具体策略来解决不同的需求。

原文发布于微信公众号 - 程序员维他命(J_Knight_)

原文发表时间:2019-05-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券