木灵鱼儿

木灵鱼儿

阅读:189

最后更新:2022/07/05/ 1:53:21

行为模式:策略模式

简介

策略模式: 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

简单点来说就是,我现在有10个算法,可能会根据不同的状态使用不同的算法,一般的做法就是if else或者switch这种判断方式,这样的话就跟状态模式一样,后续新增的算法和状态判断又得套一层if判断,这样的话代码的可读性就下降,且维护起来比较麻烦。

我们来看个代码例子:

function calc(type: number, a: number, b: number) {
  if (type === 1) {
    return a + b;
  } else if (type === 2) {
    return a - b;
  } else if (type === 3) {
    return a * b;
  } else if (type === 4) {
    return a / b;
  } else {
    throw new Error("Invalid type");
  }
}

针对这种多个if判断的代码,我们常见的优化方式就是通过表驱动的方式进行管理,通过一个键值对象来存储方法,使用的时候直接调用对应的属性方法即可。

const calcFns = {
  add: (a: number, b: number) => a + b,
  subtract: (a: number, b: number) => a - b,
  multiply: (a: number, b: number) => a * b,
  divide: (a: number, b: number) => a / b,
};

但是事实上这种方式的优化其实也并不是很好,因为在实际的代码中,一个算法可能会有15行以上的高度,这就极大的增加了代码的阅读难度和维护成本,会带来理解上的困难。

所以在面向对象编程方面,使用策略模式来进行优化,他将算法的实现与使用算法进行分割,通过一个环境类来统一调用不同的算法类,环境类依赖算法类的接口,最后由客户去决定使用哪一种算法,使用时将算法实例传入环境类中,通过环境类的统一的接口方法得到结果。

所以他会有三个角色:

  1. 抽象策略角色:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现
  2. 具体策略角色:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境类角色:持有一个策略类的引用,最终给客户端调用。

代码实现

//抽象策略
abstract class Calc {
  public abstract calculate(a: number, b: number): number;
}

//加法策略
class Add extends Calc {
  public calculate(a: number, b: number): number {
    return a + b;
  }
}

//减法策略
class Subtract extends Calc {
  public calculate(a: number, b: number): number {
    return a - b;
  }
}

//乘法策略
class Multiply extends Calc {
  public calculate(a: number, b: number): number {
    return a * b;
  }
}

//除法策略
class Divide extends Calc {
  public calculate(a: number, b: number): number {
    return a / b;
  }
}

//环境类
class Context {
  private calc: Calc | null = null;

  public setCalc(calc: Calc) {
    this.calc = calc;
  }

  //得到结果
  public getResult(a: number, b: number): number {
    if (this.calc === null) {
      throw new Error("Calc is null");
    }
    return this.calc.calculate(a, b);
  }
}

//客户端调用
class Client {
  constructor() {
    const context = new Context();
    //计算加法
    context.setCalc(new Add());
    console.log(context.getResult(1, 2));
    //计算减法
    context.setCalc(new Subtract());
    console.log(context.getResult(1, 2));
    //计算乘法
    context.setCalc(new Multiply());
    console.log(context.getResult(1, 2));
    //计算除法
    context.setCalc(new Divide());
    console.log(context.getResult(1, 2));
  }
}

new Client();

我们使用什么算法就new出对应的实例传递给环境对象,虽然不是很明白为啥需要一个环境类做一个中转,可能是为了解耦,反正就是很面向对象。

我猜测就是,如果有一天这个算法发生很大的改变,我们可以不改变客户端调用的代码,而在环境类中做特殊处理。

应用场景

  1. 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  2. 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  3. 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  4. 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  5. 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

扩展

在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度。

说白了就是我们无法避免不使用if,switch的判断,因为这个判断一般都是在调用算法之前,用于判断使用哪个算法的,所以,策略工厂模式就是将这个判断移到了环境类中,它接受一个type类型,通过类型来判断使用哪个算法。

class Context {
  private calc: Calc | null = null;

  //得到结果
  public getResult(type: number, a: number, b: number): number {
    if (type === 1) {
      this.calc = new Add();
    } else if (type === 2) {
      this.calc = new Subtract();
    } else if (type === 3) {
      this.calc = new Multiply();
    } else if (type === 4) {
      this.calc = new Divide();
    }

    if (this.calc === null) {
      throw new Error("Calc is null");
    }
    return this.calc.calculate(a, b);
  }
}

这就类似于代价转移,将客户端分散的代码统一到一处进行管理,当然这是针对策略过多的情况下使用的。兜兜转转还是回到了以前。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 189

相关文章