Быстрое практическое руководство по паттерну «Адаптер»: что это даёт, когда применять и как реализовать на TypeScript, React и Node.js, чтобы упростить интеграции и модернизировать унаследованный код.
December 19, 2025 (9mo ago) — last updated August 7, 2026 (1mo ago)
Паттерн «Адаптер» — TypeScript, React и Node.js
Как паттерн «Адаптер» переводит несовместимые интерфейсы — практические примеры на TypeScript, React и Node.js для модернизации интеграций.
← Back to blog
Паттерн «Адаптер» — TypeScript, React и Node.js
Краткое содержание: Практическое руководство по использованию паттерна «Адаптер» для объединения несовместимых интерфейсов — примеры на TypeScript, React и Node.js.
Введение
Бывало у вас отличная библиотека или унаследованный модуль, который не вписывается в остальную систему? Паттерн «Адаптер» переводит один интерфейс в другой, позволяя повторно использовать существующий код без его изменения. В этой статье показаны простые, применимые техники и примеры на TypeScript, React и Node.js, которые помогут почистить интеграции и улучшить тестируемость.
Почему паттерн «Адаптер» важен
Паттерн «Адаптер» — структурный паттерн, который оборачивает несовместимый объект и предоставляет интерфейс, ожидаемый вашим компонентам. Он был впервые описан Gang of Four в 1994 году1. Адаптеры особенно полезны при интеграции сторонних API, модернизации унаследованного кода и унификации разнородных источников данных.
Типичные сценарии использования:
- Интеграция сторонних API с разными форматами данных.
- Модернизация унаследованных API на колбэках для работы с async/await.
- Унификация нескольких источников данных в единый интерфейс для UI‑компонентов.
Правильное использование адаптера держит бизнес‑логику чистой и отделённой от внешних систем.
Основная идея и роли
Паттерн реализует четыре роли:
- Клиент — код, которому нужен определённый интерфейс.
- Целевой интерфейс — контракт, ожидаемый клиентом.
- Адаптируемый (Adaptee) — несовместимый класс или модуль с нужной функциональностью.
- Адаптер — реализует целевой интерфейс и делегирует вызовы адаптируемому, при необходимости преобразуя данные.
Два распространённых стиля адаптера:
- Объектный адаптер (композиция): адаптер хранит экземпляр адаптируемого — гибкий и предпочтительный в JavaScript/TypeScript.
- Классовый адаптер (наследование): требует множественного наследования и редко используется в современном JS/TS.
Быстрая сводка
| Concept | Description |
|---|---|
| Type | Structural |
| Primary intent | Allow objects with incompatible interfaces to work together |
| Core idea | Wrap the adaptee to expose the target interface |
| Key problem solved | Reuse existing classes without changing their source code |
| Common use cases | Third‑party libraries, legacy code, multiple data sources |
Практический пример: TypeScript + React
Представим панель управления, которая получает профили пользователей из двух сервисов с разной структурой ответов. Без адаптера компоненты засоряются условной логикой. Централизация преобразований сохраняет компоненты чистыми и устойчивыми.
Несовместимые формы API
// Data from UserServiceA
interface UserA {
userId: number;
fullName: string;
emailAddress: string;
}
// Data from UserServiceB
interface UserB {
id: string;
name: string;
contact: {
email: string;
};
}
Целевой интерфейс приложения
interface UnifiedUser {
id: string;
name: string;
email: string;
}
Адаптеры в TypeScript
// Adapter for UserServiceA
function adaptUserA(userA: UserA): UnifiedUser {
return {
id: userA.userId.toString(),
name: userA.fullName,
email: userA.emailAddress,
};
}
// Adapter for UserServiceB
function adaptUserB(userB: UserB): UnifiedUser {
return {
id: userB.id,
name: userB.name,
email: userB.contact.email,
};
}
React‑компонент, использующий объединённые данные
interface UserProfileProps {
user: UnifiedUser;
}
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
return (
<div>
<h2>{user.name}</h2>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
</div>
);
};
Этот компонент опирается на одну предсказуемую форму данных, что упрощает тестирование и повторное использование.
Пример: модернизация модуля Node.js на колбэках
Унаследованные модули часто используют колбэки с первым параметром‑ошибкой. Вместо изменения устойчивого модуля создайте адаптер, который предоставляет API на основе Promise.
Наследуемый модуль (не изменять)
// legacyFileProcessor.js
const fs = require('fs');
class LegacyFileProcessor {
processFile(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
return callback(err, null);
}
const processedContent = data.toUpperCase();
callback(null, processedContent);
});
}
}
module.exports = LegacyFileProcessor;
Адаптер, возвращающий Promise
// FileProcessorAdapter.js
const LegacyFileProcessor = require('./legacyFileProcessor');
class FileProcessorAdapter {
constructor() {
this.legacyProcessor = new LegacyFileProcessor();
}
processFile(filePath) {
return new Promise((resolve, reject) => {
this.legacyProcessor.processFile(filePath, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
}
module.exports = FileProcessorAdapter;
Это похоже на поведение util.promisify в Node.js, но сохраняет логику адаптации явной и тестируемой3.
Использование адаптера в приложении
const FileProcessorAdapter = require('./FileProcessorAdapter');
const fileProcessor = new FileProcessorAdapter();
async function handleFileProcessing() {
try {
console.log('Processing file with modern async/await...');
const content = await fileProcessor.processFile('my-file.txt');
console.log('Processed Content:', content);
} catch (error) {
console.error('An error occurred:', error);
}
}
handleFileProcessing();
Так вы оставляете унаследованный код нетронутым и предоставляете остальной части системы современный интерфейс.
Когда использовать адаптер
Используйте адаптер, когда компоненты не могут напрямую общаться из‑за различий в интерфейсах. Сценарии:
- Третья сторона возвращает данные в формате, отличном от ваших моделей.
- Вам нужно обёрнуть унаследованные API на колбэках для async/await.
- Несколько источников с разной структурой данных — создайте адаптер для каждого источника.
Когда не стоит использовать адаптер:
- Вы контролируете обе стороны и небольшой рефакторинг решит проблему — лучше рефакторить.
- Нужно упростить подсистему целиком — фасад (Facade) будет уместнее.
Краткий чеклист
| Situation | Use Adapter? | Why |
|---|---|---|
| Need to use a third‑party library with incompatible API | Yes | You can’t change the library |
| Control both sides and change is small | No | Refactor directly |
| Need a simplified high‑level interface | No | Use Facade |
| Migrating legacy systems incrementally | Yes | Wrap old components |
| Multiple differently structured data sources | Yes | Adapters unify them |
Тестирование и производительность
Адаптеры улучшают тестируемость: вы можете мокать интерфейс адаптера для изолированного тестирования компонентов и отдельно тестировать логику преобразования. Накладные расходы минимальны — обычно один дополнительный вызов — и несущественны по сравнению с сетевым вводом/выводом или запросами в базу данных. JavaScript остаётся самым используемым языком согласно опросу разработчиков, что подчёркивает частые интеграционные задачи, решаемые адаптерами4.
Часто задаваемые вопросы
В: Какую проблему решает паттерн «Адаптер»?
А: Он устраняет несовместимость интерфейсов, переводя вызовы от клиента в вызовы, которые понимает адаптируемый объект, что позволяет переиспользовать код без его изменения.
В: Как адаптер помогает с унаследованным кодом?
А: Адаптер оборачивает унаследованные модули и предоставляет современный интерфейс, позволяя интегрировать старый, стабильный код в новые приложения без рискованных переписок.
В: Когда выбирать адаптер вместо других паттернов?
А: Используйте адаптер, когда нужно обеспечить совместимость между двумя несовпадающими интерфейсами. Если цель — упростить всю подсистему, предпочтительнее фасад.
Краткие практические советы
- Держите адаптеры простыми: одна ответственность — привести формат данных к ожидаемому виду.
- Тестируйте адаптеры отдельно: проверяйте все возможные варианты преобразований.
- Централизуйте адаптации, чтобы изменения API трогали только адаптеры, а не компоненты.
Дополнительная литература
- Глубокое погружение в полиморфизм vs наследование: /blog/polymorphism-vs-inheritance
- Стратегии модернизации унаследованных систем: /blog/modernizing-legacy-systems
- Справочник и примеры паттерна «Адаптер»: GeeksforGeeks2
ИИ пишет код.Вы делаете его долговечным.
В эпоху ускорения ИИ чистый код — это не просто хорошая практика — это разница между системами, которые масштабируются, и кодовыми базами, которые рушатся под собственным весом.