デザパタ_0 どうしてデザインパターンを学ぶの?


初めに

この記事シリーズは、筆者(Java学習歴:2年ほど、Java実務経験:少し)が
「GoFデザインパターン23」を理解するために作成しました。
精確さよりイメージ重視で進めるつもりで、Javaプログラミング中級者未満の方を対象にした内容になるかと思います。

至らないところがあれば、ぜひコメントください<(_ _)>

主な学習ソース:
・「増補改訂版Java言語で学ぶデザインパターン入門(結城 浩 著)」
デザインパターン | TECHSCORE(テックスコア)


デザインパターンって?

プログラムを良質にするための設計パターン。
こういう状況では、こう設計するといいよ、後々助かるよ、といった先人の知恵の集合体です。

この記事で扱うのは、「GoFデザインパターン」として知られる23個のパターンです。
GoF」と呼ばれる著名なソフトウェア技術者グループが提唱したもので、デザインパターンの中でもとりわけ有名です。
日本語版書籍:『オブジェクト指向における再利用のためのデザインパターン』
(すみません読んでません!)

どうしてデザインパターンを学ぶの?

目的は色々ありますが。。
GoFデザインパターンの学習で、以下のような利点が期待できます。

プログラムの保守性・再利用性を上げる設計手法が学べる

・デザインパターンは、プログラムの構成要素を機能ごとにまとめたり、役割を切り分けて一般的な部品作るのに役立ちます。

パターンを利用しているプログラムの理解がはかどる。

・JavaのCollection(ArrayListなど)はIteratorパターンを利用しています。
 また、インスタンス生成にFactoryパターンを使うものも多くあります。
・デザインパターンを利用した設計は、一見しただけでは意図が理解しにくいものもあります。
 自分がパターンを知らない場合、プログラムを見ても理解できず、複雑で質の悪い設計だ、と誤った解釈する可能性があります。

オブジェクト指向の有効な使い方が学べる。

・Javaに限らず、オブジェクト指向のプログラミング言語は多いです。
 が、しかし、「オブジェクト指向」の上手な使い方を知るのは結構大変です。。
 (あまり直感的に理解しやすい概念ではない、と思います)
 各種パターンの学習が、オブジェクト指向習得の手助けになると思います。

ソフトウェア開発者の共通知識として、コミュニケーションに役立つ。

・GoFは有名なデザインパターンで、知っているエンジニアも多いです。
 そのため、『この設計は「XXXパターン」です』といえば、それで説明十分になることもあるでしょう。

 ※GoFデザインパターンについて、現在では古くなり洗練されていない部分も多い、という意見もあるようです。
 しかし、コピペ的な利用をするのでなければ、上記の利点は十分得られると考え、GoFの学習を決めました。

これからデザインパターンを学習する方は、パターンをうのみにするのではなく、
パターン毎に「目的」があり、「その目的のためにGoFではこう設計している」
という点を意識して貰えたら応用力がつくかと思います。


追加説明:

おまけです。読み飛ばし可です。

保守性って?

プログラムに追加・修正を加えるときのやりやすさ。
コードの可読性や、問題発生時の解析のために適切なログを取っている、なども含む。
プログラム設計上は、機能追加や仕様変更等の事態に対応できる「柔軟性」を示します。

・プログラム内の機能・役割ごとに部品がまとまっていると、プログラムの見通しが良くなり保守性が上がる。
・各部品を修正しても、周りに影響が出にくい作りだと保守性は上がる。

再利用性って何?

プログラムやその部品の使いまわしのしやすさ。
ある部品をプログラム内の複数の場所で使える、とか、多少の修正で別のプログラムにも使いまわせる、など。
同じコードを二度書く手間が減るほか、
一度厳しいテストフェーズを抜けた部品は、品質が保証されているはずなので、
品質保証済みの部品をそのまま再利用できればテストの工数も減らせる。

・特殊な条件に依存していない「一般的な設計」の部品は再利用性が高い
・言語公式のAPI群などは、非常に再利用性が高いと言える。

まとめると:

保守性・再利用性の高い、「良い」プログラムは、
・機能・役割ごとに「整理整頓」されていて、修正・機能追加がしやすい。
・プログラム内の特殊な事情とできるだけ切り離された「一般的な作り」の部品が多い。

とはいえ、プログラムは実際の具体的な問題を解決するためのものなので、
どこかに「具体的な」処理や値などを書かなければいけません。

