[技術講座]
(株)オージス総研
福田 直樹
デザインパターンの解説は、ここ数年書籍や雑誌の記事などで多く目にします。しかし、デザインパターンというと小難しいイメージだったり、一部のマニアックな設計者だけが使うものだ、というような感覚を持たれている方もいらっしゃるのではないでしょうか。また、何となくは理解できた気はするけれども、効果が実感できずに適用に二の足を踏んでいるという方もいらっしゃると思います。
今回は、ケーススタディにデザインパターンを適用した設計を検討し、主にデザインパターンを適用しない場合と適用した場合の違い、メリット、考慮点を示すことによって各デザインパターンを理解をしていただくような形で進めたいと思います。読んでいただく方のデザインパターン学習の動機付けになり、かつ効率よく理解できるような内容にしていきたいと考えています。
今回の連載は、Javaの基本文法は理解しており、これからさらにステップアップして設計力をアップしたいという方、デザインパターンを適用した設計とはどういうものかを知りたいという方を主な対象としています。デザインパターンはオブジェクト指向設計、ひいてはソフトウェア設計のよりよい構造を作っていくための指針となるものです。ぜひソフトウェア設計に携わる方全般に読んでいただき、今後の業務の参考にしていだけると幸いです。
個別のデザインパターンの解説は次回に譲るものとして、第1回の今回は、デザインパターン全体を学習する上で押さえておくべき知識と学習する際のポイントなどを確認します。
※雑誌『Java WORLD』2006 年 4 月号に掲載した記事のオリジナル原稿を Java WORLD 編集部の了解を得て掲載しています。
まず、簡単にデザインパターンについて説明しておきましょう。デザインパターンとは、一言で言うと、設計のノウハウ集です。そこには、多くの開発者たちがこれまで検討を重ね設計してきたノウハウが蓄積されています。設計時にはソフトウェアとして実現していくためのしくみを作成していきますが、その際にはよく出てくる構造、振る舞いがあるわけです。経験者であれば、こういう状況の際にはこの構造で作ればいいとか、この問題に対しては以前こういう解決策を適用してうまくいった、という点があるでしょう。しかし、初心者が設計を行う際には、何らかの問題に対処する際、いろいろな解決策を検討してそれを検証し、試行錯誤しながら解決策を適用していくことになります。初心者でもよく出てくる構造、振る舞いを知ることでソフトウェアの品質や開発効率を高めることができるのです。
デザインパターンは広義では設計のパターンということになりますが、狭義ではGoF(脚注A)の23のデザインパターンを指します。これらの分類とパターン名一覧を挙げておきましょう(表1)。それぞれのデザインパターンには、ある問題に対応するための考え方、クラス構造等が設計ノウハウとして示されています。
表1:GoFのデザインパターン | ||||||||
|
脚注A GoFとは、Gang of Four、邦訳「オブジェクト指向における再利用のためのデザインパターン」を 執筆した4人(Erich Gamma, Rechard Helm, Ralph Jonson, John Vlissides)の総称。
次に、デザインパターンを使うことでどういう効果があるのかをまとめておきましょう。何のために学ぶのか、これから学習するためのモチベーションを確認することにもなります。新しいことを学ぶ際には何でもそうですが、学習メリットが明確になることによって、学習のスピードや理解の深さも全然違ってくるはずですね。
では、デザインパターンを利用した効果ですが、端的に言うと、ソフトウェア設計で「より効率的に」品質のいい構造を作ることができるということです。先にも述べたように、経験者たちがこれまで蓄えてきたノウハウを使用することができるため、こなれた(枯れた)設計が可能になります。
品質にはさまざまなものがありますが、デザインパターンを用いる上で特に重要な効果としては「保守性」に関する品質要求に対応できることでしょう。ソフトウェアを構築する際に単に動けばいいということではなく、ソフトウェアのライフサイクル全体を考えた場合には、保守段階にかかるであろう潜在的なコストは非常に大きいものがあるはずです。保守性の中でもデザインパターンを適用することで、『変更容易性』、ソフトウェア構造の『理解容易性』に対するメリットが得られると言えます。例えば、ある一部分を変更する際、あるいは機能を追加する際にそれに対する変更の影響が少なくて済むということです。また、設計構造としてわかりやすさが得られる点や「○○パターンを使っている」ということがわかれば、設計の意図も共有でき、保守担当者の理解が容易になるということです。ビジネスの中でのソフトウェアの重要性はさらに高まっていますし、ソフトウェアの機能拡張、不具合修正などにかかる要求、コストは増えていくことでしょう。ソフトウェアは変更されることが宿命ですから、保守コストの軽減というのは非常に重要なメリットだと言えます。
さて、「変更容易性」、「理解容易性」を高めることができるデザインパターンですが、これはよりよいオブジェクト指向設計を実施していくことによって、このようなメリットが得られるものです。オブジェクト指向設計の基本指針は、クラス設計におけるクラスの責務分割を適切に行うということです。責務分割を行う際には、オブジェクト指向の基本概念を理解し、うまく適用できるかがポイントになります。
そんなことはわかっていると言われそうですが、デザインパターンは高尚なものではなく、クラス設計におけるクラスの分割、責務の割り当ての1つのノウハウであり、特に『継承』『コンポジション』『ポリモフィズム』といった、オブジェクト指向設計の基本といえるテクニックを使った設計の延長線上にあるものなのです。
つまり、「継承」、「コンポジション」、「ポリモフィズム」の機構を理解し正しく適用できるようにすること、またクラスの責務分割の考え方を理解することができれば、ほとんどのデザインパターンの構造がすーっと頭の中に入っていき、理解できやすくなると言えるのです。
では、デザインパターンを習得する際にこれら必須の学習項目を見てみましょう。
継承は、クラス間に親子関係を結び、親クラスの属性や操作などの特徴を継承することができる仕組みです。これは「法人顧客」は「顧客」であるというような、is-aの関係が成り立つ際に使用する必要があります。単純な継承の例は非常にわかりやすいものです。ここ最近よく目にしますが、継承はクラスのカプセル化を壊すものだとか、極端に「継承は使うべきではない」というような論調も存在するようです。継承によってプログラムの複雑さが増してしまうことには注意が必要ですが、継承という機構自体は非常に強力なメリットを持つものです。継承を使うことによって、簡単に既存の実装の再利用ができたり、実装の重複をなくしたりできるわけですね。デザインパターンでもTemplate MethodやFactory Methodのような比較的よく使用されるデザインパターンでも継承を用いている例もあり、適切に使用すれば継承を使うことはプラスの効果をもたらします。ただ、再利用したいクラスの機能を単に拡張したいという理由でサブクラス化(is-aの関係が崩れているようなサブクラス化)してしまったり、あまりにも深い継承階層になる際には注意が必要です。継承も設計テクニックの一つとして使い分けることを考えましょう。
コンポジションは、他のオブジェクトを取り込み、複合的なオブジェクトを構築します。何らかの処理をする際に複合的なオブジェクトによって処理がなされます。コンポジションは、クラスとクラスの関係がhas-aの場合に使用されます。これは、デザインパターンで多く使われており、この特徴を押さえることもデザインパターンを理解する上で非常に重要なことです。これは、図1のようにEmployeeクラスはRoleクラスを「保持する」という意味合いになり、EmployeeクラスのフィールドにRoleクラスのオブジェクトを持つ形になります。継承の場合にはスーパークラスの持つ属性、操作はサブクラスにすべて引き継がれる形になり、一方の変更が他方に影響を及ぼしてしまうような密な関係になりがちですが、コンポジションの場合にはそうはなりません。EmployeeとRoleが別オブジェクトとして存在するために変更の影響は受けにくいと言えます。また、Roleにあたる役割クラスが複数出てくるような場合にはRoleクラスの部分で継承階層を定義し、ProgrammerクラスやDesignerクラスのようなクラスを定義します(図2)。これによって、役職クラスの変更や追加への対応に柔軟性が増し、より品質のいいクラス構造を作ることができるわけです。これは、この後説明する「ポリモフィズム」のメリットも含まれています。
図 1 :コンポジション(EmployeeクラスはRoleクラスを持つ) |
図 2 : Roleクラスのサブクラスとして ProgrammerクラスとDesignerクラスを定義 |
ポリモフィズムは、オブジェクトに対して同じメッセージ呼び出しをした場合に実行されるメソッドが動的に決定され、異なる振る舞いをさせることができる特徴を言います。動的に決定されるというのはどういうことでしょうか。実行時まで処理の内容がわからないということです。呼び出すメソッドの仕様部分と実装部分がコンパイル時に決まるのではなく、実行時に決まる特徴です。この結合を遅らせることは非常に融通性の高い構造を作ることができます。これによってクラス構造の変更にも対応できやすい構造となり得ます。
図3のようにEmployeeクラスに給与を計算するようなgetSalary()メソッドがあり、役職(Role)によって給与の計算ロジックや係数が異なっているような場合、Employeeクラスの定義は、Roleオブジェクトに処理を委譲する形になります。実行時にProgrammer、Designerのインスタンスを生成し、Employeeからは実行時に設定されたオブジェクトのメソッドが呼ばれます。
図 3 : Employeeクラスは自分が保持するRoleオブジェクトに対して呼び出しを行う |
これは呼び出す側は、以下のような同じ呼び出し形式で呼び出されるオブジェクトを変化させることができる部分がポイントです。Employeeクラスは、サブクラスを知る必要がなくRoleクラスを知っているだけになるわけですね。
int getSalary(){ return role.getSalary(); } |
オブジェクト指向ではオブジェクト同士がメッセージをやり取りしながら処理を進めていくわけですが、メッセージをやり取りするクラスの定義を固定化させながら、実際の動作を変化させる柔軟性を持たせることができます。
実際にどのサブクラスを生成するかを実行時に決める必要がありますが、ポリモフィズムを考える際のポイントは、その生成する仕組みとメッセージをやり取りする際のクラスの構造とは別に考えることです。生成(new)する際の仕組みは別問題とし、メッセージをやり取りするアプリケーションのクラス構造を柔軟にしておくことを考えるとメリットがわかりやすいでしょう。どの部分の構造を柔軟にしたいのかということを意識しておく必要があるわけです。
ここまでの内容を整理すると、図4のようにまとめられます。
図 4 : デザインパターン適用のイメージ
|
今度は、デザインパターンを理解していくポイントとしてクラス構造に焦点をあてて見ていきましょう。
まず各デザインパターンの目的やクラス構造を見ていくと、大まかには何を解決したいのかについてわかってくると思います。ただ、なぜこのクラスが必要なのか?とか、なぜこんな面倒なことをしているんだろう?という点も出てくるでしょう。デザインパターンは責務分割のノウハウであることは説明しましたが、オブジェクト指向の初心者の方には、まずは大雑把ですが、「こういう感じ(目的や粒度)でクラスの分割をするんだ」というイメージを持つことをお勧めします。なかなかクラスの粒度がしっくり来ないという方が多いかもしれませんが、そういうもんだと割り切って、その後、一つ一つのクラスの責務を確認していきましょう。一足飛びに出来上がった構造を見てすぐには理解できないので、まずはオブジェクト指向設計のクラスの粒度の例を確認するというくらいの意味合いです。
各デザインパターンには、先に説明した「継承」「コンポジション」「ポリモフィズム」の構造が出てきます。これらはクラスの責務分割をする際の実現テクニックであり、各クラスを見ると一貫した責務で定義がされているわけです。各デザインパターンを見ていくと、クラスの責務を割り当てる指針が得られます。
次に、たくさんのデザインパターンを見ていくと、似たような構造をしていることに気づくはずです。それぞれ見ていくと、「何の問題を解決するか」というのは各デザインパターンで異なりますが、「どのようにして問題を解決するか」という問題解決のための着眼点(言い換えると、形作られているクラス構造で共通する考え方)というのが見えてきます。これを押さえることで、各デザインパターンのクラス構造が理解できやすくなり、それぞれ出てくるクラスは何のためにあるのかというのがわかるようになります。
その着眼点と言うのは、以下のような点です。
どのように問題を解決するかは問題の捉え方や視点によって変わるので、各デザインパターンがどれに当てはまるか一概には言えませんが、多くのデザインパターンはこのような着眼点を持ち、問題を解決しています。少し例を見てみましょう。
1つ目は「変動要素の分離」です。これは、アプリケーション内で固定化している部分と変動する部分を分離するということです。変動要素を分離し、クラスとして分割することで、変更に対する柔軟性がアップします。例を挙げるとすると、Stateパターン、Strategyパターンなどがあります(図5)。Stateパターンは「状態」をクラスとして、Strategyパターンは「アルゴリズム」をクラスとして定義しますが、それぞれが変動要素として外出しにされ、何らかの変更が発生した場合にも変動する部分だけを修正し、他の部分には影響を及ぼさないようにすることができます。
図 5: 変動要素の分離 |
2つ目は「間接層の導入」です。これは、クラスとクラスの結合度を弱めることで一方のクラスの変更の影響を他方に与えない、またはクラスの再利用性を高めることにもなります。モジュール同士(ここではクラス)の関連性を弱めることはソフトウェア設計の定石です。これも例を挙げると非常に多くのデザインパターンが当てはまるのですが、Proxyパターン、Adaptorパターンを挙げておきます(図6)。間接層を導入することで本来使用したい機能を持っているクラス(例えば、分析で出てくるドメインクラス(脚注B))とそれを使用するクラスを直接結合させず、モジュールの結合を弱めているわけです。
図 6: 間接層の導入 |
3つ目は「複雑さの軽減」です。これは、それぞれのクラスの複雑さを軽減しよう、というものです。クラスの責務は単一にするべきで異なる責務を同じクラスに入れ込まないようにするものです。モジュール(クラス)の凝集度を高めることもソフトウェア設計では定石です。オブジェクト指向の原則では、「単一責任の原則」(脚注C)とも言われます。これも多くのデザインパターンで当てはまる指針ですが、ここではDecoratorパターン、Visitorパターンを挙げておきます(図7)。1つのクラスに対して何らかの機能を追加したいといった場合には、そのクラスに機能を追加する方法や継承を使用しサブクラスでその機能を追加するといった方法が考えられます。両方のデザインパターンともコンポジションを使い、別のクラス階層を追加することでクラスの責務を単一にしつつ、機能を追加する形になっています。
図 7: 複雑さの軽減 |
この3つの着眼点を頭に入れながら各デザインパターンを見ることで、特徴が把握できやすくなるでしょう。
脚注B システム化する業務において管理すべき情報を表現したクラス。例えば、販売管理システムでは「注文」や「商品」のようなクラスが出てくるでしょう。デザインパターンのクラス構造でどれがドメインクラスなのかを認識することも理解の助けになるでしょう。
脚注C 単一責任の原則=「クラスを変更する理由は一つ以上存在してはならない」。「責任」=「変更理由」として定義されています。ただ、単一責任というのは、何が単一の責任なのかというのは難しいもので、作りたいソフトウェアの目的によって変わるものです。デザインパターンはクラスの責任の粒度に対して指針を与えてくれます。
ここからは、ケーススタディにデザインパターンを適用していきます。Javaプログラミングの経験はあるものの、オブジェクト指向は初心者であるAくんがデザインパターンを適用したアプリケーションを作成していきます。今回は、ケーススタディの導入部をお送りします。
今回のケーススタディは、IT研修の企画、実施、運営をしている企業を取り上げます。この会社は、研修を実施した後、受講者にアンケートを記入してもらっています。アンケートの内容を入力し管理するアプリケーションを企画部で持っており、その情報をマーケティングに活かしています。現在の機能としては、受講者/アンケートの登録機能、登録データの照会、削除機能などがあります。
そのアプリケーションは2年前にシステム部で作ったものです。その後、機能追加などの対応をしてきましたが、その対応コストがばかにならなくなってきました。システム部は変更のコストがかかるため対応に躊躇し、企画部の方では機能追加をしたいという要望があり、両部はあまりうまくいってないようです。そこで企画部長とシステム部長で話し合いを持ったようです。
その数日後、システム部長のX部長はAくんのところにやって来ました。
X部長「これまで片手間に対応していたアンケート集計のシステムだが、システム部で正式にメンテナンス対応をすることになった。企画部の方では今後もタイムリーな情報活用のための機能拡張をしたいと思っているようだ。現状のシステムは主にAくんがメンテナンスしていたんだね?」
Aくん「はい。対応していましたが、ロジックがかなり複雑で。ドキュメントもないですし、あまり触りたくないというのが正直なところなんです。」
X部長「そのようだね。ここ数ヶ月のプロジェクト実績コストを見るとかなり多いようだし。先に少しCくんとも相談したんだが、今後のメンテナンス性も考えてオブジェクト指向で再構築してはどうかという話も出ていたんだ。幸いシステムのリリースも先でいいようだから、学習も兼ねてAくんが中心になって対応してもらえないか?」
Aくん「興味はあるんですが、、、実際のオブジェクト指向開発の経験がないので、、。」
X部長「では、Cくんにアドバイザーとして関わってもらおう。Cくんはオブジェクト指向を導入したプロジェクトを多く経験しているし、最近のプロジェクトではアーキテクトとしてフレームワークの構築を担当していたんだ。」
Aくんは、今回のセミナー受講管理システムの再構築の件を引き受けることになりました。その後、Aくんは、オブジェクト指向の師匠となるCさんと会っています。
Cさん「Aくんはオブジェクト指向の概念については独学で勉強しているようだね。」
Aくん「はい。最近オブジェクト指向設計のセミナーを受講したのと、自分で本で勉強したことはあるので、基本はわかっているつもりなんですが。カプセル化とか継承とかの概念については。」
Cさん「では、デザインパターンを聞いたことがあるかい?」
Aくん「えーっと、聞いたことはあります。最近も何か見たような気がします。、、あ、Javaの本をAmazonで探したときに、売れてる上位に名前があったような。。」
Cさん「そうだね。Javaのデザインパターンの本は最近は結構出てきているね。それだけ重要でかつ使いこなせると重宝するものなんだ。Aくんは新しいことを学ぶ意欲がありそうだから、この機会にデザインパターンも学習してみようか。」
その後、CさんはAくんに大まかなデザインパターンの説明をしました。しかし、Aくんはまだピンときてないようですが、新しいことを学べるようでワクワクしているようです。
次回からは、このケーススタディでデザインパターンを適用した設計を行い、出てきたデザインパターンの解説も行っていきます。現状のソースコードとの比較もしながら、デザインパターンを使った設計のメリットや考慮点を確認していきます。Aくんはすんなり理解できるでしょうか?皆さんもAくんと一緒にデザインパターンを学習してみてください。
今回はデザインパターンの全体像や活用の効果、学習する際のポイントを中心に見ていきました。今回の内容をまとめておきます。
© 2007 OGIS-RI Co., Ltd. |
|