Приведите пример паттерна Adapter из встроенных библиотек ноды (есть несколько).nodejs-58

Паттерн Adapter (Адаптер) — это структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. В Node.js этот паттерн часто встречается во встроенных модулях. Рассмотрим несколько ярких примеров.

1. Stream.Readable как адаптер для различных источников данных

Модуль 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 адаптирует различные источники данных (строки, массивы, файлы) к единому интерфейсу потока.

2. Util.promisify как адаптер для callback-функций

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 подходу.

3. Http.IncomingMessage как адаптер для входящих запросов

Сервер 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 остается одинаковым.

4. Child_process.spawn как адаптер для внешних процессов

Модуль 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}`);
});

Адаптация: Независимо от ОС и типа выполняемой команды, интерфейс взаимодействия остается единым.

5. Buffer как адаптер для бинарных данных

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

Адаптация: Разные источники данных преобразуются к единому бинарному представлению.

6. Events.EventEmitter как адаптер для событийных интерфейсов

Многие модули 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');

Почему это Adapter?

Во всех примерах прослеживается общая схема:

  1. Есть некий существующий интерфейс (callback, поток данных, внешний процесс)
  2. Создается обертка (адаптер), которая предоставляет новый интерфейс
  3. Клиентский код работает только с новым интерфейсом, не зная о деталях реализации

Резюмируем:

Node.js активно использует паттерн Adapter во встроенных модулях для обеспечения совместимости между разными интерфейсами, унификации работы с разнородными системами и предоставления удобных абстракций над сложными механизмами. Наиболее яркие примеры — потоки, promisify, работа с процессами и буферизация данных.