策略模式+发布订阅(优秀博文阅读笔记

69 阅读4分钟

策略模式,我粗浅来看就只是:

    if(x) funx()
    else if(y) funy

替换为

    const key = 'x' // 或者const key = 'y' 
    obj = {
        x: funx,
        y: funy
    }
    obj[key](); // 执行

大致是将原先的if-else中的条件当作对象key,内部执行作为对象value

在我的日常业务代码中,并不常将函数作为对象的value,也不甚知它的应用场景。

我常用的仅是将某一类型对应的数据存放在对象中,依据类型获取存放的相应数据。如下:

objMap = {
    typeA : {infoA1:1, infoA2:2, infoA3:3},
    typeB : {infoB4:4, infoB5:5, infoB6:6},
}

日常业务场景多较为简单,即使是不同类型,数据格式大致也相同,处理数据的方式也差不多,在一个函数中处理就好。
若每个类型需做的处理不同,将如上策略模式应用上未尝不可。

刚才看到这篇文章,大致观摩了一下:

为什么说网上99%的策略模式都有问题?带你设计一个工程上可用的策略模式

发布订阅策略模式结合在一起让我看到了策略模式在工程中的应用场景的另一种可能。
从中习得一二,望之后能应用在自己代码中。

出于可扩展性复杂逻辑的判断对默认情况、特殊情况的处理等几个维度的考虑。博主(指引用的文章的博主,不是我,下面再说博主也是指他)做出一些创作。

首先是对象的key值,在我日常代码中、一些普遍的策略模式科普文章中,key值仅是一个英文单词、简单易辨认。
博主做出另一种考虑:key值的形成受多个字段影响,可能是一个对象多个字段共同影响的结果,换句话说,key可以是一个字符串化的对象

可如果单纯将对象字符串化,也不一定能完全匹配。如下:

image.png

于是博主用Map来保存。
① 获取对象的key组成的数组,通过sort排序,
② 按顺序存入Map对象中
只要对象中存在那几个键值对,且对应的值相同,在map中就可以匹配。

image.png

在Map中存取数据都这样处理,只不过传入的key需要是一个对象,经过处理后变成对应的字符串。

代码解读

类型定义:

/**
 * 类型定义
 */
type emitNameType = "default" | "error";
type IfElseType = {
  // eventbus
  eventBus?: {
    default: Array<Function>;
    error: Array<Function>;
  };
};
interface ObjectType {
  id?: boolean;
  isOpen?: boolean;
  [key: string]: any;
}

定义class IfElseIfElse同时做了订阅者、发布者的角色。

ActionAdd 添加策略,参数为ActionAdd(参数1-对象组成的数组,参数2-回调函数)相当于指定某几个对象对应的函数。

想要调用该对象对应的函数时需用ActionExecute(参数-对象),执行策略。
如果该对象有对应的函数,则执行,若没有,则执行默认函数 default
如果有特殊情况也会做处理。

其中 emit即调用errordefault用的。同时处理特殊情况,如果没有兜底方案则抛出错误:throw new Error("没有这个事件");

主要方法其实就只有ActionAdd ActionExecuteemit。其中emit用于在ActionExecute执行时做兜底方案 image.png

/**
 * @des 要求用户的 action 方法传入。
 * 定义class
 */
class IfElse {
  MapHash: Map<string, Function>;
  config: IfElseType;
  constructor(config: IfElseType) {
    this.config = Object.assign({}, config);
    this.MapHash = new Map();
  }
  /**
   * @des 属性 和 方法 // 添加策略(添加订阅者
   * @param HashKey 属性object array数组
   * @param HashValue 方法
   */
  ActionAdd(HashKey: ObjectType[], HashValue: Function) {
    for (let i = 0; i < HashKey.length; i++) {
      const orderedMap = new Map();
      // 把对象的key取出来存在数组,排序
      const sortedKeys = Object.keys(HashKey[i]).sort();
      // 把对象里的东西挪到map对象里面
      for (const key of sortedKeys) {
        orderedMap.set(key, HashKey[i][key]);
      }
      // 把排序后的map转为对象再转为JSON字符串
      let Key = JSON.stringify(Object.fromEntries(orderedMap));
      // 直接将回调函数存在 MapHash 中
      this.MapHash.set(Key, HashValue);
    }
  }
  /**
   * @des 触发某一个事件
   * @param name
   * @param data 给function的值
   */
  emit = (name: emitNameType, data: any) => {
    if (!this.config.eventBus) return;
    if (this.config.eventBus[name]) {
      this.config.eventBus[name].forEach((element: Function) => {
        element(data);
      });
    } else {
      throw new Error("没有这个事件");
    }
  };
  // 执行策略(通知订阅者
  ActionExecute(HashKey: Record<string, any>, that?: any) {
    // 同 ActionAdd 处理传入的 HashKey。将其化为字符串
    const orderedMap = new Map();
    // 把对象的key取出来存在数组,排序
    const sortedKeys = Object.keys(HashKey).sort();
    // 把对象里的东西挪到map对象里面
    for (const key of sortedKeys) {
      orderedMap.set(key, HashKey[key]);
    }
    // 把排序后的map转为对象再转为JSON字符串
    // fromEntries: 可以将某些数据结构(比如 Map 或键值对数组)转换为普通对象
    let Key = JSON.stringify(Object.fromEntries(orderedMap));

    if (!this.MapHash.get(Key)) {
      this.emit("default", "触发默认方法");
      return;
    }
    let Fn = this.MapHash.get(Key)!;
    if (that) Fn.bind(that);
    try {
      Fn();
    } catch {
      this.emit("error", "报错示例");
    }
  }
}

image.png

使用

// 创建实例
let res2 = new IfElse({
  eventBus: {
    default: [(e: any) => console.log("触发默认方法:", e)],
    error: [(e: any) => console.log("触发报错:", e)],
  },
});
// 定义常量
const Age20_SchoolA_RegionCN = { age: 20, school: "A", region: "cn" };
const Age20_SchoolA_RegionCN_WeacherRainy = {
  age: 20,
  school: "A",
  region: "cn",
  weacher: "rainy",
};
const Age20_SchoolB = { age: 20, school: "B" };
const AgeNo22_SchoolA = { age: 22, school: "A" };
const AgeNo22_SchoolB = { age: 22, school: "B" };

// 添加策略
function backFun(workHours: number) {
  console.log("高", workHours);
  return workHours * 25;
}
res2.ActionAdd([Age20_SchoolA_RegionCN], () => backFun(10));
// 执行策略,如果没有匹配的则触发默认方法
res2.ActionExecute({ age: 20, school: "A", region: "cn" }); // 高