その辺りの事情も踏まえ、GoFデザインパターンでは、以下のような設計を行うことが多いです。

部品を機能・役割ごとに、できるだけ細かくする

・細かく分けることで、各部品の担当する仕事が明瞭になる。
・部品の取り換え・修正時の影響範囲が小さくなる。

部品間の関係性と、具体的な処理の記述は分ける

・部品間の関係性は、インターフェイスや抽象クラスを使い決めておく。
・具体的な処理は普通のクラス(具象クラス)に任せる。
 こうすると、具象クラスは取り換え容易になる。

具体的な処理や値の記述を、特定の箇所にまとめる

・どの具象クラスを new () するか、といった具体的な記述はあちこちに書かず、
 例えばmain関数のような専門家に管理させる。
 そうすると、main()だけの修正で、様々な具象クラスを切り替えできる。

部品間の依存関係を整理する

・部品間の依存関係を少なくする、または、特定の箇所に集めることで、
 部品修正時の影響範囲を小さくできる。

部品に適切な隠蔽/カプセル化を行う

・必要な部分だけ利用側に見せることで、部品の独立性を保つ
・複数の部品を組み合わせて大きな一つの部品にすることもできる。
 その場合は、窓口用の部品以外は隠蔽する。


依存って?

プログラムの部品が他の部品と、密接に関係していること。

プログラムを修正したいときに良く起きる問題として、
ある部品「A」を修正すると、他の部品「B」も修正が必要になる(可能性がある)ことがあります。
このとき、「B」は「A」に「依存している」と言います。

Javaで言うと、クラス「B」の記述の中にクラス「A」の名前が出てきたとき、
「B」は「A」に依存しています。
例えば、「A」の名前を「NewA」に変更すると、「B」の中に出てくるすべての「A」を「NewA」に変更する必要が生まれます。そうしないとコンパイラに怒られます。

実際に名前だけ変えることは少ないでしょうが、クラス「B」の中でわざわざクラス「A」を記述している以上、
「B」は何らかの形で「A」を利用していることになります。(インスタンスを利用していたり、継承していたり、状況は色々ありえます)
この状態で「A」内の記述を書き換えると、「A」を利用している「B」の機能にも何らかの影響が及ぶ可能性が生まれます。

逆に、クラス「B」にいくら修正を加えてもクラス「A」側には影響がありません。
(「A」が「B」を利用していない場合)
クラス「A」のコード内からは、そもそも「B」が存在することかどうかも確認できないでしょう。そのため、クラス「A」はクラス「B」に依存していないといえます。

部品間の依存が複雑になると、部品の独立性が失われ、修正時の手間も増えていきます。
そのため、依存はなるべく小さく、コントロールしやすくなるように設計する必要があります。

※本稿で取り上げた、クラスの関係上で見て取れる依存性のほかにも、
実際の設計では、潜在的な依存性 ≒ 広い意味での依存性についても考慮が必要なようです。
以下の記事が勉強になりました。
プログラムの依存関係とモジュール構成のこと @wm3さん

隠蔽/カプセル化って?

プログラム部品の特定の部分を、外部から見せない、触らせないようにすることを「隠蔽」と言います。
適切な部分を「隠蔽」することを「カプセル化」と言います。

例えば、ここにストーブがあったとします。
ストーブの上部には電源ON/OFFボタンがあり、それを押せば暖かい風が出ます。
機械内部はカバーで覆われ、内部で動いているヒーターやファンなどの部品は見えないし、触れません。
これが「カプセル化」されている状態、利用者にとって不要な部分が「隠蔽」されている状態です。

もし、ストーブのカバーが外れており、機械部品が丸見えだったらどうなるでしょうか。
電源ボタンは一目で見つからず、ヒーター・ファン・ネジその他の内部で使う部品が目に入ります。どこを操作すれば動くのか、とても分かりにくくなります。
うっかり重要な部品に触れてしまい、ストーブが壊れてしまうことも考えられます。
これが「隠蔽」がされていない状態です。

このように、適切な隠蔽を行うと、2つの利点があります。
・利用者側で、部品利用のための窓口(電源ボタンなど)のみ確認すればよく、扱いやすくなる。
・外部からの期待しない変更から、部品の内部要素を守る

Javaでは主に、private, public 等のアクセス制御を使って隠蔽を実現します。