logo

KUROFUNE

Bridging Global Tech Solutions

ソフトウェアのデザインパターンについて

ソフトウェアのデザインパターンについて

Atsuki Sakai

Next.jsでのマルチテナントWEBアプリケーションの構築方法が分からなかったので勉強した事を自分の理解で簡単にまとめます。

今週学んだ事

以下、ChatGTPからの回答

ソフトウェア開発において「デザインパターン」とは、よくある設計上の問題を解決するために再利用可能なアーキテクチャ上の“ひな型”や“ベストプラクティス”をまとめたものです。特定の言語や環境に依存しない形でまとめられており、問題を効率的・効果的に解決するための考え方やクラス構造の例を提供してくれます。代表的なものとしては、エリック・ガンマら4名(通称“Gang of Four”や“GoF”)が著書『Design Patterns: Elements of Reusable Object-Oriented Software』で提唱した23種類のパターンが有名です。

以下では、GoFデザインパターンを中心に概要を紹介します。


1. デザインパターンの分類

GoFが提唱したデザインパターンは、大きく以下の3つに分類されます。

  1. 生成に関するパターン(Creational Patterns)
    オブジェクト生成(インスタンス化)に関する問題を解決します。インスタンス生成の方法や、クラス間の依存度を適切に管理する方法を提供します。
  2. 構造に関するパターン(Structural Patterns)
    クラスやオブジェクトをどのように組み合わせるか、階層をどう設計するかといった構造面の問題を解決します。拡張性や保守性を高めるための方法が示されています。
  3. 振る舞いに関するパターン(Behavioral Patterns)
    オブジェクト間のやりとりや責任の分散方法など、動作やアルゴリズムに関する問題を解決します。分業化や動的な処理の仕組みづくりに役立ちます。

2. 主なデザインパターンの一覧

2.1 生成に関するパターン

  1. Singleton(シングルトン)
    • あるクラスのインスタンスがアプリケーション全体で1つしか存在しないことを保証するパターン。
    • 例:設定管理クラス、ログ管理クラスなど。
  2. Factory Method(ファクトリーメソッド)
    • インスタンスを生成する処理をサブクラスに委譲し、生成過程を抽象化するパターン。
    • 例:特定の抽象クラスでcreateXXX()メソッドだけ定義し、具体的な生成方法はサブクラスで実装する。
  3. Abstract Factory(アブストラクトファクトリ)
    • 関連するオブジェクト群(製品群)を、具体的なクラスに依存せずに一括で生成するパターン。
    • GUI部品一式など、製品の組み合わせに応じて工場を切り替えたい場合に利用。
  4. Builder(ビルダー)
    • 複雑なオブジェクトの生成過程を分割し、最終的にオブジェクトを組み立てるパターン。
    • 例:複数のステップ(部品)で構成されるオブジェクトを、組み立て方法だけをカプセル化する。
  5. Prototype(プロトタイプ)
    • 既存のオブジェクトをコピー(クローン)して新たなオブジェクトを作るパターン。
    • 例:オブジェクト生成コストが大きい場合などに、既存インスタンスをコピーすることで生成処理を効率化する。

2.2 構造に関するパターン

  1. Adapter(アダプター)
    • 既存のクラスやインターフェイスを別のインターフェイスで使いたい場合に、変換レイヤーとして中間オブジェクトを挟むパターン。
    • 例:既存のライブラリと新規システムのインターフェイスが合わないとき。
  2. Bridge(ブリッジ)
    • 抽象部分と実装部分を分け、それぞれを独立に拡張できるようにするパターン。
    • 例:描画API(実装)と図形定義(抽象)を分けて、両者を自由に組み替える。
  3. Composite(コンポジット)
    • 再帰的な構造(木構造)を表現し、個別要素と集合体を同一視して取り扱えるようにするパターン。
    • 例:ディレクトリとファイルなど、ツリー構造を再帰的に扱う場合。
  4. Decorator(デコレーター)
    • 機能を追加したい対象を継承ではなく、ラップ(包み込む)することで動的に拡張するパターン。
    • 例:ストリームに対するバッファリング機能を追加するクラスなど。
  5. Facade(ファサード)
    • 複雑なサブシステムに対してシンプルなインターフェイス(窓口)を提供し、利用者が内部構造を意識しなくてもよいようにするパターン。
    • 例:複数のAPI呼び出しをまとめて一つのメソッドとして提供する。
  6. Flyweight(フライウェイト)
    • インスタンスの重複を避け、共通化できる部分を共有してメモリ使用量を抑えるパターン。
    • 例:文字オブジェクトのように大量に出現する可能性があるものを共有する。
  7. Proxy(プロキシ)
    • ほかのオブジェクトへのアクセスを制御する代理人オブジェクトを用いて、機能追加(遅延読み込み、認証など)を行うパターン。
    • 例:リモート呼び出しや、アクセス制御を行う際に使われる。

