はじめに
いかにもベタベタなタイトルですが、僕自身の考えをまとめる意味も含めつつ、どなたかのお役に立てたらと思い、記事にまとめてみました。
対象にしたい読者層
- 最近はじめて GoF のデザインパターンを学んだけど、いまいちピンと来なかった方々
- オブジェクト指向も学んできたけど、これまたいまいちピンと来ていない方々
おことわり
独学と記憶を頼りに記述しているため、正確性に欠いている可能性があります。その際はコメント等でご指摘いただけると幸いです。また、書いている僕自身の個人的思いが多分に含まれているため「それあなたの感想ですよね」と言われると「はい」としか答えられない部分もあるかもしれません。予めご承知おき願います。
デザインパターンの前に
デザインパターンをはじめて学んだときに、これは全く新しい知識だと感じる方もいらっしゃるかもしれません。しかし、デザインパターンは他のあらゆる知識体系から独立した世界の話ではありません。
以下に示す知識を押さえていると、デザインパターンと向き合ったときに理解が進みやすいと思います。
モジュール結合度、凝集度
みなさんも「モジュール結合度は低い方がよく、凝集度は高い方がよい」というのは一度は目にした(耳にした)ことがあると思います。これらの尺度の重要性は、デザインパターンを学ぶときにも変わりません。デザインパターンの適用は、疎結合・高凝集な設計を実現するための手段になり得るということです。
SOLID 原則
SOLID 原則とは、オブジェクト指向設計をより柔軟にメンテナンスしやすい形にるための基本的な 5 つの原則です。
- S:単一責任の原則(Single Responsibility Principle)
- O:開放・閉鎖原則(Open Closed Principle)
- L:リスコフ置換原則(Liskov Substitution Principle)
- I:インタフェース分離の原則(Interface Segregation Principle)
- D:依存性逆転の原則(Dependency Inversion Principle)
上記の頭文字をとって SOLID と名づけられ、5 つの原則としてひとまとめに紹介されるようになったのは GoF のデザインパターンがまとめられた後のことですが、今から学ぶ方々にとってはこれらの知識を体系的に学んでいた方がデザインパターンの理解も深まりやすいと思います。中には前提知識が揃ってないと取っつきづらい原則もあるので、デザインパターンの学習と行ったり来たりでもよいかもしれません。
なお、各原則についてはここでは説明しませんので、他の有益な書籍や記事等を参照願います。
継承より委譲
オブジェクト指向を学んだときに「派生クラスに基底クラスの資源を継承することで、差分プログラミングが実現できる」という話題は必ず一度は出てきていると思います。
しかし、残念ながら、継承を濫用することはバッドプラクティスとして知られています。冷静に考えると、差分プログラミングを実現するだけなら単なるモジュール呼び出しでも十分実現できるはずですし、継承を使うと基底クラスと派生クラスの間で強い関係性ができてしまうため柔軟性を欠いてしまい後から苦労してしまうことが多いです。
ですので、特段の理由がない限り、機能を拡張する際は委譲により実現すべきと言われています。ちなみに、特段の理由、すなわち継承が許されるケースとして is-a の関係があります。デザインパターンの中にも、継承より委譲をうまく取り入れたパターンがいくつか見られますし、継承ならではの特性を活かしたパターンも見られます。
本題:デザインパターンって
少し前置きが長くなりましたが、ようやく本題です。
デザインパターンって、結局いったい何もので、どうつきあっていけばいいんでしょう?いくつかの問いかけを交えつつ、読者のみなさんが何らかの気づきが得られるように試みてみます。
名前と形を憶えておけばよい?
GoF のデザインパターンは、23 種類もあります。きっと、はじめて学んだ方にとっては、名前と形を憶えるだけでも大変だと思います。しかし、残念ながら名前と形を憶えているだけでは不十分です。
これは何パターン?
さて、これはクラス図の断片ですが、
これは何パターンが適用されているかわかりますでしょうか?
残念ながらこれだけではどのパターンか断言することができません。これらのクラス群が「何を実現しようとしているのか」「(将来的に起こり得る)どのような問題を解消しようとしているのか」によって答えは違ってきます。
たとえば、これらのクラス群がコンポーネントの入れ子構造やツリー構造を扱いたくて設計されたものであれば Composite パターンになるでしょうし、委譲を使って付加的な機能追加を柔軟にできるように設計されたものであれば Decorator パターンになるでしょう。
もちろん、クラス名や定義されているメソッドから類推することは可能です。(先の出題ではわざとクラス名やメソッド定義は省略していました。) ですが、その名前付けでさえ「○○パターンを適用したから、それがわかる名前をつけておこう」という経緯があるはずです。「この名前をつけたから○○パターン」ということにはならないです。
目的を知ることが大事
ここまでの流れから、デザインパターンは名前と形を憶えるだけでは不十分で、「何を実現しようとしているのか」「(将来的に起こり得る)どのような問題を解消しようとしているのか」といった、各パターンの目的を押さえることが重要になることはおわかりいただけるかと思います。
もちろん、名前と形も重要です。名前がつくことで開発者間の共通認識となり、円滑なコミュニケーションを助けます。また、定石的な形があることで、開発者の理解の助けになりますし、同じ問題を解決する際に各々が頭を悩ませる必要がなくなります。デザインパターンを通じて、先人の優れた知恵を借りることができるのです。
古いんじゃない?役に立たないんじゃない?
GoF のデザインパターンがまとめられたのは今から四半世紀くらい前のことです。古いのは紛れもない事実です。では、役に立たないかというと、全くそうとは言い切れないと思います。
よいプラクティスとして
デザインパターンのプラクティスは優れたものが多いです。代表的なのは Iterator パターンであり、そこそこ最近な言語でイテレーターが言語仕様として存在していないものはないんじゃないかと思います。
また、少し切り口は異なりますが、Chain of Responsibility パターンの「自分の責務が果たせるものだけは自分が扱って、あとは丸投げ」するような処理機構は、例外処理(いわゆる Try~Catch 構文の Catch ブロック側)と似ています。
他にも色々ありそうですが、デザインパターンを知ることで、オブジェクト指向設計の領域にとどまらず普遍的な設計のセオリーを学べるのではないかと思います。
現在でも有効なパターンもある
先に紹介した Composite パターンや Decorator パターンなどもそうですが、今でも有効な、むしろ当たり前のように使われているパターンがいくつもあります。これらのパターンは現役バリバリで活躍していますので、みなさんもいろんなフレームワークやライブラリ等に触れる中で「あっ、これデザパタで学んだところだ!」という発見が得られるかと思います。もちろん、デザインパターンを知っていることで対象への理解度が深まり、習得もスムーズになることが期待できます。
知識の更新も必要
もちろん、すべてのパターンが現在も有効かといわれるとそうでないものもあります。GoF のデザインパターンは、かつてオブジェクト指向言語が今ほど高機能でなく、かつ効果的な設計手法を模索していただろう時代にまとめられた、ある意味雑多なカタログ的な側面もあるのも事実だと思います。
ですので、時代の経過とともに残念ながら化石となったパターンもあるのは否めないかと思います。GoF のメンバーの 1 人であるエリック・ガンマ氏が 2009 年頃に受けたインタビューで「今もしデザインパターンをまとめなおすとしたら」という興味深い話題に触れていますので、一読するのもよいかと思います。
Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson
また、言語の進化や新しいパラダイムの台頭により、より優れた代替手段が出てきたものもあります。先に挙げたイテレーターも、今となっては関数型の考えを取り入れた方がうまくいく場合もあります。他にも、TemplateMethod パターンでロギングやトランザクション制御を埋め込んでいるような設計があれば、アスペクト指向の考えを取り入れた方がよいケースもありそうです。
大事なのは、考え方の新旧に関わらず、常によいものを取り入れて採用していくことだと思います。古いものに固執しすぎて新しい優れた考えを吸収できなくなるのもよくないし、新しいものに流されてばかりで本質を見失うこともよくないことだと思います。
うまく使えるかなあ…
これはなかなか一筋縄にはいかないと思います。ここまで述べてきたとおり、目的を無視したデザインパターンの適用は失敗に終わる可能性が高いです。(とはいえ、パターン自体が洗練されているので何も考えなくてもぴったりはまることも多いですが。)
しかし、だからと言って、遠巻きに見ているだけではなかなか身につかないと思います。実践を繰り返して、成功も失敗も受け入れて初めて身につくものだと思います。ですので、適度なサイズがある題材をピックアップして何かを設計してみるのがよい練習になるんじゃないかと思います。
僕の場合は、ライフゲームを作ってみたりもしています。途中で力尽きているのですが、 GitHub に上げてます。このような感じで、実装のプログラミングにだけフォーカスをあわせるのではなく設計も意識したプログラミングの練習をするのもひとつの手ではないかと思います。
まとまってないまとめ
デザインパターンは、一見すると単なる古びたカタログ集にも見えますが、中身をよく知ると、優れた先人たちの知恵が詰め込まれていることがよく理解できます。そして、それは今もなお価値のあるものです。
みなさんもデザインパターンに触れ、その価値を知り、優れた技術者へと成長するための糧とすることができれば素晴らしいことだと思います。
