|
| 1 | +--- |
| 2 | +title: GoFデザインパターン - アダプタ編 |
| 3 | +date: 2026-02-04 |
| 4 | +category: Coding |
| 5 | +description: GoFコレクションにおけるアダプタパターン実践ガイド:概念、使いどころ、C++の実装例、注意点と出典を実用的に解説します |
| 6 | +tags: [Coding, DesignPattern] |
| 7 | +recommended: true |
| 8 | +thumbnail: assets/img/ogp.png |
| 9 | +--- |
| 10 | + |
| 11 | +こんにちは、パン君です。 |
| 12 | + |
| 13 | +今回は GoF(Gang of Four)のデザインパターンのアダプタ編になります。 |
| 14 | +サンプルコード(C++)、作り方、使うタイミング、注意点、mermaid図による可視化などを含めて実用的に解説します。 |
| 15 | + |
| 16 | +## はじめに |
| 17 | + |
| 18 | +アダプタは互換性のないインターフェース同士をつなぐ ラッパー を提供し、クライアントが期待するインターフェースに既存のクラスを適合させるためのデザインパターンです。 |
| 19 | + |
| 20 | +利点としては |
| 21 | +既存コードや外部ライブラリを変更せずに新しいインターフェースへ適合できることです。 |
| 22 | + |
| 23 | +欠点は |
| 24 | +ラッパーが増えると設計が分散し可読性や保守性の低下を招くことです。 |
| 25 | +また、間接化によるランタイムのコスト増も発生します。 |
| 26 | + |
| 27 | +## ユースケース |
| 28 | + |
| 29 | +代表的に下記のようなケースで作成されるパターンです。 |
| 30 | + |
| 31 | +- 古い API / ライブラリを新しいアプリケーションインターフェースに統合したいとき。 |
| 32 | +- 異なるモジュール間でインターフェースの不一致があるが、既存実装を変更できないとき。 |
| 33 | +- テスト時に実装を差し替えるための薄い境界を作りたいとき(ただし Mock を使える場合はそちらの方が簡潔な場合もある)。 |
| 34 | + |
| 35 | +## 構造 |
| 36 | + |
| 37 | +Adapter は Target のインターフェースを実装し、内部で Adaptee の操作を呼び出して必要な変換を行います。 |
| 38 | + |
| 39 | +```mermaid |
| 40 | +classDiagram |
| 41 | + class Target { |
| 42 | + <<interface>> |
| 43 | + +request() |
| 44 | + } |
| 45 | + class Adaptee { |
| 46 | + +specificRequest(params) |
| 47 | + } |
| 48 | + class Adapter { |
| 49 | + +Adapter(adaptee) |
| 50 | + +request() |
| 51 | + } |
| 52 | + Target <|.. Adapter |
| 53 | + Adapter o-- Adaptee : adapts |
| 54 | +``` |
| 55 | + |
| 56 | +## C++ 実装パターン |
| 57 | + |
| 58 | +以下は簡潔な C++ の例です。 |
| 59 | + |
| 60 | +```cpp |
| 61 | +// 例: クライアントは Target::request() を使うことを期待している |
| 62 | +struct Target { |
| 63 | + virtual ~Target() = default; |
| 64 | + virtual void request() = 0; // クライアントが期待するメソッド |
| 65 | +}; |
| 66 | + |
| 67 | +// 既存の(変更できない)クラス |
| 68 | +class Adaptee { |
| 69 | +public: |
| 70 | + void specificRequest(const std::string& params) { |
| 71 | + // 既存 API の複雑な呼び出し |
| 72 | + std::cout << "Adaptee::specificRequest with " << params << std::endl; |
| 73 | + } |
| 74 | +}; |
| 75 | + |
| 76 | +// Adapter: Target を実装しつつ Adaptee を適合させる |
| 77 | +class Adapter : public Target { |
| 78 | +public: |
| 79 | + explicit Adapter(std::shared_ptr<Adaptee> adaptee) |
| 80 | + : adaptee_(std::move(adaptee)) {} |
| 81 | + |
| 82 | + void request() override { |
| 83 | + // 必要な変換を行ってから adaptee を呼ぶ |
| 84 | + std::string translated = translate(); |
| 85 | + adaptee_->specificRequest(translated); |
| 86 | + } |
| 87 | + |
| 88 | +private: |
| 89 | + std::string translate() { |
| 90 | + // 実際の変換ロジック。ここでは単純化。 |
| 91 | + return "translated-params"; |
| 92 | + } |
| 93 | + |
| 94 | + std::shared_ptr<Adaptee> adaptee_; |
| 95 | +}; |
| 96 | + |
| 97 | +// main.cpp |
| 98 | +auto adaptee = std::make_shared<Adaptee>(); |
| 99 | +std::unique_ptr<Target> target = std::make_unique<Adapter>(adaptee); |
| 100 | +target->request(); // クライアントは Target のみを意識する |
| 101 | +``` |
| 102 | + |
| 103 | +ポイントとして |
| 104 | +必要に応じてデータ形式の変換、エラーハンドリング、複数メソッドのマッピングを行うようにしましょう。 |
| 105 | +また、継承ベースと委譲ベースがありますが、ユースケースに寄って使い分けしてください。 |
| 106 | +端的に説明すると、継承ベースはクラスの継承を活用し、委譲ベースはインターフェースの実装です。 |
| 107 | + |
| 108 | +## 実装上の注意点 |
| 109 | + |
| 110 | +1. 単純な変換に留める |
| 111 | + Adapter に過度のロジックやビジネスルールを詰め込みすぎると責務が肥大化します。 |
| 112 | + 可能なら変換ロジックは別モジュールに切り出しましょう。 |
| 113 | +2. 所有権とライフサイクルを明確にする |
| 114 | + C++ ではポインタの種類により Adapter と Adaptee の関係が変わります。 |
| 115 | + リーク・二重解放を避けるために明示的に管理しましょう。 |
| 116 | +3. パフォーマンスの考慮 |
| 117 | + 頻繁に呼ばれるパスで重い変換を行うと性能ボトルネックになります。 |
| 118 | + 必要なら変換済みキャッシュを検討しましょう。 |
| 119 | +4. テストしやすさを保つ |
| 120 | + Adapter を薄く保つことで単体テストが簡単になります。 |
| 121 | + Adaptee をモック可能にしておくと良いです。 |
| 122 | +5. ログ・エラーハンドリングを整備する |
| 123 | + |
| 124 | +## よくあるアンチパターン |
| 125 | + |
| 126 | +下記はこのデザインパターンを利用する際に起きる悪い例です。 |
| 127 | + |
| 128 | +- Adapter に大量のドメインロジックを入れる。 |
| 129 | +- プロジェクト全体で無差別に Adapter 層を追加してしまい、呼び出しチェーンが深くなる。 |
| 130 | +- 変換で失われる意味や例外ケースを無視する。 |
| 131 | + |
| 132 | +## 実務での採用判断チェックリスト |
| 133 | + |
| 134 | +- 既存コードを直接変更できないか?(変更不可なら Adapter が妥当) |
| 135 | +- 変換ロジックは単純か? 複雑なら中間サービスやファサードでの整理が必要か? |
| 136 | +- 頻度・性能要件は許容できるか?(変換コストが高ければ別設計を検討) |
| 137 | +- テスト戦略は確立できるか?(Mock や Integration テストの整備) |
| 138 | + |
| 139 | +## まとめ |
| 140 | + |
| 141 | +アダプタは「互換性のないインターフェースを橋渡しする」ための実用的かつよく使われるパターンです。 |
| 142 | +ただし、Adapter を安易に量産すると設計が複雑化するため、責務を明確にし変換ロジックを局所化して使うことが重要です。 |
| 143 | +実装では所有権、例外・エラー、性能、テストのしやすさを特に注意してください。 |
| 144 | + |
| 145 | +参考・出典を記します |
| 146 | + |
| 147 | +[!CARD](https://sourcemaking.com/design_patterns/adapter) |
| 148 | + |
| 149 | +[!CARD](https://en.wikipedia.org/wiki/Adapter_pattern ) |
| 150 | + |
| 151 | +それでは、次回は別の GoF パターンについても解説していきます。 |
| 152 | +この記事で紹介した実装は学習目的に簡素化しています。 |
| 153 | +実プロダクションで採用する際は要件に合わせて十分に検討してください。 |
| 154 | + |
| 155 | +以上、パン君でした! |
0 commit comments