Узнайте, как паттерн «Адаптер» соединяет несовместимые интерфейсы на примерах TypeScript, React и Node.js, чтобы модернизировать интеграции и унаследованный код.
December 19, 2025 (4mo ago) — last updated February 10, 2026 (2mo 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 в 19941. Адаптеры незаменимы при интеграции сторонних API, модернизации унаследованного кода и унификации разнородных источников данных.
Типичные сценарии, где помогают адаптеры:
- Интеграция сторонних API, которые возвращают данные в разных форматах.
- Модернизация унаследованных API на колбэках для работы с async/await.
- Унификация нескольких источников данных в единый интерфейс для UI-компонентов.
Использование адаптера держит бизнес-логику чистой и отделённой от внешних систем.
Паттерн «Адаптер» в кратце
| 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 |
Структура и роли
У паттерна четыре роли:
- Клиент — код, которому нужен конкретный интерфейс.
- Целевой интерфейс — контракт, которого ожидает Клиент.
- Адаптируемый (Adaptee) — несовместимый класс или модуль с требуемой функциональностью.
- Адаптер — реализует Целевой интерфейс и делегирует вызовы Адаптируемому, при необходимости переводя их.
Два распространённых стиля адаптера:
- Объектный адаптер (композиция): адаптер хранит экземпляр Адаптируемого. Это самый гибкий подход.
- Классовый адаптер (наследование): адаптер наследует Адаптируемый и реализует Целевой интерфейс. Это требует множественного наследования и встречается реже в современном JavaScript и TypeScript.
Практический пример: 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,
};
}
Централизация преобразований сохраняет компоненты чистыми и устойчивыми. Если API переименует поле, менять придётся только адаптер.
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, но сохраняет логику адаптации явной и тестируемой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, чьи входные или выходные данные не соответствуют вашим моделям.
- Обёртка унаследованных 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, so adapt to it |
| Control both sides and change is small | No | Refactor directly to avoid extra indirection |
| Need a simplified high-level interface to a complex system | No | Facade is a better fit |
| Migrating legacy systems incrementally | Yes | Wrap old components to match new interfaces |
| Multiple differently structured data sources | Yes | Adapters unify them into one shape |
Тестирование и производительность
Адаптеры улучшают тестируемость, отделяя основную логику от внешних систем. Вы можете мокать интерфейс адаптера, чтобы тестировать компоненты в изоляции, и отдельно тестировать адаптеры, чтобы проверить логику преобразования.
Накладные расходы от адаптера минимальны — обычно это один дополнительный вызов функции — и несущественны по сравнению с сетевым вводом/выводом или запросами в базу данных. Для большинства веб‑приложений преимущества в поддержке и декуплинге намного перевешивают эту малую стоимость. JavaScript остаётся самым используемым языком в опросе разработчиков Stack Overflow, что подчёркивает, как часто разработчики сталкиваются с интеграционной работой, которую решают адаптеры4.
Часто задаваемые вопросы
В: Какую проблему решает паттерн «Адаптер»?
О: Он устраняет несовместимость интерфейсов, переводя вызовы от клиента в вызовы, которые понимает адаптируемый объект, так что вы можете переиспользовать код без его изменения.
В: Как адаптер помогает с унаследованным кодом?
О: Адаптер оборачивает унаследованные модули и предоставляет современный интерфейс, позволяя интегрировать старый, стабильный код в новые приложения без рискованных переписок.
В: Когда выбирать Адаптер вместо других паттернов?
О: Выбирайте Адаптер, когда нужно обеспечить совместимость между двумя несовпадающими интерфейсами. Если цель — упростить всю подсистему, используйте Фасад.
Дополнительная литература и внутренние ссылки
- Глубокое погружение в полиморфизм vs наследование: /blog/polymorphism-vs-inheritance
- Стратегии модернизации унаследованных систем: /blog/modernizing-legacy-systems
- Справочник и примеры паттерна «Адаптер»: GeeksforGeeks2
В Clean Code Guy мы помогаем командам внедрять практические шаблоны проектирования, которые превращают хрупкие, сложные кодовые базы в активы, устойчивые, тестируемые и приятные для работы. Если вы боретесь с унаследованной системой или сложными интеграциями, наши Clean Code Audits могут дать вам чёткую, применимую дорожную карту к более здоровой архитектуре. Узнайте, как мы можем помочь вам выпускать лучший код быстрее.
ИИ пишет код.Вы делаете его долговечным.
В эпоху ускорения ИИ чистый код — это не просто хорошая практика — это разница между системами, которые масштабируются, и кодовыми базами, которые рушатся под собственным весом.