使用 ES decorators 构建一致性 API

简介: ![pic] 重用和一致性是程序设计中经久不衰的两个课题。在最新的 ES Proposal 中,「decorators 语法」为此带来了一定的便利,并且,很适合应用于大型的类库中。 ---------------------------------------------- ## 装饰模式 提到 decorator 大家都不会陌生,即「装饰模式」—— 我们可以在「不侵入原有

![pic]

重用和一致性是程序设计中经久不衰的两个课题。在最新的 ES Proposal 中,「decorators 语法」为此带来了一定的便利,并且,很适合应用于大型的类库中。


装饰模式

提到 decorator 大家都不会陌生,即「装饰模式」—— 我们可以在「不侵入原有代码」的情况下,为代码增加一些「额外的功能」。

所谓「额外的功能」一般都比较独立,不和原有逻辑耦合,只是做一层包装。你也可以把它看成「包装模式」。形如:

// 旧方法
function func() {}

// 包装后的新方法
function funcWrapped() {

  // 有的没的
  doSomethingBefore(); 
  
  // 旧方法的过程本身并不变化
  func();
  
  // 这啊那的
  doSomethingAfter();
}

这样看来,有一些场景特别适用这个模式,比如:

  • 记录方法的开始执行和结束执行。
  • 为运算过程提供额外的缓存能力。
  • 标记方法为 deprecated。
  • 等等。

编写一个装饰器

如果有好多方法都想包上这种「额外的功能」,那么我们不会一个个地去改写,而是考虑抽出一个「装饰器」—— 它能够接受原方法,然后生成包装后的方法。比如,我们想记录所有方法的运行时间:

function performanceTimingDecorator(func) {

  // 返回包装后的新方法
  return function(...args) {
    const start = Date.now();
    func(...args);
    const end = Date.now();
    const t = end - start;
    
    console.log(`${func.name} performed ${t}ms.`);
  };
}

function func() {}

const funcWrapped = performanceTimingDecorator(func);

// func performed 2ms.
funcWrapped();

使用 ES decorators

如果一个系统内需要大量运用装饰器,那么上述的写法可读性还有待提高。ES decorators 解决了这个问题,这是一个新的语法(语法糖):

// 定义 decorator
function performanceTiming(...args) {

  // 返回包装后的方法
  return function(target, key, descriptor) {
    // ...
  };
}

class MyClass {
  
  // 使用这种语法修饰方法 func
  @performanceTiming
  func() {}
}

新的 decorator 语法 @xxx 的形式非常类似 Java Annotation,不过后者作为静态语言,其 Annotation 的实现机制以及使用场景和 ES decorators 都有区别,这是一个题外话。事实上,ES decorators 完全借鉴自 Python 的 decorators。

同时,聪明的你应该发现,相比手写装饰器,新的语法中其实「该写的东西一个都没少」。那这个 decorators 语法有什么意义呢?

在我看来,这种语法糖对 decorators 的「定义」和「调用」都做了收敛,带来了「形式美感」。说人话,可读性更好。

  1. 在 decorators 定义时,约束了装饰器的输入(固定的几个相关参数)和输出(返回一个 function),使所有装饰器风格得到收敛。
  2. 在 decorators 调用时,以无侵入的语法「修饰」类或方法,可维护性和可读性都提升很多。

这两个优势,让我想到 ES decorators 的一个重要使用场景,便是应用于构架一致性 API。

构架一致性 API

对于多人开发的大型类库来说,「一致性」是很重要同时也很难执行的一个课题。这里的「一致性」包括:

  1. 各模块提供一致的标准公用功能。
  2. 公用功能的实现和调用方式也保持一致。
  3. 整体 API 的风格一致。

其中 1、2 两点可以通过引入 ES decorators 机制来更好地达到。

实践演示

先封装好部分 decorators(可参见 @ali/universal-decorator 这个包),这里选取两个装饰器:

  • @deprecated - 用于修饰类的方法,如果方法被调用,则在 console 中提示此方法已经过时,以便开发者转而调用其他方法。
  • @moduleLevel - 这是 Rax 体系下模块类的一个静态成员标准字段,可取值为几个有限的枚举,此装饰器对此做了约束。

接下来具体地应用到库中。

例如 @ali/universal-tracker 中,report() 方法已经迁移到了 @ali/universal-goldlog,原方法已经废弃,则可以写作:

import {deprecated} from '@ali/universal-decorator';

class Tracker {

  @deprecated('This method is moved to universal-goldlog.', {
    url: 'http://web.npm.alibaba-inc.com/package/@ali/universal-goldlog'
  })
  report() {
    // ...
  }
}

然后在调用 report() 后则会提示:

![deprecated-result]

这样,在相关的所有库中都引入类似的装饰器,从而保证 API 表达上的一致,并且这些公共逻辑遵循一致的实现。

另外还有一个例子,可以用来对类的字段做约束。以大量基于 Rax 的页面模块为例,这些模块 class 需要声明一个静态属性 moduleLevel 是 app 级别还是 page 级别,以便于框架将其渲染到对应的容器中。但是静态成员的赋值不够清晰明朗,也不能对枚举值做约束。使用 decorators 来改写则:

import {moduleLevel} from '@ali/universal-decorator';

@moduleLevel('page')
class MyModule1 {}

@moduleLevel('other')
class MyModule2 {}

moduleLevel 这个 decorator 将为类赋上一个名为 moduleLevel 的静态成员,并且会对传入值作判断,如果入参不是 'page''app',则发出警告:

