デザインパターンの世界観です。
自分の書いたブログ「神々とともに生きる詩人」2021/02/02より。編集済み。
デザインパターンの概要について言えるのは、クラスをどのように設計するか、ということです。
ある機能を実装する上での、内部のクラス設計、たとえば、抽象的なテンプレートやAPIから実装したり(Template Method, Factory Method)、段階的に基礎から構築したり(Builder, Abstract Factory)、受付や代理人にやらせたり(Facade, Mediator, Proxy)、通知を行ったり(Observer)します。
同時に、ある機能を実現する上での、スマートなクラス設計を行います。
要素と入れ物を同一視したり(Composite)、要素に飾りをたくさんつけたり(Decorator)、あるいは状態(Memento, State)やミニ言語(Command, Interpreter)でクラス設計を表現します。
ほかにも、インスタンスがひとつであることを保証したり(Singleton)、訪問者を作ったり(Visitor)、責任をたらい回しにしたり(Chain of Responsibility)、あるいは、機能のクラスと実装のクラスを分けたり(Bridge)します。
マルチスレッドにおいては、スレッドの待機や排他制御を表現します。
後日注記:このほか、次の要素の数え上げをする(Iterator)、一枚上にラッパーをかぶせる(Adopter)、インスタンスをnewするのではなく複製して作る(Prototype)、アルゴリズムだけを変える(Strategy)、インスタンスを可能な限り共有して軽くする(Flyweight)などといったパターンがあります。
詳しくはJava言語で学ぶデザインパターン入門が参考になります。
Java言語で学ぶデザインパターン入門を参考に執筆しました。
Iteratorパターンは、反復子とも呼ばれ、単に連続したデータをひとつひとつ順番に操作するためのパターン。すべてのシーケンス型のデータが配列のように連続した領域に固定長で確保されるとは限らないので、ひとつひとつ順番にアクセスするための専用のAPIを用意することがある。C++, Java, Pythonなど、多くの高水準言語では、クラスライブラリや言語仕様としてイテレータを持っている。
Adopterパターンは、一枚上にラッパーをかぶせる。たとえば言語やAPIが違うなどの理由で、そのままでは別のプログラムから使うことのできないプログラムに対して、ラッパーをかぶせることでコンポーネントとして利用することができる。OSや言語、ネットワークの垣根を越えて分散オブジェクトを実装する、CORBAなどが具体例。
Template Methodパターンは、親クラスの集団をまず大まかに設計し、派生クラスのほうで具体的な詳細を設計するパターン。こうすることで、大まかなフレームワークを作っておいた上で、それぞれの場面や状況に必要な具体的な実装を簡単に開発することができる。Ruby on Railsのような多くのMVCフレームワークはこの方式をとっている。
Factory Methodパターンは、Template Methodを用いて、インスタンスを自動的かつ大量に生成するような工場を用意する。
Singletonパターンは、どんな場合においても、必ずインスタンスがひとつしかないことを保証する。残念ながら、メリットよりも多くの弊害があるため、現代ではアンチパターンとして知られている。
Prototypeパターンは、クラスベースのオブジェクト指向のように、すべてのインスタンスを設計図(クラス)からnewして作るのではなく、既にある部品(プロトタイプ)を複製することで新しいインスタンスを作る。JavaScriptなどではこれを「プロトタイプベース」のオブジェクト指向と呼ぶ。たい焼きのたとえ話であれば、たい焼き器からたい焼きを作るのではなく、たい焼き複製魔法を使って既にあるたい焼きからたい焼きを作るイメージ。
Builderパターンは、部品ひとつひとつを構成する基礎と土台を築き、部品からインスタンスを作る。メニューやバー、ボタン、ラベルなどを用いてGUIアプリケーションを作るようなイメージ。それぞれが知る範囲が限られているため、将来的に別のものに取り替えたり、改良したりすることが容易となる。
Abstract Factoryパターンは、それぞれの抽象的なインターフェースだけに注目して、インターフェースを組み合わせて抽象的な製品にまとめる。APIだけに注目し、抽象的な工場、抽象的な部品、抽象的な製品を作っていく。たとえば、OSや言語のシステムコールやクラスライブラリのようなイメージ。
Bridgeパターンは、Template Methodパターンのような抽象クラスの役割を考えた場合、ここには二つの「継承」あるいは「実装」の意味がある。まず、「機能を追加する」というクラス階層で、親クラスには存在しない機能を派生クラスで新しく追加する。もうひとつは、「抽象的なAPIを実装する」というクラス階層で、親クラスで抽象的に表現されたインターフェースを派生クラスで実装する。このように二つのクラス階層が存在するため、Bridgeパターンでは、機能のクラス階層と実装のクラス階層を、二つの独立したクラス階層に分離する。
Strategyパターンは、たとえばソートや探索のアルゴリズムのように、アルゴリズムだけを取り換え可能にし、場合場合によって別のアルゴリズムを使う。OSの低レベルレイヤやデータベースのインデックス方法などもこの例。あるいは、UNIXのシステムコール(POSIX)が定義されていれば、どんなCPUアーキテクチャであっても同じPOSIX APIのカーネルが動くのと同じイメージ。
Compositeパターンは、たとえばディレクトリとファイルのように、入れ物と内容を同じものであると見なす。こうすることで、「入れ物の中にある入れ物の中にある内容」のようなものを単純に表現できる。
Decoratorパターンは、たとえばHTMLのタグやTeXのマークアップのように、飾りと内容を同じものであると見なす。たとえばワープロソフトを開発する際などに、装飾や図などをテキストと同じものであると扱うことができる。
Visitorパターンは、複数のデータ構造に対する訪問者を作る。たとえば、データ構造が一つであった場合、そのデータ構造を表すクラスの中に処理を書けばいい。だが、スタックでもキューでも連結リストでも成り立つ処理を書きたい場合、これをどこに書けばいいか。このような時、データ構造から処理を分離し、データを訪れる「訪問者」を作る。
Chain of Responsibilityパターンは、責任のたらいまわし。鎖のように複数のオブジェクトを繋いだうえで、その鎖の中を渡り歩くことでオブジェクトを決定する。たとえばJavaScriptのプロトタイプ継承は、そのクラスに存在しないメソッドをprototypeがあればそこから探し、なければprototype.prototypeから探す。あるいは、ルータは自分の隣接ネットワークに目的のホストがあるかを探し、なければどのルータに送ればそのホストに近づけるかを知っているため、そのルータにパケットを転送する。そしてルータからルータ、その次のルータへとパケットは転送され、目的地へと辿り着く。
Facadeパターンは、プログラムに「窓口」を作る。すべての目的の場所やその役を知らなくても、そうした場所や役を知っている専用の窓口に問い合わせ、窓口が案内し、目的地へと辿り着く。ひとつのルータ(ゲートウェイ)に遅られたパケットは、その宛先IPアドレスを見てネットワークの内部にある適切なホストへと中継・転送される。このようにすることで、ルータのファイアウォールがしっかりしていれば、不正なパケットをそこで排除できる。
Mediatorパターンは、プログラムに「相談役」を用意し、その相談役との間でプログラムの中での連絡を行う。ひとりひとりにひとりひとりが個別に指示を出すのではなく、ひとりのマスターが存在し、そのマスターへとすべてのスレーブは報告し、そのマスターがすべてのスレーブに指示を伝える。
Observerパターンは、変化に対して観察し、「通知」を行う。たとえば、GTKやQtのようなツールキットは、ボタンがクリックされるかどうかなど、起きるイベントを常に監視する。そして、イベントが起きた時点で、それを必要なプログラムに通知する。変更されたファイルだけをスキャンするアンチウイルスソフトウェアや、設定の変更を監視するレジストリ的なシステムもこの具体例。
Mementoパターンは、状態を保存し、いつでも元の状態に復元できるようにする。viやEmacsのようなテキストエディタでは、その状態を常に記録しており、必要ならアンドゥしていつでも元の状態に戻せるようになっている。データベースなどの差分バックアップもこれであると言える。
Stateパターンは、状態をクラスとして保存する。その時の「状態」と呼ばる範囲はとても多く、プログラムを表すプロセスや、スケジューリングを行う際のそれぞれのコンテキスト、ワープロを使っている時のその時の文書の状態など、多くのことが「状態」として表せる。このような状態をクラスとして保管する。クラスとして表現することで、ある状態から派生した別の状態を表現したり、無限にデータから条件分岐しなくても、クラスを用いて状態を管理できる。
Flyweightパターンは、使用するインスタンスをできるだけ作りすぎずに共有して「軽量さ」を保つ。たとえば、Linuxのコマンドはそれぞれ分かれているが、このコマンドはフィルターやファイル処理だけを見れば、共通の機能が多い。このような機能を共有することで、全体の軽量さを保つ。現に、多くのコマンドの機能をひとつのコマンドで実装し、軽量さを保つ、組み込み向けのBusyboxと呼ばれるソフトウェアがある。
Proxyパターンは、本人の代わりに「代理人」を立て、代理人でもできることは代理人にやらせ、必要な場合にのみ本人を要求する。たとえば、最新の情報が必要ないのであれば、Webページはローカルにキャッシュされた内容でいい。Webページが更新された場合のみ、インターネット接続を用いて最新のページ内容をダウンロードすればいい。この場合、ローカルにキャッシュされた内容を「本当のページの代理人」とすることができる。
Commandパターンは、「命令」を表すクラスを作る。たとえば、ひとつの命令をクラスとして表現することで、命令をオブジェクトとして管理することができる。このようにすれば、命令を複数使った別の命令にすることも容易に可能となる。HTMLやTeXのマークアップなどが実例である。また、命令はイベントともいわれ、ユーザーのマウスやキーボードの操作もイベントに相当する。
Interpreterパターンは、プログラムの行う処理や問題を「ミニ言語」で表現する。たとえば、データベースを操作する場合、柔軟にテーブルを自動作成するためには、SQLというミニ言語があると効果的である。ほかにも、HTMLの装飾をする場合、CSSというミニ言語があると、ひとつひとつのHTMLを編集しなくても、ミニ言語の文法を用いて必要なデザインをいっぺんに変えることができる。その代わり、SQLやCSSを解釈する言語処理系が必要となる。これはまさに「ミニ言語」である。
2023.07.05編集
ソフトウェアを開発し、設計する時に出てくる「良くあるパターン」をまとめたもの。
僕は以下の本を読んで、とても勉強になった。みんなも読んでほしい。(注意点:本当はまだそんなに読めていない。)
デザインパターンは、Javaのようなオブジェクト指向と相性が良い。
特に、クラスライブラリを使うだけのプログラミングから、クラスとインスタンス、継承と実装、そしてスレッドを用いた、オブジェクト指向のプログラム設計が出来るようになる。
たくさんのパターンを知っておくことで、プログラム要件が来た時に、どのようにすれば実装出来るか、事前に準備しておくことが出来る。
いわば、「設計のネタ帳」である。
このページに書籍の全てを載せてしまうと、コピーになってしまうので、内容は自ら本を買って読んでほしい。下の説明は目次の引用に過ぎない。
自分の書いたブログ「神々とともに生きる詩人」2021/01/14より。
オブジェクト指向で設計するために、頻出するパターンを教える、GoFのデザインパターンですが、乱用に注意しましょう。
あくまで、大規模なソフトウェアを設計する上で、頻出するパターンを示したものであり、必要ないのに「設計が美しくなる」と思って、なんでもかんでもデザインパターンを使ったところで、保守しやすいソフトウェアにはならないからです。
逆に、複雑で理解できないプログラムになってしまいます。
しかしながら、デザインパターンの用語が分からないと、プログラマとの間で言葉が通じないことがあります。
そのためにも、用語として知っておくとよいでしょう。
以下のリンク先に、より詳しいことが書いてあります。
GoFのデザインパターンも古くなり、今では批判も存在します。
たとえば、Iteratorパターンは最近のモダンなプログラミング言語の言語仕様では標準的かつ自明のことであり、Template Methodパターンは継承を使った実装の際のテクニックに過ぎません。
Singletonパターンはアンチパターンだと言われています。
関数型プログラミング言語の支持者は、「デザインパターンというものがあること自体が、関数型言語よりもオブジェクト指向言語のほうがプログラミングの抽象化能力が低いということを明確に表している」と批判します。
詳しくは以下が参考になります。
以下にデザインパターンの一覧と解説があります。プログラムの設計の前に参照のこと。
特におすすめなのはBuilderパターンです。
23種類の設計のネタ帳。
有名なものにBuilderパターンなど。
イテレータやアダプタのような単純なパターンから、抽象ファクトリーや状態・記憶や代理人を扱ったりする設計上のデザイン、ミニ言語などの高度なパターンもあるので、一度目を通しておこう。