Futureアドベントカレンダーの3日目のエントリーです。昨日は@yamat2667さんのFlutterの記事でした。
デザインパターンというと、普段のプログラミングから一歩踏み込んで設計力を上げたい人が履修すべきコンテンツのように思われているようなツイートなりを見かけます。オブジェクト指向言語全般に使える知識だ、とか、開発者同士のコミュニケーションに使えるぞ、とか、コードの質が上がるぞ、とか。
一方で、パターンを詰め込もうとすると逆にコード量が増えて、パターン由来の変なクラス名が増えたりして、設計が歪むぞ、というのも当初から言われてきました。
実際のところデザインパターンというのはなんだったのかをいろいろな本を眺めつつ考えました。
デザインパターンの前提をきちんと読む。
デザインパターンのオリジナルは、オブジェクト指向における再利用のためのデザインパターンです。デザインパターンの原著が出版されたのはちょうど30年前の1994年で、日本語版が出版されたのは1999年です。どのような意図をもって書かれたのかをピックアップしてみます。
概論には以下の説明があります。
P14より
オブジェクト指向システムにおいて重要でかつ繰り返し現れる設計を、それぞれ体系的に名前にし、説明を加え、評価したものである。
P16より
プログラミング言語の選択は重要である。なぜなら、どの言語を使うかによってどのような観点でデザインパターンをまとめるかが違ってくるからである。我々のパターンはSmalltalk/C++レベルの言語形態を想定している。その選択によって、容易に実現できることとできないことが決まる。たとえば、もし、我々が手続き型言語を選択していれば、Inheritance(継承)、Encapsulation(カプセル化)、Polymorphism(ポリモルフィズム)といったデザインパターンを組み入れたであろう。(中略)CLOSはマルチメソッドを有している、Visitorパターンのようなパターンは必要性がなくなるのである。
最初のページv(ローマ数字の5)の序文にはこう書かれています。
デザインパターンは(中略)標準的なオブジェクト指向言語を用いて実装できるものばかりである。ただし、アドホックな解放よりは若干工数はかかる。しかし、この工数のおかげで、柔軟性や再利用性の増加という非常に大きなメリットが得られるのである。
これらを勘案すると、デザインパターンは30年前のC++やSmalltalkの最大公約数の機能に合わせて作られている、ということがわかります。実装継承よりかはインターフェース指向とかは強調されていますが、インターフェースを提供した言語が産業界でメジャーになってはいない、という状況を鑑みると、クラスがあって、継承ができる言語のポリモーフィズムの機能を活用する方法にフォーカスしていることがわかります。また、パラメータ化した型を考慮したパターンはない(P34)、つまりテンプレートやジェネリクスを前提とするものは含まないというのも書かれています。
当時の言語のレベル
C++の最初の規格は1998年でC++98と今は呼ばれています。1991年に初めてテンプレートは導入されましたが、STLは入った直後ぐらいでしょうか?
C++で開発するということは、現在のプログラミング言語では当たり前に提供されているようなランタイム機能を自作するところから始めなければなりませんでした。リストや文字列クラスといった、今時の言語が持っていて当たり前の標準的な機能もまだない状態でした。Microsoftのクラスライブラリでデスクトップアプリケーションを作るにはCListやCStringクラスを使い、Qtを使うならQList、QStringを使い、Borland C++なら・・・という感じですね。今でもフレームワーク固有のコードを書くならそのような状況はあるのですが、STLとの相互の乗り入れはしやすくて、汎用のロジックを書いて共有はだいぶやりやすくなりました。
RTTIやnamespaceもまだなかったぐらいかと思います。ということなので、動的に何かをするということはまず不可能で、クラス、メンバー変数、継承によるポリモーフィズムのみで対処することが必要だったということです。例えるなら詰将棋ですね。
なお、出版当時はC#はおろか、1995年リリースのJavaやRubyはまだありません。
デザイン・パターン本家で変わろうとしていた内容
こちらのサイトに15年前のインタビュー記事があり、デザインパターンを変更するなら、という内容が最後に書かれています。
- Factory Method, Abstract Factoryは統合する
- Null Object, Type Object, Dependency Injection, Extension Object/Interfaceを足す
現時点で、改訂版のデザインパターンの本が出るという話は聞こえてきません。ずっと23のままです。
ちなみに、最近もデザインパターンの書籍は何冊か出ていますが、23個全部紹介するのではなく、代表的なものをピックアップし、いくつかのパターンを説明しない、という構成になっているようです。
今時の言語機能を使って実現するとどうなるか
C++98以前とC++11以降で大きく違うのはクロージャ機能の追加です。
Commandパターンは「後から呼び出せるようにする」ということなので、クロージャそのものです。
Template Methodは、カスタマイズポイントをクラスの利用者に提供するというものです。これもクロージャを使い、高階関数として実装する方が今時でしょう。ボタンをカスタマイズするのに、onClickイベントに関数を設定すればおしまいというのが今の流れで、ボタンクラスをオーバーライドして派生クラスを作ろう、みたいなことはないでしょう。そうなるとStragegyパターンも差がなくなります。
リフレクションがあればPrototypeパターンもなくせるかもしれないですね。
今時はイミュータブルなデータ構造が流行っているのでMementoみたいな変更ロジックの持ち回りではなく、そもそも全部メモリのスナップショットが勝手に取れるような状態管理ライブラリも見かけます。これを管理するUndo/Redo実装もReduxでは見かけますね。全部コピーはするけど差分抽出ロジックでメモリを節約、みたいなこともできそうですね。
そういえば、以前買って読んだなと思ってFunctional Programming Patterns in Scala and Clojureというのを読み返してみたら、2部がオブジェクト指向のパターンを関数型で置き換えるという内容でした。まあIteratorをreduceで処理しているのは良いのか?という感じはありますが・・・
サッと考えただけでもこれだけ「いらないのでは」というものが増えています。
他のパターンランゲージ
関数型スタイルのコーディングも増えています。リスト処理だったり、高階関数だったり、リスト処理だったり。非関数型言語でも参考にされることが増えています。再起処理、パターンマッチ、遅延評価あたりは言語の支援が必要なので真似しにくいケースも多いのですが、これらも立派に設計のためのパターンランゲージでしょう。
DDDも、原著を読むと表紙の返しの部分にパターンランゲージが書かれています。これについては以前会社のブログに書きました。
これが参照している通称PofEAA、エンタープライズアプリケーションアーキテクチャパターンもパターンランゲージです。
並行処理だとPatterns for Parallel Programmingという洋書もありますが、日本だと結城浩さんの増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編の方がメジャーですかね。ちょっとレイヤーがだいぶ違う感じで、前者はOpenMPとかそっち系に特化しており、結城の方はアプリケーション実装よりです。
多種多様ないろんなパターンを集めて紹介する書籍が、Pattern Oriented Software Architecture シリーズで、volume 1からvolume 5まで出ています。日本語訳はvolume 1だけですかね。
とはいえ、これらの本もデザインパターンがブームになってから10年以内に出た本ばかりで、その後は音沙汰がないですね・・・最近聞くのはクラウドシステムのパターンぐらいですかね。
パターンランゲージの限界
パターンランゲージには更新が難しいというのが問題かな、と思います。
パターンランゲージは、そこに含まれるパターンの相互作用で設計が想起されていくのを目指して、多くの人が関わって議論を重ねて作り上げていきます。一度出来上がってしまって、そこが固定化されてしまうと、いざ新しいコンテキストが加わって、新しいパターンを追加するかも、その結果他のパターンがいらなくなる・統合される、分割した方が良い、みたいな状況への対応が難しいのではないかと思います。単なる追加は良いものの、既存のパターンに手を加えるとなると、オリジナルのパターンの作者自身がやらないと「削除」のジャッジは下せないように思います。
あるいは、過去の議論の流れが全て記録されており、何かしらの白黒はっきりつけるシステムがあって更改できるようなメカニズムがあれば良いのかもしれませんが、単にパターンを作るよりも、そのようなシステムに合わせて繰り返し使えるような議論をしていく、というのは相当難易度が高いでしょう。
デザインパターンも、言語機能の発展にともない、関数型のパターンとかもガンガン取り入れつつ、「こういう言語機能がない場合はこのパターン、ある場合はその言語の機能を使ってね」「こういう実装もあるよ」みたいな発展があれば今でも現役が張れたのではないか、という気がしています。
ただ、それもきちんと現在のスナップショットがわかるような状態になっていたり、大幅な更新がなく安定している必要があります。エクストリームプログラミングのプラクティスはパターンランゲージであるものの、PofAA記事で書いた通り、12→13→24(11+13)→19だったか変化し続けました。Clean Agileはケント・ベックとまた別の13です。当時はネットでの発信ではなく書籍がメインだったというのはありますが、あまりに変化し過ぎても、それを参照するものが追いつけずに新旧混在という状況になって、その後は議論が盛り上がることはなくなってしまいました。
デザインパターン自身は変化がなかったがゆえに、他のパターンランゲージで紹介したように並列処理のデザインパターン、分散システムのパターン、エンタープライズアプリケーションのパターンなど、レイヤーごとのパターンが作られて、デザインパターンが触れていない領域をカバーしていくという流れになったとのかなと思います。
まとめ
デザインパターンは記述力が高くなかった当時の言語にあわせて、なおかつオブジェクト指向以外の機能は使わずに作るというのを目標にしていました。ついでにいえば、当時うちにあったパソコンのメモリは5.6MBでした。そういう時代だからこそ求められたものというのはあったと思います。
当時は日本でオブジェクト指向が爆発的なブームになる前夜、ぐらいでしたが、仮にその後全てを支配する時代であれば「どんな言語でも使えるもの」だったのかもしれませんが、近年はオブジェクト指向機能は使わない、実装継承はやらない、という言語も増えてきています。GoやRustは言語レベルでそうですし、JavaScriptなんて、ES4で大揉めして、ES6(2015)でようやくクラスが入ったのに、JavaScriptプログラマーはAngularのコードを書く時ぐらいしかクラス使わないですし。
また、言語そのものにもオブジェクト指向とは関係なく多数の機能が追加され、昔ながらのクラスだけでは扱いきれない機能も増えてきています。ジェネレータ、非同期I/Oなどですね。
デザインパターンそのものは、現代人がシェークスピアを読むみたいに、当時の状況を鑑みながら読んでいくには良いかなと思いますが、「これでオブジェクト指向を学ぼう」「ソフトウェア設計を学ぼう」という考えで扱うには少々時間が経ち過ぎてしまった、あるいは更新のタイミングを失ってしまったものなのかな、という感想を持っています。
明日は@bigface00さんの「BigQuery環境を守る!アクセス制御ことはじめ」です。