中介者设计模式——业务实践

定义:中介者设计模式是经过中介对象封装一系列对象之间的交互,使对象之间再也不相互引用,下降他们之间的耦合。javascript

中介者设计模式和观察者设计模式同样,都是经过消息的收发机制实现的,在观察者模式中,一个对象既能够是消息的发送者也是消息的接收者,对象之间信息交流依托于消息系统实现解耦。而中介者模式中消息发送送方只有一个,就是中介对象,并且中介对象不能订阅消息,只有那些活跃对象(订阅者)才可订阅中介者的消息,简单的理解能够看做是将消息系统封装在中介者对象内部,因此中介者对象只能是消息的发送者。前端

实现原理java

image.png | left | 728x414

建立中介者对象(调度中心)

废话很少说直接上代码;git

// eventeimtter.js

// 建立中介者对象(调度中心)
class EventEimtter {
  constructor() {
    // 建立消息对象
    this.$event = {};
  }
  /** * 检测消息对象是否存在,不存在则初始化该消息 * @param {*} event */
  checkEvent(event) {
    if (!this.$event) {
      this.$event = {};
    }

    if (!this.$event[event]) {
      this.$event[event] = [];
    }
  }
  /** * 订阅消息 * @param {*} type 消息类型 * @param {*} action * @param {*} context 消息做用域上下文 */
  on(type, action, context = null) {
    this.checkEvent(type);
    this.$event[type].push(action.bind(context));
    return this;
  }
  /** * 发送消息 * @param {*} type * @param {...any} args */
  emit(type, ...args) {
    if (!this.$event[type]) {
      this.$event[type] = [];
    }
    this.$event[type].forEach(func => {
      func(...args);
    });
    return this;
  }
  /** * 仅能发送一次 * @param {*} type * @param {*} action * @param {*} scope 做用域 */
  once(type, action, scope = null) {
    this.checkEvent(type);
    const newfn = (...args) => {
      this.off(type, action);
      action.call(scope, ...args);
    };
    this.on(type, newfn);
    return this;
  }
  /** * 移除已经订阅的消息 * @param {*} type * @param {*} action */
  off(type, action) {
    const $event = this.$event[type];
    if ($event) {
      for (let i in $event) {
        if ($event[i] === action) {
          $event.splice(i, 1);
          break;
        }
      }

      if (!$event.length) {
        delete this.$event[type];
      }
    }

    return this;
  }
  /** * 移除某个的类型消息 * @param {*} type */
  removeListener(type) {
    delete this.$event[type];
    return this;
  }
  /** * 移除全部订阅消息 */
  removeAllListener() {
    this.$event = null;
    return this;
  }
  /** * 获取全部的消息类型 */
  getEvent() {
    return this.$event;
  }
}


export default EventEimtter;
复制代码

小试牛刀,能否一用

在这里,我只须要订阅两个消息,而后让中介者发布;看看是否可以发布成功。github

//单元测试
import EventEimtter from './eventeimtter';

const event = new EventEimtter();

// 订阅 demo 消息,执行回调函数 ———— 输出 first
event.on('demo', () => {
  console.log('first');
});
// 订阅 demo 消息,执行回调函数 ———— 输出 second
event.on('demo', () => {
  console.log('second');
})

// 发布 demo 消息
event.emit('demo')
// first
// second
复制代码

业务价值的产生,实际开发中的实践

先说痛点,在实际的项目开发中一个页面 js 可能有十几个 class 类;你所见到的代码会是这样的。ajax

image.png | left | 827x1279

以上代码中,能够看出一个 React 组件,彻底不见 React 周期函数,类函数过多 ,render 函数过于庞大;监听的方法也不少,阅读,维护,迭代成功太高。这段代码不论是对于开发者自己仍是维护者,都不友好;迫切须要代码拆分,且实现结构层次清晰。后端

然而实际开发中,业务变动、迭代过快,有的业务自己复杂度极高,一个项目经手人也不少。若是代码不整洁,后来人就很难看懂,人们每每会对难以看懂的代码失去耐心,不肯意进一步了解。若是不能进一步了解一部分代码,也就难以改进它,这样的后果可能有两点:设计模式

  • 重构,代码被抛弃
  • 直接复制这段代码在别的地方使用

下面是我站在前端的角度去思考业务:浏览器

image.png | left | 827x664

  1. 业务数据:负责获取业务数据
  2. 业务逻辑:实现产品所定义的规则
  3. 逻辑数据:经过一系列规则所产出的逻辑数据
  4. 视图数据:经过逻辑数据转换成视图数据(不将逻辑和视图直接绑定)
  5. 视图展现:经过视图数据,直接驱动视图层展现对应视图
  6. 视图功能:经过视图展现组装成的需求功能

