Edited at

オブジェクト志向でのインターフェースについてのメモ

More than 1 year has passed since last update.


柔軟なインターフェースを作る

オブジェクト指向アプリケーションは、一見クラスの集まりに見えるが、クラスはテキストエディタ上で目にするものであり、ソースコードのリポジトリで確認するもの。

このレベルでとらえるべき設計の詳細があるのは確かだが、実際はクラスの集まりではない

オブジェクト指向アプリケーションは、「クラスから成り立つ」が、「メッセージ(メソッド)によって定義される」もの。

クラスは、ソースコードリポジトリに何が入るかを制御し、メッセージは、実際の動きや、アプリケーションを反映する。

したがって、設計ではオブジェクト間で受け渡されるメッセージについても考慮しなければならない。

オブジェクトが何を知っているか(オブジェクトの責任)

誰を知っているのか(オブジェクトの依存関係)

オブジェクト同士が互いに、どのように会話するのか

などなど。

この、「オブジェクト間の会話」は「インターフェース」を介して行われる。

アプリケーションが育ち、かつ変化していけるような柔軟なインターフェースの作成ができるような考え方を目指す。


インターフェースを理解する。

次の図のような二つのアプリを考えてみる。

それぞれ、オブジェクト間で受け渡されるメッセージから構成される。

image.png


左のアプリ

・メッセージに明らかなパターンはなく、全てのオブジェクトが任意のメッセージを任意のオブジェクトに送れるようになっている。

再利用が困難。それぞれが自身について外部にさらしすぎ、近隣のオブジェクトについても知りすぎている。この余計な知識により、オブジェクトは「今この瞬間にやっていること」しかできないよう、細かく、明示的に(そして破滅的に)調整されていく。一つとして単独で動作するオブジェクトはない。ある一つを再利用するにも全てが必要で、ひとつの変更には全ての変更を必ず伴う


右のアプリ

・メッセージには明確に定義されたパターンがある。オブジェクトは明確で適切に定義された方法でコミュニケーションをとっている。

着脱可能。コンポーネント(部品)のようなオブジェクトから構成されている。それぞれが互いに最小のことのみを明らかにしていて、互いについて知っていることも最小限

左のアプリが抱える設計上の問題は、必ずしも依存オブジェクトの注入や単一責任の失敗に起因しない。

これは、クラスが何を「する」かではなく、何を「明らかにする」かにある。

左のアプリは、クラスは全てを明らかにしている。どのクラスにあるどのメソッドであっても、他のオブジェクトから正当に実行可能。実際にはクラス内のメソッドは全て違っていて、一般的なもの、変わりやすいものなどが入り混じっている。それを全く考慮できていない。

右のアプリは、メッセージのパターンに、何らかの制約があることは明らか。どのメッセージがどのオブジェクトに渡せるのかについて、何らかの合意と約束がある。それぞれのオブジェクトは、他のオブジェクトが使えるようなメソッドを明確に定義している。

これらの(合意と約束が)晒されたメソッドについて構成されるのが、クラスの「パブリックインターフェース」というもの。

インターフェースには山ほど概念があるが、ここでの定義は「クラス内」にあるようなインターフェース。

クラスはメソッドを実装し、そのいくつかは他のオブジェクトから使われることが意図されている。それらの(他のオブジェクトから使われることを想定した)メソッドがそのクラスのパブリックインターフェースを構成する。

その他のインターフェースとして、「複数のクラスにまたがり、どの単一のクラスからも独立しているものが挙げられる

。」(Pythonでいうメタクラスなど?)

この場合の「インターフェース」は、「それ自身がインターフェースを定義するような」メッセージの集合を表す。

「複数のクラスで扱える一つのメソッド」などがそう。

この場合、このインターフェースによって仮想のクラスが定義されているようなもの。

つまり、「(インターフェースによって)要求されるメソッドを実装するクラス」はどんなクラスであれ、「そのインターフェース」のように振る舞える(仮想のクラスを継承しているような感じ?)


インターフェースを定義する

レストランの厨房で考えてみる。

お客さんがメニューから料理を注文し、厨房へ伝えられる。そしてそのうち、料理が出てくる。

この間、厨房では多くのことが行われているが、お客さんにその全てを公開することはしていない。

だが、何らかは明らかにしなければお客さんは注文することすらできない。なので厨房には、お客さんが使うことが期待される「パブリック」インターフェースがある。すなわち、それがメニュー。

厨房内では多くのことが起こり、メニュー以外の他のメッセージも数多く受け渡される。しかし、これらのメッセージは「プライベート」であり、お客さんからは見えない。

この違いが存在する理由は、単純に効率がいいから。

例えば、お客さんが料理の指示をするようになったとして、その材料がなかった場合、お客さんはその都度また料理法を学ばなくてはならない。

それを防ぐためにメニューを作り、「厨房がどのように料理を作るかは一切完治させず」に、「何を」望むかをお客さんに頼ませることができる。

クラスは、それぞれが厨房のようなもの。クラスは単一の責任を果たすために存在するが、メソッド自体はいくつも実装する。これらのメソッドはそのスケールや粒度、範囲において異なる。

このうちの幾らかは、そのクラスにとってメニューのような存在。それゆえ、それはパブリックであるべき。結果、他の内部実装の詳細に関わるものはプライベートなメソッドとなる。


パブリックインターフェース

クラスの主要な責任を明らかにする

外部から実行されることが想定される

気まぐれに変更されない

他者がそこに依存しても安全

テストで完全に文書化されている


プライベートインターフェース

クラス内のパブリック(他のオブジェクトが使うことのできる)以外のメソッドは全て、プライベートインターフェースに含まれる。それらは、以下の特性を備える。

実装の詳細に関わる

他のオブジェクトから送られてくることは想定してない

どんな理由でも変更され得る

他者(他のオブジェクト)がそこに依存するのは危険

テストでは言及さえされないこともある


責任、依存関係、そしてインターフェース

クラスは単一の目的を持つものだと考えてみると、クラスがすること(クラスのより具体的な責任)は、自ずとその目的を果たすものとなる。

これらの具体的な責任についての記述と、クラスのパブリックメソッドとの間には、対応が見れる。

実際、パブリックメソッドは、責任の説明として解釈_できるものであるべき。

パブリックメソッドは、クラスの責任を明確に述べる契約書なのである。

また、依存関係のルールである、「クラスは自身より変更の可能性が低いクラスにのみ依存するべき」から考えると、今やっていることは、どのクラスも全てパブリックな部分とプライベートな部分に分けて考えること。

この「変更の可能性が低いところに依存する」という考えは、クラス「内」のメソッド(プライベートメソッドがパブリックメソッドに依存)にも当てはまる。

クラスのうちパブリックな部分は、安定した部分でもある。

対して、プライベートな部分は、変化し得る部分

メソッドにパブリックやプライベートと印をつけることは、クラスの使用者に対し、どのメソッドには安全に依存できそうか、ということを伝えていることになる。

したがって自身が他のクラスのパブリックメソッドを使うときも、それらのメソッドが安定していると信頼していることになる。

よって、他のクラスのプライベートメソッドに依存することを決めるときには、本質的に不安定な何かに依存していることを理解し、それゆえ離れたところの関係ない変更にも影響を受けるリスクが増えることも知った上で行わなくてはならない。