オブジェクト指向設計実践ガイドを読む No.4【柔軟なインターフェースをつくる】その 1
こんばんは。 sasumasa です。
今回はオブジェクト指向設計実践ガイドの第 4 章の「柔軟なインターフェースをつくる」について、僕の経験も踏まえてまとめていきたいと思います。
なお、本記事は他のクラスとの依存関係についてまとめた「オブジェクト指向設計実践ガイドを読む No.3」の続きとなっています。
ここまでのおさらい
本テーマの記事も 4 つ目ということで、軽くここまでのおさらいをしておきます。
なぜオブジェクト指向設計をするのか、その目的と実践について No.1 でまとめました。
次に、いい設計のためにはクラスの責務を単一化するべき、という話を No.2 でしました。
そして前回は他のクラスとの依存関係をどのように管理するかという話をしました。
変更に強いプログラムを書くために単一責任のクラスを実装する。ユースケースは複数のクラスが協業して達成されるものなので依存は避けられませんが、それがより管理しやすく、柔軟なものになるために依存関係を管理するという流れだったと思います。
ここから、話題はクラスからメッセージ(メソッド)に移っていきます。
メッセージを中心に設計する
誰でも最初はそうだと思いますが、あるユースケースを達成するためにプログラムを書くときにはクラスから考えるのが多いのではないでしょうか?
クラスはオブジェクト指向設計で重要な要素であり、プログラムはクラスとそのメソッドから構成されます。
しかし設計においては「どんなクラスがあるのか」だけでなく「どんなメッセージがやり取りされるか」が大切です。
個人的にはこのメッセージを中心に設計を考える方が大切だと思っています。
本書でも触れられているメッセージ(以下メソッドと呼びます)を設計で意識すべき理由について以下にまとめてみました。
- 外部に晒すメソッドの管理をすることはクラス同士の依存関係の管理につながるから
- クラス内のメソッドの責務を管理することは柔軟なメソッドを作り出すだけでなく、クラスの責務を明確に表すから
- メソッド同士のやりとりに注目することで、必要なクラスを導き出せるから
この 3 つについて触れていきたいと思います。
パブリックメソッドとプライベートメソッド
Ruby などのオブジェクト指向プログラミングを学んでいる人には、メソッドには大きく分けてパブリックなものとプライベートなものがあることを知っていると思います。
機能的に言えば、メソッドがパブリックかプライベートかによって変わることはクラス外から呼び出すことができるかどうかぐらいのはずです。
この事実は設計の面から考えると重要な意味を持ちます。メソッドがパブリックということは、そのメソッドがクラスの責務を表すということだからです。
僕はプログラミングでソフトウェアを作れます。僕がソフトウェアを作れることで、他の人は「sasumasa はソフトウェアを作ることができるんだ」と認識し、僕にお仕事の依頼だったりアプリの企画だったりを持ってきてくれます。
クラスもそれと同じで、そのクラスがやれること(パブリックメソッド)を知ることで、そのクラスとの仕事の仕方が決まるのです。
また、メソッドにも変わりやすいもの(実装の詳細に関わり、変わることが十分あり得るもの)と変わりにくいもの(気まぐれに変更されないもの)があります。前回まとめたクラスにおける単一責任の原則のノウハウをメソッドに当てはめると、変わりやすいものよりも変わりにくいものに依存する方がリスクは小さいはずです。
この変更のリスクという点においてもパブリックメソッドとプライベートメソッドを分けるメリットがあります。変わりにくいパブリックメソッドのみを外部に晒すことで、このメソッドの中の処理の変更や拡張に柔軟に対応できるのです。
例:レストランの厨房
メソッドがパブリックかプライベートかの違いについて「レストランの厨房」というわかりやすい例があるので紹介しておきます。
レストランには、メニューとそのメニューを作る厨房があります。お客さんは具体的な作り方を知らないままメニューを見て頼み、具体的な作り方は厨房のコックさんのみが知っています。
この「メニュー」こそがパブリックなメソッドで外部から呼び出されるものです。雑に書くとこんな感じでしょうか。
class Kitchen
def steak
# コックさんがどうにかして作る
end
end
# お客さんはどこかで来店(customer = Customer.new)している
kitchen = Kitchen.new
customer.eat(kitchen.steak) # お客さんはどうやってステーキがつくられるのかを知らない
そして、具体的な調理方法は厨房だけが知っていればいいので、プライベートなメソッドであるべきです。
class Kitchen
attr_reader :refrigerator, :supplier
def initialize(refrigerator, supplier = "sasumasa meat supplier")
@refrigerator = refrigerator
@supplier = supplier
end
def steak
sliced_meat = sliced(meat)
rare_meat = bake(sliced_meat, 10)
served(rare_meat)
end
private
def meat
refrigerator ? refrigerator.meat : supplier.meat
end
def bake(ingredient, seconds)
# フライパンを用意して火をつけて...などの具体的な処理
end
def served(food)
# いい感じに盛り付ける方法が書いてある
return served_food
end
end
# お客さんはどこかで来店(customer = Customer.new)している
kitchen = Kitchen.new
customer.eat(kitchen.steak) # お客さんはどうやってステーキがつくられるのかを知らない
上の steak メソッドは、ステーキが調理され提供されることを責務としていますが、その具体的な方法についてはプライベートメソッド(厨房)に任せています。
このクラスのパブリックメソッドが知っているのはステーキが調理されて提供されるために肉が切られ、焼かれて、盛り付けられるということだけです。厨房とお客さんの間をとりもつところですから(ちょっとセンスがいいとは言えない例えですが)ホールスタッフみたいなものでしょうか。
そしてこのパブリックメソッドを呼び出すお客さんはホールスタッフの知る大まかな調理法さえも知る必要はありません。メニューを見てステーキが食べたいと思ったら頼むだけです。
最後に、それぞれの関心があることとやっていること、プログラムの処理を簡単にまとめておきます。
関心があること | やってること | プログラムの処理 | |
---|---|---|---|
お客さん | ステーキが食べられること | 注文してステーキを食べる | パブリックメソッドの呼び出しと戻り値の受け取り |
ホールスタッフ | ステーキを作ってもらうこと | ステーキがどんなものか大体知っていて、厨房にお願いする | パブリックメソッド(プライベートメソッドの呼び出し) |
厨房 | ステーキをどうやって作るか | 具体的にステーキを作る工程を実行する | プライベートメソッド(各工程の実行) |
第 4 章は内容が多そうなので回数を分けることにしました。今回はここまでです。
Discussion