Паттерн Adapter (Адаптер) — это структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. В Node.js этот паттерн часто встречается во встроенных модулях. Рассмотрим несколько ярких примеров.
Модуль stream
предоставляет абстракции для работы с потоками данных. Readable
stream выступает адаптером для разных источников.
const { Readable } = require('stream');
// Адаптер для строки
const stringAdapter = new Readable({
read() {
this.push('Hello, world!');
this.push(null); // Сигнал конца данных
}
});
// Адаптер для массива
const arrayAdapter = Readable.from(['one', 'two', 'three']);
// Адаптер превращает разные источники в унифицированный поток
stringAdapter.pipe(process.stdout);
arrayAdapter.pipe(process.stdout);
Как это работает: Readable
адаптирует различные источники данных (строки, массивы, файлы) к единому интерфейсу потока.
util.promisify
преобразует функции с callback'ами в Promise-based интерфейс.
const { promisify } = require('util');
const fs = require('fs');
// Стандартный callback-стиль
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// Адаптер создает Promise-версию
const readFileAsync = promisify(fs.readFile);
// Теперь можно использовать async/await
async function readData() {
try {
const data = await readFileAsync('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
Что происходит: promisify
адаптирует устаревший callback-интерфейс к современному Promise-based подходу.
Сервер HTTP в Node.js использует адаптер для унификации работы с входящими запросами.
const http = require('http');
http.createServer((req, res) => {
// req - адаптер для входящего HTTP-запроса
console.log(req.method); // GET, POST и т.д.
console.log(req.url); // URL запроса
// Адаптер предоставляет унифицированный интерфейс
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
console.log('Body:', body);
res.end('OK');
});
}).listen(3000);
Адаптация: Независимо от источника запроса (HTTP/1.1, HTTP/2, HTTPS), интерфейс IncomingMessage
остается одинаковым.
Модуль child_process
предоставляет адаптеры для работы с внешними процессами.
const { spawn } = require('child_process');
// Адаптер для выполнения команды ls
const ls = spawn('ls', ['-lh', '/usr']);
// Унифицированный интерфейс для работы с выводом
ls.stdout.on('data', data => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', data => {
console.error(`stderr: ${data}`);
});
ls.on('close', code => {
console.log(`child process exited with code ${code}`);
});
Адаптация: Независимо от ОС и типа выполняемой команды, интерфейс взаимодействия остается единым.
Buffer
адаптирует различные представления данных к единому бинарному формату.
const buf1 = Buffer.from('Hello', 'utf8'); // Адаптация строки
const buf2 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // Адаптация массива
const buf3 = Buffer.alloc(5, 'Hello'); // Адаптация через выделение памяти
console.log(buf1.equals(buf2)); // true
console.log(buf2.equals(buf3)); // true
Адаптация: Разные источники данных преобразуются к единому бинарному представлению.
Многие модули Node.js наследуют от EventEmitter
, создавая адаптеры для событийных моделей.
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
// Адаптация пользовательских событий к стандартной модели
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
Во всех примерах прослеживается общая схема:
Node.js активно использует паттерн Adapter во встроенных модулях для обеспечения совместимости между разными интерфейсами, унификации работы с разнородными системами и предоставления удобных абстракций над сложными механизмами. Наиболее яркие примеры — потоки, promisify, работа с процессами и буферизация данных.