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

栏目: 后端 · 发布时间: 7年前

内容简介:定义:中介者设计模式是通过中介对象封装一系列对象之间的交互,使对象之间不再相互引用,降低他们之间的耦合。中介者设计模式和观察者设计模式一样,都是通过消息的收发机制实现的,在观察者模式中,一个对象既可以是消息的发送者也是消息的接收者,对象之间信息交流依托于消息系统实现解耦。而中介者模式中消息发送送方只有一个,就是中介对象,而且中介对象不能订阅消息,只有那些活跃对象(订阅者)才可订阅中介者的消息,简单的理解可以看作是将消息系统封装在中介者对象内部,所以中介者对象只能是消息的发送者。实现原理

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

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

实现原理

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

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

废话不多说直接上代码;

// 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;
复制代码

小试牛刀,可否一用

在这里,我只需要订阅两个消息,然后让中介者发布;看看是否能够发布成功。

//单元测试
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 类;你所见到的代码会是这样的。

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

以上代码中,可以看出一个 React 组件,完全不见 React 周期函数,类函数过多 ,render 函数过于庞大;监听的方法也很多,阅读,维护,迭代成功过高。这段代码不管是对于开发者本身还是维护者,都不友好;迫切需要代码拆分,且实现结构层次清晰。

然而实际开发中,业务变更、迭代过快,有的业务本身复杂度极高,一个项目经手人也很多。如果代码不整洁,后来人就很难看懂,人们往往会对难以看懂的代码失去耐心,不愿意进一步了解。如果不能进一步了解一部分代码,也就难以改进它,这样的后果可能有两点:

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

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

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

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

项目实践

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

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

需求列表如下

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

一般要求:使用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();
  }
}
复制代码

生命周期的使用时机

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

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 生命周期中,实践业务数据转换

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

业务数据的来源:

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

深耕业务开发与设计

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

总结

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

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning jQuery

Learning jQuery

Jonathan Chaffer、Karl Swedberg / Packt Publishing / 2007-7-7 / GBP 24.99

jQuery is a powerful JavaScript library that can enhance your websites regardless of your background. In this book, creators of the popular jQuery learning resource, learningquery.com, share the......一起来看看 《Learning jQuery》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具