2.3 振る舞いに関するパターン

  1. Chain of Responsibility(責任の連鎖)
    • 処理を複数のオブジェクトに分散し、ある要求に対して自分が処理できなければ次のオブジェクトに渡す、という形で連鎖的に処理を行うパターン。
    • 例:エラーハンドラーやログフィルタリングなど。
  2. Command(コマンド)
    • 操作や処理をオブジェクトとしてカプセル化し、呼び出し元と実行対象を分離するパターン。
    • 例:メニューのアクションをコマンドとして扱い、取り消し(Undo)や再実行(Redo)を実現する。
  3. Interpreter(インタプリタ)
    • 独自言語や記法で記述された文を解釈(評価)する仕組みを構築するパターン。
    • 例:簡易スクリプトを読み取って動作させるなど。
  4. Iterator(イテレータ)
    • 集合体(コレクション)の要素を順番に走査するための手段を提供するパターン。
    • 例:リスト、配列、ツリーなどを統一的に扱う。
  5. Mediator(メディエーター)
    • オブジェクト間の複雑なやり取りを仲介役となるオブジェクトに委ね、相互依存関係を減らすパターン。
    • 例:GUIの部品間のやり取りを中央管理するクラスに一任して、各部品間の依存を少なくする。
  6. Memento(メメント)
    • オブジェクトの状態を保存しておき、必要に応じてその状態を復元できるようにするパターン。
    • 例:テキストエディタのアンドゥ機能やゲームのセーブ機能。
  7. Observer(オブザーバー)
    • 特定のオブジェクトの状態変化を、依存する複数のオブジェクトに通知するパターン。
    • 例:イベントリスナーやPub/Subの仕組み。
  8. State(ステート)
    • オブジェクトの内部状態によって振る舞いを切り替えるパターン。
    • 例:自販機の状態(コインが投入されている、在庫がある等)によって動作を変える。
  9. Strategy(ストラテジー)
    • アルゴリズムファミリをカプセル化し、実行時に交換可能にするパターン。
    • 例:ソートアルゴリズムを切り替えたい時にStrategyパターンを使う。
  10. Template Method(テンプレートメソッド)
    • 処理の枠組み(テンプレート)をスーパークラスで定義し、具体的な処理はサブクラスで実装するパターン。
    • 例:フレームワーク側である程度の処理の流れを決め、実装者が部分的なメソッドをオーバーライド。
  11. Visitor(ビジター)
    • データ構造から処理を分離し、処理を追加したい場合に既存構造を変更せずに拡張できるパターン。
    • 例:構文木に対して新しい処理を追加したい場合に、各要素にVisitorを適用する。

3. デザインパターンを学ぶメリット

  1. 共通言語としての役割
    「Adapterパターンで作りましょう」のように、チーム内で設計意図を簡潔に共有できます。
  2. 再利用性・保守性の向上
    良くある問題に対する解決策がまとまっているため、独自のやり方を生み出すより安全かつ生産的です。
  3. 変更に強い設計が可能
    コードの変更や拡張が必要になった際に、パターンに従って設計していれば影響範囲を抑えられます。

4. デザインパターン導入時の注意点

  • パターンの乱用に注意
    デザインパターンは便利ですが、常に適用すればよいというわけではありません。過度に使うとコードが複雑になることもあります。
  • プロジェクト規模や要件を考慮
    小規模プロジェクトではシンプルなコードのまま実装したほうが分かりやすいケースもあります。
  • 学習時は小さな例で理解し、実際のプロジェクトに合わせて応用
    サンプルコードや既存ライブラリの実装を読むことは非常に有効です。

5. まとめ

デザインパターンは、オブジェクト指向の代表的な設計課題に対する「使いまわせる知恵の結晶」です。各パターンは目的や適用シーンが明確に定義されており、特定の問題を解決するための骨組みを提供してくれます。実際の開発の中で、どの問題にどのパターンが適用できるのかを判断しながら使い分けることが大切です。

もしさらに詳しく学習したい場合は、Gang of Four(GoF)の『Design Patterns: Elements of Reusable Object-Oriented Software』が原典としておすすめです。日本語訳版や、パターンごとの解説サイト・動画なども多数ありますので、実例を交えながら学んでみると理解が深まるでしょう。


ここまで

実際に開発していて機能を追加していくと何処かで機能を見直す時があります。その際にデザインパターンを知っていれば最小の手直しで済みそうです。

Facadeが個人的には再利用性と可読性が高そうなので好みかなと思いました。その他もあまり使用する場面がパッと出てこないデザインパターンもあったので実際にコードを書いて理解したいと思います。

特にFacadeの設計方法は可読性と拡張性が高いので個人的には取り入れたいと思いました。