Node.js 适配器模式

适配器模式

Adapter

适配器模式可以让我们使用一个不同的接口来访问一个对象的功能。它用来适配一个对象,来让期望一个不同接口的组件可以使用它。如下图所示: 

Adapter 实际就是 Adaptee 的封装,导出一个不同的接口。如图所示,Adapter 的接口也可以是 Adaptee 中一个或多个方法的组合。 这个模式非常直观,我们直接来看一个例子。

从文件系统API使用 LevelUP

我们围绕 LevelUP API 来构建一个适配器,把它变成一个兼容核心 fs 模块的接口。 尤其是,我们将确保每次调用 readFile() 和 writeFile() 都将转成到 db.get() 和 db.put() 的调用;这样我们就可以把 LevelUP 数据库当做简单文件系统操作的一个存储后台。

我们从创建一个名为 fsAdapter.js 的模块开始。我们将从加载依赖开始,然后导出我们将用来构建适配器的 createFsAdapter() 工厂:

var path = require('path');

module.exports = function createFsAdapter(db) {
	var fs = {};
		//... 继续
}

下面,我们来实现工厂内部的 readFile() 函数,并确保它的接口跟 fs 模块的原生接口之一是兼容的:

fs.readFile = function(filename, options, callback) {
	if (typeof options === 'function') {
		callback = option;
		options = {};
	} else if (typeof options === 'string') {
		options = {encoding: options};
	}

	db.get(path.resolve(filename), {	//[1]
		valueEncoding: options.encoding
		},
		function(err, value) {
			if (err) {
				if (err.type === 'NotFoundError') {	//[2]
					err = new Error('ENOENT, open \'' + filename + '\'');
					err.code = 'ENOENT';
					err.errno = 34;
					err.path = filename;
				}
				return callback && callback(err);
			}
			callback && callback(null, value);	//[3]
		}
	);
};

在上述代码中,我们必须做一些额外工作确保我们新函数的行为跟原始的 fs.readFile() 函数要尽可能接近。各步骤描述如下:

  1. 要从 db 类获取一个文件,我们使用 filename 作为索引来调用 db.get(),并确保总是使用它的全路径(通过使用 path.resolve())。我们把数据库使用的 valueEncoding 设置成跟从输入拿到的 encoding 选项相同。
  2. 如果key在数据库中没找到,我们就使用 ENOENT 作为错误代码来生成一个错误,这个是 fs 模块用来通知一个缺失文件的。其他任何类型的错误都被前转给 callback(我们在这个例子中,只处理了最常见的错误情况)
  3. 如果 key-value 对被成功从数据库获取,我们将使用 callback 把这些值返回给调用者。

实现粗燥,大致可用。

我们再来实现 writeFile() 函数:

fs.writeFile = function(filename, contents, options, callback) {
	if (typeof options === 'function') {
		callback = options;
		options = {};
	} else if (typeof options === 'string') {
		options = {encoding: options};
	}

	db.put(path.resolve(filename), contents, {
		valueEncoding: options.encoding
	}, callback);
}

最后,返回 fs 对象就好:

return fs;

我们可以写一个小的测试模块来试一下:

var fs = require('fs');

fs.writeFile('file.txt', 'Hello', function() {
	fs.readFile('file.txt', {encoding: 'utf8'}, function(err, res) {
		console.log(res);
	});
});

// try to read a missing file
fs.readFile('missing.txt', {encoding: 'utf8'}, function(err, res) {
	console.log(err);
});

我是不是可以搞一章 fs module 和 Adapter 模式

现在我们可以把 fs 模块换成我们的适配器了,如下:

var levelup = require('level');
var fsAdapter = require('./fsAdapter');
var db = levelup('./fsDB', {valueEncoding: 'binary'});
var fs = fsAdapter(db);

Adapter 模式在跟浏览器共享代码中扮演非常重要的角色。

实践中

现实世界中使用适配器的例子数不胜数,可以去探索和分析下列值得关注的示例:

跟不同的翻译引擎进行适配,跟不同的短信网关进行适配

* 示例的完整实现是 level-filesystem(https://www.npmjs.org/package/level-filesystem)。

从核心 API 里面分组,并和模式结合进行讲解,术道并重。

回复