策略模式和发布订阅模式的使用场景

开头

  • 好久不见;
  • 我又来了,下周分享会到我了,提前准备好;
  • 绞尽脑汁不知道讲点啥,简单的怕被嫌弃,难的又讲不懂 🙃
  • 该文的定义/术语等摘抄大量同类文章,掘金上相关文档都翻遍了,这摘点那摘点,我都忘了有哪些了,就不一一放链接了;
  • 代码技术栈: react + 少少量的antd;

什么是设计模式

设计模式是一种可复用的解决方案,用于解决软件设计中遇到的常见问题;

通俗的讲 设计模式是一套被反复使用,多数人知晓的,经过分类的,代码设计经验的总结。是在开发过程中,针对特殊问题/场景的更优的解决方案

怎么用

设计模式的核心操作是去观察你整个逻辑里面的变与不变,然后将变与不变分离,达到使变化的部分灵活、不变的地方稳定的目的。

设计模式分类

什么桥接模式、建造者模式、生成器模式啥的。这些今天都不会讲😂,我也不知道是干嘛的, 今天就说两个前端常用的,

策略模式

它的定义很精简:一个类的行为或其算法可以在运行时更改。我们把它降维到代码层面,用人话翻译一下就是,运行时我给你这个类的方法传不同的“key”,你这个方法会执行不同的业务逻辑。
细品一下,和if else好像没啥区别?

  • 什么时候用:

    在阿里的开发规范里有这样一条 :超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。
    说概念有点干 举个小例子🌰
    **有这样一个场景,在请求某个table数据的时候,后端有时会返回状态码,前端需要根据不同的状态码来展示不同的语句,我找到了我去年重构uproxy页面写的一段代码,

image.png

呃。 一言难尽。。。也不知道当时怎么想的,就硬if else,你好歹用个switch也比这个强啊。那个any类型也就不提了,感谢组长不杀之恩,复审竟然过了;

这样写其实也不是不行,对用户来说,最起码运行不报错,效果也符合产品预期。但对开发来说,首先代码过于冗余,不易阅读; 其次如果后期要再添加/修改某个状态,我只能继续else if,不利于拓展和维护;

既然不行咱就改改 先看看switch版本的:

const generateStatus = (text: string) => {
  switch(text) {
    case 'STATUS_OK':
      return { styles: globalStyle.suc, text: '运行' };
    case 'STATUS_UNKNOWN':
      return { styles: globalStyle.info, text: '未知状态' };
    case 'STATUS_FAIL':
      return { styles: globalStyle.fail, text: '故障', };
    case 'STATUS_STOPPED':
      return { styles: globalStyle.stop, text: '停止' };
    default: return { styles: '', text };
  }
};

render: (status: string) => {
  const { styles, text } = generateStatus(status); 
  return (
    <span className={styles}>{text}</span>
  );
}

感觉好多了。。。
当然,除了用switch这种写法,还可以用上面主要讲的策略模式:

// 1.创建一个对象
const statusData = {
  'STATUS_OK': { styles: globalStyle.suc, text: '运行' },
  'STATUS_UNKNOWN': { styles: globalStyle.info, text: '未知状态' },
  'STATUS_FAIL': { styles: globalStyle.fail, text: '故障' },
  'STATUS_STOPPED': { styles: globalStyle.stop, text: '停止', },
};

render: (status: string) => {
  return (
    <span className={statusData[status]?.styles ?? ''}>{statusData[status]?.text ?? text}</span>
  );
}

延伸一下:
这里是把status参数当成key值来拿到对应的数据。但在有些时候,上面这种写法可能不能满足我的要求。

这里找不到合适的例子,我把需求魔改一下,比如:当前的status是个对象,且没有具有唯一性的属性,比如:

type IType = 'uproxy' | 'ushard';
type ICode = 'STATUS_OK' | 'STATUS_UNKNOWN' | 'STATUS_FAIL' | 'STATUS_STOPPED'

render: (status: {
	type: IType,
  code: ICode,
}) => {
	//....比如这种情况;
};

// 这种情况下也可以用map来实现 (map和object的区别是map可以 value: value);
const statusData = new Map([
  [{type: 'uproxy', code: 'STATUS_OK'}, {styles: globalStyle.suc, text: 'uproxy运行'}];
  [{type: 'ushard', code: 'STATUS_OK'}, {styles: globalStyle.suc, text: 'ushard运行'}];
])

render: (status) => {
  const statusData = [...statusData].find(([key,value]) => 
    key.type === status.type && key.code === status.code
  );
	return (
    <span className={statusData[1].styles}>{statusData[1].text}</span>
	);
}

总结:

  • 使用策略模式的优点是将一个个逻辑封装起来,并可以任意的替换。在逻辑较多的场景下,代码比直接if else好维护些
  • 但从上面的例子也可以看出,即使用了策略模式,该写的逻辑还是要写,并不能减少很多的代码量,并且策略越多,拆分组合的过程就会越复杂,所以在使用过程中要合理运用。针对不同的场景,选择合适的解决方案。
发布订阅模式

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。

不知道大家有没有发现,** 项目里的eventEmitter就是通过发布-订阅模式来实现的,下面是我从** 项目里粘贴的代码

class EventEmitter {
  constructor() {
    this.events = {}; //事件存储中心;
    this.addListener.bind(this);
    this.removeListener.bind(this);
    this.emit.bind(this);
  }

  protected events: {[key: string]: any};

  public addListener(event: string, listener: (...args: any[]) => void) {
    // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列
    (this.events[event] || (this.events[event] = [])).push(listener);
  }

  public removeListener(event: string, listener: (...args: any[]) => void) {
    if (this.events[event]) {
      this.events[event].splice(this.events[event].indexOf(listener), 1);
    }
  }

  //发布事件。 args 用于收集发布事件时传递的参数
  public emit(event: string, ...args: any[]) {
    if (this.events[event]) {
      this.events[event].forEach((listener: any) => {
        listener(...args);
      });
    }
  }

}

const eventEmitter = new EventEmitter();

export default eventEmitter;

要使用的话也很简单,类似于js的addEventListener和removeEventListener;

🌰 : 在某些场景下我们需要监听一下localstorate的变化,但是js原生并没有这样的api来达到我们的目的。我们可以把localstorage的setItem方法重新给封装一下,然后通过EventEmitter来监听localStorage的变化;

//1.设置监听
React.useEffect(() => {
  eventEmitter.addListener('storageEvent', handleStorageChange);
  return () => {
    eventEmitter.removeListener('storageEvent', handleStorageChange);
  };
}, []);

//2.封装一个自定义的localStorage的setItem的方法;
//每次在localStorage中写入/修改数据的时候都会调用emit函数发布事件,并把key,value给传递过去;
const setItem = (key, value) {
  eventEmitter.emit('storageEvent', {key, value});
  return localStorage.setItem(key, value);
}
  • 完结, 撒花。。。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值