桥接模式:跨平台的事件机制设计

内容列表

对于 Web 的图表组件库来说,一些功能比较强大的开源库,渲染层可以支持 DOM、SVG、Canvas、WebGL 等多个平台的环境,而图表库的很多功能的实现都和渲染层紧密相关。

最近,在参考学习一些开源的图表组件库时,发现在跨平台设计中,事件机制的实现很有意思,所以在这里以最简化的代码来解释和记录一下这个方案。如果要用经典的软件设计模式来解释,大概就是桥接模式了。

桥接模式(Bridge Pattern) 将一个功能的实现拆分为抽象(Abstraction)和实现(Implementor),让其相互独立的扩展和定义,借助该模式可以设计一种平台无关的软件架构。

事件机制

事件机制是软件设计中最基础、最为常见的一种设计,对于 Web 图表组件库来说要提供一些处理用户交互(例如点击、拖动、右键点击等)的机制。一个典型的事件模型类如下:

class EventEmitter {
  _handlerMap = {};
  on(event, callback) {}
  off(event, callback) {}
  emit(event, ...args) {}
}

对于用户来说,对外暴露 on()off() 方法来注册和取消事件,而图表库内部需要完成事件触发(emit())的实现,而这里与渲染层耦合。以渲染层为 DOM 实现来举例,支持点击事件:

class Chart {
  constructor() {
    // 渲染层为 DOM 实现
    this.__renderer = new DOMRenderer();
    this._handler = new EventEmitter();
  }

  on(...args) {
    this._handler.on(...args);
  }

  off(...args) {
    this._handler.off(...args);
  }

  __bindEvent() {
    // ! 事件触发(绑定)与渲染层耦合
    this.__renderer.domElem.addEventListener("click", (event) => {
      this._handler.emit("click", ...[event, ...otherArgs]);
    });
  }
}
跨平台实现

参考桥接模式,这里可以把图表类中的事件机制实现拆分为抽象(Handler)和实现(HandlerProxy),前者管理用户注册的事件池,后者负责特定平台的事件触发实现。示例代码如下:

class Handler extends EventEmitter {
  constructor(handlerProxy) {
    super();

    this.__handlerProxy = handlerProxy;

    // 注册事件到代理类中
    this.__handlerProxy.on("click", (event, ...args) => {
      // ! 触发用户注册的事件
      this.emit(event, ...args);
    });
  }
}

class DOMHandlerProxy extends EventEmitter {
  constructor(renderer) {
    super();

    this.__renderer = renderer;
  }

  __bindEvent() {
    // 根据渲染层的平台实现事件绑定,以 DOM 实现为例
    this.renderer.domElem.on("click", (event, ...args) => {
      // ! 触发 Handler 注册的事件
      this.emit(event, ...args);
    });
  }
}

对于图表类来说,Handler 类提供了完整的事件机制,但其内部把具体平台相关的事件触发实现交给 HandlerProxy 类去实现。这样就完成了事件机制的实现与特定平台实现分离的目标,针对不同平台实现不同的HandlerProxy 类即可。现在图表类的代码应该如下:

class Chart {
  constructor() {
    // 渲染层为 DOM 实现
    this.__renderer = new DOMRenderer();
    this._handler = new Handler(new DOMHandlerProxy(this.__renderer));
  }

  on(...args) {
    this._handler.on(...args);
  }

  off(...args) {
    this._handler.off(...args);
  }
}

现在来看,图表类中之前事件触发实现与平台相关的代码已经被独立出去,且可以根据不同的渲染层实现完成无缝衔接。

结语

以上就是利用桥接模式对跨平台的事件机制的简化设计,解决此类问题时,最重要的是划分抽象实现两部分。

参考

相关

Nginx 配置

2018-03-15

Nginx 作为一个轻量、高性能的服务器,近年来颇受欢迎,无论是生产环境还是开发环境都有其发挥作用的地方,其配置文件相对来说还是较为简单的。而且,现在 nginx 也支持 Windows 环境了,利用不同的配置可以满足我们不同的需求。

了解更多

单元测试工具:Junit

2017-08-30

通常一个项目的代码量是比较大的,而且其中逻辑也较为复杂,在开发完成后再进行项目测试其实是比较耗费时间和精力的,因此边开发边测试是个很好的选择,而 JUnit 则为我们提供了这样的便利。

了解更多

Web 应用:单页面应用与路由

2017-10-25

现在,Web 技术不仅仅是局限于页面的开发技术,在应用的开发方面也是一种潮流,B/S 架构的技术是一种趋势。而像一般的管理型 Web 应用,不注重 SEO,非常适合单页面应用(SPA)的实现方式,而路由功能则是单页面应用的核心技术。

了解更多