既存のライブラリやレガシーモジュールが動作しているのに、システムとインターフェースが合わずに困ったことはありませんか。アダプタパターンを使えば、既存コードを変更せずに異なるインターフェースを橋渡しできます。この記事では概念、TypeScript/React の統一例、Node.js のコールバックを Promise に変換するアダプタ例を示し、実践的な設計とテスト方法を解説します。
December 19, 2025 (7mo ago) — last updated July 24, 2026 (2d ago)
アダプタパターン — TypeScript・React・Node.js 実践例
TypeScript、React、Node.js の実例でアダプタパターンを学び、互換性のないインターフェースを統一してレガシーを近代化する手法を紹介します。
← Back to blog
アダプタパターン: TypeScript、React、Node.js の例
概要: 実用的なコード例を通して、TypeScript、React、Node.js における互換性のないインターフェースをアダプタパターンがどのように接続し、統合やレガシーコードの近代化を助けるかを学びます。
はじめに
既存のライブラリやレガシーモジュールが動作しているのに、システムの他の部分とインターフェースが合わずに困ったことはありませんか。アダプタパターンは、一方のインターフェースを別のインターフェースに変換することで、既存コードを変更せずに再利用を可能にします。この記事では概念の説明に加え、TypeScript と React の実践例、コールバックベースの Node.js モジュールを Promise ベースに変換するアダプタ例を示します。テストしやすく、保守しやすい統合手法に焦点を当てます。
アダプタパターンが重要な理由
アダプタは構造的パターンで、互換性のないオブジェクトをラップしてクライアントが期待するインターフェースを提供します。Gang of Four によって最初に文書化されました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 |
構造と役割
パターンの主要な役割:
- クライアント — 特定のインターフェースを必要とするコード
- ターゲットインターフェース — クライアントが期待する契約
- アダプティー(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,
};
}
変換ロジックを中央に集約することで、React コンポーネントは単一で予測可能なデータ形状に依存できます。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 を公開するアダプタを作ると、呼び出し側は async/await で扱えるようになります。
レガシーなアダプティー(変更しない)
// 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();
これによりレガシーコードを手つかずに保ちながら、コードベースの他部分には近代的なインターフェースを提供できます。
いつアダプタを使うべきか
アダプタはインターフェース不一致を解消したいときに使います。典型的なケース:
- サードパーティ API の出力がアプリのモデルと一致しないとき
- レガシーなコールバック API を async/await に対応させたいとき
- 異なるフォーマットを持つ複数のデータソースを統一したいとき
アダプタを使うべきでないケース:
- 両方のシステムを自分で制御でき、簡単なリファクタで不一致が解消できる場合は、直接リファクタを優先する
- サブシステム全体を簡略化したい場合はファサードのほうが適している
判断チェックリスト
| Situation | Use Adapter? | Why |
|---|---|---|
| サードパーティライブラリがアプリと不一致 | Yes | ライブラリを変えられないため適応が必要 |
| 両側を制御できる・小さな変更で解決 | No | 直接リファクタのほうが単純 |
| 高レベルな単一インターフェースが必要 | No | Facade が適切 |
| レガシーの段階的移行 | Yes | 旧コンポーネントをラップして新しい契約に合わせる |
| 複数の異なるデータソースを統一 | Yes | 各ソースに対してアダプタを用意する |
テストとパフォーマンス
アダプタはコアロジックを外部システムから切り離すため、テスト容易性が向上します。アダプタのインターフェースをモックすれば、コンポーネント単体のテストが可能で、アダプタ自体も変換ロジックを個別に検証できます。
アダプタによるパフォーマンスのオーバーヘッドは一般に最小限で、関数呼び出しが1回増える程度です。ネットワーク I/O や DB クエリと比較すると無視できることが多く、保守性の向上がわずかなコストを上回ります。JavaScript は最も広く使われている言語の一つであり、統合作業の頻度は高いです4。
よくある質問(簡潔)
Q: アダプタパターンはどんな問題を解決するのか?
A: クライアントの呼び出しをアダプティーが理解できる形に翻訳し、インターフェース不一致を解消します。
Q: レガシーコードにどう役立つのか?
A: レガシーモジュールをラップしてモダンな API を提供できるため、リスクの高い書き換えなしに統合できます。
Q: アダプタとファサードの違いは何か?
A: アダプタは互換性の問題解決に特化し、ファサードは複雑なサブシステムを高レベルで簡略化します。
参考文献と内部リンク
- ポリモーフィズム 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加速の時代において、クリーンコードは単なる良い実践ではありません—スケールするシステムと自らの重みで崩壊するコードベースの違いです。