JavaScript 设计模式:接口实现的三种方式与最佳实践

作者:栖木

时间:2026-10-26

浏览:253

本文深入解析 JavaScript 中接口的三种模拟实现方式:注释描述、属性验证及鸭子类型。通过对比分析,重点推荐基于 Interface 类的鸭子类型方案,并阐述在大型团队协作与复杂系统设计中引入接口的必要性,最后给出从定义到验证的完整使用建议。

文章目录

JavaScript 接口概述

本书开篇重点阐述了接口的概念。



接口通常被视为一种契约或规范。在强类型的面向对象语言中,接口的实现相对直接;然而,JavaScript 原生并未提供创建或验证接口的机制,也无法直接判断类型是否实现了特定接口。因此,我们需要利用 JavaScript 的灵活性来模拟接口的行为。


接口实现的三种方式

在 JavaScript 中,实现接口主要有三种途径:注释描述、属性验证以及鸭子类型。


注:本文基于英文原著翻译,部分术语可能因语言差异存在理解偏差,请读者重点把握核心思想。


注释描述法

1. 注释描述(Describing Interfaces with Comments)


示例如下:


代码实现如下:

 
/* 
interface Composite {
function add(child);
function remove(child);
function getChild(index);
} 
interface FormItem {
function save();
} 
*/ 
var CompositeForm = function(id, method, action) { // implements Composite, FormItem
  ... 
}; 
//Implement the Composite interface.
CompositeForm.prototype.add = function(child) {
... 
}; 
CompositeForm.prototype.remove = function(child) {
... 
}; 
CompositeForm.prototype.getChild = function(index) {
... 
}; 
// Implement the FormItem interface.
CompositeForm.prototype.save = function() {
... 
}; 

这种模拟方式借用了其他面向对象语言中的 interface 和 implements 关键字,但必须将它们置于注释中,以避免语法错误。


其目的仅在于向其他开发者提示该类需实现的方法,起到文档化的作用。然而,这种方式缺乏实际的验证机制,无法确保类正确实现了接口定义的方法,因此仅作为一种文档约定。


属性验证法

2. 属性验证(Emulating Interfaces with Attribute Checking)


示例如下:


代码实现如下:

 
/* interface
Composite {
function add(child);
function remove(child);
function getChild(index);
} 
interface FormItem {
function save();
} 
*/ 
var CompositeForm = function(id, method, action) {
this.implementsInterfaces = ['Composite', 'FormItem'];
... 
}; 
... 
function addForm(formInstance) {
if(!implements(formInstance, 'Composite', 'FormItem')) {
throw new Error("Object does not implement a required interface.");
  } 
  ... 
} 
// The implements function, which checks to see if an object declares that it
// implements the required interfaces.
function implements(object) {
for(var i = 1; i < arguments.length; i++) {
// Looping through all arguments
// after the first one.
var interfaceName = arguments[i];
var interfaceFound = false;
for(var j = 0; j < object.implementsInterfaces.length; j++) {
if(object.implementsInterfaces[j] == interfaceName) {
interfaceFound = true;
break;
      } 
    } 
if(!interfaceFound) {
return false;
// An interface was not found.
   } 
  } 
return true;
// All interfaces were found.
} 

相较于第一种方式,此方法有所改进。接口定义仍通过注释进行,但增加了验证逻辑,用于判断对象是否声明实现了特定接口。


鸭子类型法

3. 鸭子类型(Emulating Interfaces with Duck Typing)


代码实现如下:

 
// Interfaces.
var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
var FormItem = new Interface('FormItem', ['save']);
// CompositeForm class
var CompositeForm = function(id, method, action) {
  ... 
}; 
... 
function addForm(formInstance) {
ensureImplements(formInstance, Composite, FormItem);
// This function will throw an error if a required method is not implemented.
  ... 
} 
// Constructor.
var Interface = function(name, methods) {
if(arguments.length != 2) {
throw new Error("Interface constructor called with "
+ arguments.length + "arguments, but expected exactly 2.");
  } 
this.name = name;
this.methods = [];
for(var i = 0, len = methods.length; i < len; i++) {
if(typeof methods[i] !== 'string') {
throw new Error("Interface constructor expects method names to be "
+ "passed in as a string.");
    } 
this.methods.push(methods[i]);
  } 
}; 
// Static class method.
Interface.ensureImplements = function(object) {
if(arguments.length < 2) {
throw new Error("Function Interface.ensureImplements called with "
+arguments.length + "arguments, but expected at least 2.");
  } 
for(var i = 1, len = arguments.length; i < len; i++) {
var interface = arguments[i];
if(interface.constructor !== Interface) {
throw new Error("Function Interface.ensureImplements expects arguments"
+ "two and above to be instances of Interface.");
    } 
for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
var method = interface.methods[j];
if(!object[method] || typeof object[method] !== 'function') {
throw new Error("Function Interface.ensureImplements: object "
+ "does not implement the " + interface.name + " interface. Method " + method + " was not found.");
      } 
    } 
  } 
}; 

何时使用接口?

接口的应用场景与使用建议

严格类型验证并非适用于所有 JavaScript 场景,因为许多开发者已习惯在无接口验证的环境下编程。然而,在设计复杂系统时,使用接口能带来显著益处。虽然接口看似限制了灵活性,实则通过解耦代码提升了系统的健壮性。它允许传递任意类型的变量,只要该变量包含所需方法即可,从而在多种场景下保持代码的灵活性。


在大型系统中,多名开发者共同参与项目时,接口显得尤为重要。开发者常需访问尚未实现的 API,或为他人提供依赖方法的存根。此时,接口作为文档和契约具有极高价值。当存根被实际 API 替换时,接口能确保无缝切换;若开发过程中 API 发生变更,只要新实现符合接口定义,即可平滑替换。


如何正确使用接口?


首要问题是判断代码是否适合使用接口。对于小型项目,引入接口可能增加不必要的复杂度,需权衡利弊。若确定使用接口,建议遵循以下步骤:


1. 在页面文件中引用 Interface 类。Interface 的源文件可在 http://jsdesignpatterns.com/ 获取。


2. 审查代码,确定哪些方法需要抽象为接口。


3. 创建接口对象,每个接口对象包含一组相关方法。


4. 移除所有构造函数中的验证逻辑,转而采用第三种实现方式,即鸭子类型。


5. 使用 Interface.ensureImplements 替代原有的构造函数验证。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章