アダプタパターンが TypeScript、React、Node.js の例を通して互換性のないインターフェースをつなぎ、統合やレガシーコードを近代化する方法を学びましょう。
December 19, 2025 (3mo 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 によって 1994 年に初めて文書化されました1。アダプタはサードパーティの 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 |
構造と役割
パターンには 4 つの役割があります:
- クライアント — 特定のインターフェースを必要とするコード
- ターゲットインターフェース — クライアントが期待する契約
- アダプティー(Adaptee) — 必要な機能を持つが互換性のないクラスやモジュール
- アダプタ — ターゲットを実装し、必要に応じて呼び出しを翻訳してアダプティーに委譲する
一般的なアダプタのスタイル:
- オブジェクトアダプタ(コンポジション): アダプタがアダプティーのインスタンスを保持する。最も柔軟なアプローチ。
- クラスアダプタ(継承): アダプタがアダプティーを継承しターゲットを実装する。多重継承を必要とし、現代の JavaScript / TypeScript ではあまり一般的ではありません。
実践例: TypeScript + React
ダッシュボードが 2 つのサービスから異なるレスポンス形状でユーザープロファイルを受け取るとします。アダプタがなければ、コンポーネントは条件分岐だらけになってしまいます。
互換性のない 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 モジュールを近代化する
レガシーモジュールはしばしばエラー優先のコールバックを使います。安定したモジュールを変更する代わりに、Promise ベースの API を公開するアダプタを作成しましょう。
レガシーなアダプティー(変更しない)
// 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;
これは Node の util.promisify と似ていますが、適応ロジックを明示的でテスト可能な形で保持します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();
これによりレガシーコードを手つかずのままにしつつ、コードベースの他部分には近代的なインターフェースを提供できます。
いつアダプタを使うべきか
インターフェースが異なるために 2 つのコンポーネントが直接通信できない場合にアダプタを使います。典型的なシナリオ:
- 入出力があなたのモデルと一致しないサードパーティ API の統合
- レガシーなコールバック API をラップして async/await と共に動作させる
- 異なるフォーマットを持つ複数のデータソースを、それぞれのソースに対して 1 つのアダプタを作成してサポートする
アダプタを使うべきでない場合:
- 両方のシステムをあなたが制御しており、小さなリファクタで不一致が解消できる場合は直接リファクタを優先する。
- 複雑なサブシステムを単純化することが目的であれば、ファサードを検討する。ファサードは高レベルで単純化されたエントリーポイントを提供し、アダプタは互換性にのみ焦点を当てる。
簡単な判断チェックリスト
| 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 |
テストとパフォーマンス
アダプタはコアロジックを外部システムから切り離すことでテスト容易性を向上させます。アダプタのインターフェースをモックすればコンポーネントを単独でテストでき、アダプタ自体を個別にテストして変換ロジックを検証できます。
アダプタによるパフォーマンスのオーバーヘッドは最小限で、通常は関数呼び出しが 1 回増える程度であり、ネットワーク I/O やデータベースクエリと比べれば無視できるレベルです。ほとんどのウェブアプリケーションでは、保守性と疎結合の利点がわずかなコストをはるかに上回ります。Stack Overflow Developer Survey によると JavaScript は最も使用されている言語であり、開発者が統合作業に直面する頻度の高さを示しています4。
よくある質問
Q: アダプタパターンはどんな問題を解決するのか?
A: クライアントからの呼び出しをアダプティーが理解できる呼び出しに翻訳することで、インターフェースの不一致を解消し、コードを変更せずに再利用できるようにします。
Q: アダプタはレガシーコードにどう役立つのか?
A: アダプタはレガシーモジュールをラップしてモダンなインターフェースを公開するため、古くて安定したコードをリスクの高い書き換えなしに新しいアプリケーションに統合できます。
Q: 他のパターンよりもアダプタを選ぶべきときは?
A: 互換性のない 2 つのインターフェース間で互換性が必要なときにアダプタを選びます。サブシステム全体を単純化したい場合は、代わりにファサードを使うべきです。
参考文献と内部リンク
- ポリモーフィズム vs 継承の詳細: /blog/polymorphism-vs-inheritance
- レガシーシステムを近代化するための戦略: /blog/modernizing-legacy-systems
- アダプタパターンのリファレンスと例: GeeksforGeeks2
At Clean Code Guy, we help teams implement practical design patterns that turn brittle, complex codebases into assets that are resilient, testable, and a pleasure to work on. If you’re wrestling with a legacy system or tricky integrations, our Clean Code Audits can give you a clear, actionable roadmap to a healthier architecture. Learn how we can help you ship better code, faster.
AIがコードを書きます。あなたがそれを長持ちさせます。
AI加速の時代において、クリーンコードは単なる良い実践ではありません—スケールするシステムと自らの重みで崩壊するコードベースの違いです。