在简单的业务需求中,可能我拿到的后端数据,就直接能够去渲染视图层,而后就完善功能。从开发的成本和复杂度上考量上,是不值得去作业务拆分。因此,在复杂的业务需求中以及兼顾拆分和维护中,这种业务方法论就能够大展手脚了。如下,我就拿开头的例子,详细解析围绕业务的6大部分的设计。函数

项目实践

我始终坚信技术的价值是在业务中产生的,技术自己是没有价值的,技术的价值取决因而否能在项目中落地以及解决业务的痛点。做为中介者模式在项目中的落地,先举一个小栗子!

image.png | left | 827x265

需求列表以下
  • 一个分页表格, 分别有网点名称、网点地址、联系电话、操做栏四列。
  • 每一行操做栏有三个按钮,分别是 桌位管理、页面装修、功能设置

通常要求:使用 zent 分页表格 Table 组件,配置好 columns ,操做栏定制渲染;更加简易的拓展以及敏捷的操做,固然维护和开发的成本也须要考虑的。

使用 zent table 组件开发,受益于 React 数据驱动的思想,columns 是以 props 传入;columns 中的定制渲染,可能须要涉及到父子组件之间的通讯。

在正常的开发中,咱们能够这么作。

const event = new EventEimtter();


const columns = [
  ...,
  {
    title: '操做',
    bodyRender: (rowData) => {
      return (
        <div> <Button onClick={() => { event.emit('page-decoration', rowData) }}> 桌位装修 </Button> <Button onClick={() => { event.emit('desk-manage', rowData) }}> 桌位装修 </Button> <Button onClick={() => { event.emit('action-setting', rowData) }}> 桌位装修 </Button> </div>
      );
    }
  },
  ....
]

// Action 消息处理函数实体类,业务逻辑源码
class Action {
  handlerPageDecoration() {
    ...
  }
  handlerDeskManage() {
    ...
  }

  handlerActionSetting() {
    ...
  }
}

const action = new Action()


class Demo extends Component {
  componentWillMount() {
    // 订阅消息
    event.on('page-decoration', action.handlerPageDecoration, this)
    event.on('desk-manage', action.handlerDeskManage, this)
    event.on('action-setting', action.handlerActionSetting, this)
  }

  render() {
    return (
      <Table columns={columns} ...props/>
    );
  }

  componentWillUnmount() {
    // 当该组件销毁时,取消因此监听事件;不然内存会炸掉
    event.removeAllListener();
  }
}
复制代码
生命周期的使用时机

image.png | left | 827x841

React 生命周期

  • constructor:尽可能简洁,只作最基本的 state 初始化
  • willMount: 一些内部使用变量的初始化
  • render: 触发很是频繁,尽可能只作渲染相关的事情
  • didMount: 一些不影响初始化的操做应在这里完成,好比根据浏览器不一样进行操做,ajax获取数据,监听 document 事件等(server render)。
  • willUnmount:销毁操做,销毁计时器、销毁本身的事件监听等
  • willReceiveProps: 当有 props 作 state 时,监听 props 的变化去改变 state,在这个生命周期里 setState 不会触发两次渲染
  • shouldComponentUpdate:手动判断组件是否应该更新,避免由于页面更新作成的无谓更新,组件的重点优化之一。
  • willUpdate:在 state 变化后若是须要修改一些变量,能够在这里执行
  • didUpdate: 与 didMount 相似,进行一些不影响到 render 的操做, update 相关的生命周期里最好不要作 setState 操做,不然容易形成死循环。
在 React 生命周期中,实践业务数据转换

image.png | left | 826x667

业务数据的来源:

  • ReactCompoent 在 willMount 时,初始化的 state、props中获取
  • didMount 时 Ajax 获取的数据 业务逻辑(业务规则):
  • 处理业务规则的源码,根据不一样的规则,对业务数据进行处理
  • 产生逻辑数据
  • 须要在 constructor  或者 willMount  中完成业务逻辑的订阅 逻辑数据:
  • 使用业务逻辑处理产生,同步到视图数据 试图数据:
  • 同步逻辑数据的,中间可加 hook 视图展现:
  • 根据视图数据单项 render

深耕业务开发与设计

image.png | left | 827x410

总结

同观察者模式同样,中介者模式的主要业务也是经过模块间或者对象间的复杂通讯,来解决模块间或对象的耦合。对于中介者对象的本质是分装多个对象的交互,而且这些对象的交互通常都是中介者内部实现的。

与外观模式的封装特性相比,中介者模式对多个对象的交互封装,且这些对象通常处于同一层面上,而且封装的交互在中介者内部,而外观模式封装的目的是为了提供更简单的易用接口,而不会添加其余功能。

相关文章
相关标签/搜索