内容简介:定义:中介者设计模式是通过中介对象封装一系列对象之间的交互,使对象之间不再相互引用,降低他们之间的耦合。中介者设计模式和观察者设计模式一样,都是通过消息的收发机制实现的,在观察者模式中,一个对象既可以是消息的发送者也是消息的接收者,对象之间信息交流依托于消息系统实现解耦。而中介者模式中消息发送送方只有一个,就是中介对象,而且中介对象不能订阅消息,只有那些活跃对象(订阅者)才可订阅中介者的消息,简单的理解可以看作是将消息系统封装在中介者对象内部,所以中介者对象只能是消息的发送者。实现原理
定义:中介者 设计模式 是通过中介对象封装一系列对象之间的交互,使对象之间不再相互引用,降低他们之间的耦合。
中介者设计模式和观察者设计模式一样,都是通过消息的收发机制实现的,在观察者模式中,一个对象既可以是消息的发送者也是消息的接收者,对象之间信息交流依托于消息系统实现解耦。而中介者模式中消息发送送方只有一个,就是中介对象,而且中介对象不能订阅消息,只有那些活跃对象(订阅者)才可订阅中介者的消息,简单的理解可以看作是将消息系统封装在中介者对象内部,所以中介者对象只能是消息的发送者。
实现原理
创建中介者对象(调度中心)
废话不多说直接上代码;
// 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 函数过于庞大;监听的方法也很多,阅读,维护,迭代成功过高。这段代码不管是对于开发者本身还是维护者,都不友好;迫切需要代码拆分,且实现结构层次清晰。
然而实际开发中,业务变更、迭代过快,有的业务本身复杂度极高,一个项目经手人也很多。如果代码不整洁,后来人就很难看懂,人们往往会对难以看懂的代码失去耐心,不愿意进一步了解。如果不能进一步了解一部分代码,也就难以改进它,这样的后果可能有两点:
- 重构,代码被抛弃
- 直接复制这段代码在别的地方使用
下面是我站在前端的角度去思考业务:
- 业务数据:负责获取业务数据
- 业务逻辑:实现产品所定义的规则
- 逻辑数据:通过一系列规则所产出的逻辑数据
- 视图数据:通过逻辑数据转换成视图数据(不将逻辑和视图直接绑定)
- 视图展示:通过视图数据,直接驱动视图层展示对应视图
- 视图功能:通过视图展示组装成的需求功能
在简单的业务需求中,可能我拿到的后端数据,就直接可以去渲染视图层,然后就完善功能。从开发的成本和复杂度上考量上,是不值得去做业务拆分。所以,在复杂的业务需求中以及兼顾拆分和维护中,这种业务方法论就可以大展手脚了。以下,我就拿开头的例子,详细解析围绕业务的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
深耕业务开发与设计
总结
同观察者模式一样,中介者模式的主要业务也是通过模块间或者对象间的复杂通信,来解决模块间或对象的耦合。对于中介者对象的本质是分装多个对象的交互,并且这些对象的交互一般都是中介者内部实现的。
与外观模式的封装特性相比,中介者模式对多个对象的交互封装,且这些对象一般处于同一层面上,并且封装的交互在中介者内部,而外观模式封装的目的是为了提供更简单的易用接口,而不会添加其他功能。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 设计模式(二十三)中介者模式
- 设计模式 | 中介者模式及典型应用
- 《JavaScript设计模式与开发实践》模式篇(11)—— 中介者模式
- 中介者设计模式(Mediator Design Pattern)
- 行为型模式:中介者模式
- 中介者模式【Mediator Pattern】
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。