![module-level-result]

最后,由于使用了 ES decorators 语法的代码,类似于一种声明式的标记,所以更利于我们对这些代码作静态分析,比如进一步的提前校验,或是条件编译等等。这部分更多的想法和思路,有待发掘。

引用

  1. Exploring EcmaScript Decorators

题图:一棵被装饰得五光十色的圣诞树。很多涉及到 decorator 的文章动不动就拿圣诞树来举例子,俨然 Christmas tree 是 decorate 的固定宾语。?

目录
相关文章
|
6天前
|
SQL 缓存 测试技术
构建高性能RESTful API:最佳实践与避坑指南###
—— 本文深入探讨了构建高性能RESTful API的关键技术要点,从设计原则、状态码使用、版本控制到安全性考虑,旨在为开发者提供一套全面的最佳实践框架。通过避免常见的设计陷阱,本文将指导你如何优化API性能,提升用户体验,确保系统的稳定性和可扩展性。 ###
39 12
|
6天前
|
存储 SQL API
探索后端开发:构建高效API与数据库交互
【10月更文挑战第36天】在数字化时代,后端开发是连接用户界面和数据存储的桥梁。本文深入探讨如何设计高效的API以及如何实现API与数据库之间的无缝交互,确保数据的一致性和高性能。我们将从基础概念出发,逐步深入到实战技巧,为读者提供一个清晰的后端开发路线图。
|
3天前
|
JSON JavaScript API
深入浅出Node.js:从零开始构建RESTful API
【10月更文挑战第39天】 在数字化时代的浪潮中,API(应用程序编程接口)已成为连接不同软件应用的桥梁。本文将带领读者从零基础出发,逐步深入Node.js的世界,最终实现一个功能完备的RESTful API。通过实践,我们将探索如何利用Node.js的异步特性和强大的生态系统来构建高效、可扩展的服务。准备好迎接代码和概念的碰撞,一起解锁后端开发的新篇章。
|
6天前
|
存储 前端开发 搜索推荐
淘宝 1688 API 接口助力构建高效淘宝代购集运系统
在全球化商业背景下,淘宝代购集运业务蓬勃发展,满足了海外消费者对中国商品的需求。掌握淘宝1688 API接口是构建成功代购系统的關鍵。本文详细介绍如何利用API接口进行系统架构设计、商品数据同步、订单处理与物流集成,以及用户管理和客户服务,帮助你打造一个高效便捷的代购集运系统,实现商业价值与用户满意度的双赢。
|
8天前
|
JSON 缓存 API
构建高效RESTful API的最佳实践
【10月更文挑战第34天】在数字时代的浪潮中,后端开发扮演着至关重要的角色。本文将带你深入探索如何构建高效的RESTful API,从设计原则到实际编码技巧,再到性能优化和错误处理,我们将一一解锁这些技能。你将学会如何打造一个既优雅又强大的后端服务,让你的应用程序在激烈的市场竞争中脱颖而出。那么,让我们一起踏上这段精彩的旅程吧!
24 2
|
16天前
|
前端开发 关系型数据库 API
深入浅出后端开发——从零到一构建RESTful API
本文旨在为初学者提供一个关于后端开发的全面指南,特别是如何从零开始构建一个RESTful API。我们将探讨后端开发的基本概念、所需技术栈、以及通过实际案例展示如何设计和实现一个简单的RESTful API。无论你是完全的新手还是有一定编程基础的开发者,这篇文章都将为你提供实用的知识和技巧,帮助你在后端开发的道路上迈出坚实的一步。
|
17天前
|
前端开发 JavaScript API
探索GraphQL:如何构建高效的数据API
【10月更文挑战第25天】在现代Web开发中,API的效率和灵活性至关重要。本文探讨了如何利用GraphQL构建高效的数据API。GraphQL通过声明式查询方式,允许客户端精确指定所需数据,减少数据传输量,提高API效率。文章介绍了设置GraphQL服务器、设计API Schema、实现解析函数及调整前后端交互的具体步骤,展示了GraphQL的优势和应用场景。
31 2
|
17天前
|
JSON API 数据格式
如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架
本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了示例代码。通过这些步骤,读者可以快速上手构建自己的RESTful API。
25 2
|
7天前
|
JavaScript 前端开发 NoSQL
深入浅出:使用Node.js构建RESTful API
【10月更文挑战第35天】在数字时代的浪潮中,后端技术如同海洋中稳固的灯塔,为前端应用提供数据和逻辑支撑。本文旨在通过浅显易懂的方式,带领读者了解如何利用Node.js这一强大的后端平台,搭建一个高效、可靠的RESTful API。我们将从基础概念入手,逐步深入到代码实践,最终实现一个简单的API示例。这不仅是对技术的探索,也是对知识传递方式的一次创新尝试。让我们一起启航,探索Node.js的奥秘,解锁后端开发的无限可能。
|
17天前
|
关系型数据库 测试技术 API
探索后端开发:构建高效API的艺术
【10月更文挑战第25天】在数字化时代,后端开发不仅仅是编写代码那么简单。它是连接用户与数据的桥梁,是实现业务逻辑的基石。本文将深入探讨如何构建高效的API,从理解RESTful原则到选择合适的框架,再到处理数据库交互,每一步骤都是精心策划的舞蹈。我们将通过实际案例,揭示如何在保证性能和安全性的同时,提供流畅的用户体验。让我们一起走进后端开发的世界,发现那些隐藏在代码背后的智慧和创造力。