最近、オブジェクト指向における「委譲」の適切な使い方について考える機会があったのでマーティン・ファウラーの『リファクタリング』を読みながら勉強してみます。サンプルコードはRubyです。
www.ohmsha.co.jp
サンプルコード
以下のようにNameクラスとPersonクラスがあるとします。PersonクラスはNameクラスに依存しています。
※ PersonクラスのコンストラクタにNameクラスのインスタンスを渡す方法もあり、そこでも議論ができそうですが本記事では割愛します。
Nameクラス
class Name
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def full_name
"#{@last_name} #{@first_name}"
end
end
Personクラス
class Person
def initialize(first_name, last_name)
@name = Name.new(first_name, last_name)
end
end
PersonクラスのインスタンスからNameクラスのfull_nameメソッドを呼ぶ方法を考えます。以下の2パターンがありそうです。
1.Personクラスで委譲メソッドを提供する
1つ目は以下のようにPersonクラスのインスタンスから直接full_nameメソッドを呼ぶ方法です。
person = Person.new("太郎", "山田")
puts person.full_name
このためにはPersonクラスでName#full_nameメソッドに委譲するメソッドを提供する必要があります。
class Person
def full_name
@name.full_name
end
end
2.nameフィールド経由で呼ぶ
2つ目は以下のようにPersonのnameフィールド(=Nameクラスのインスタンス)を経由してfull_nameメソッドを呼ぶ方法です。
person = Person.new("太郎", "山田")
puts person.name.full_name
そのためにはPersonクラスにattr_readerを定義してnameフィールドを公開する必要があります。
class Person
attr_reader :name
end
メリット/デメリット
それぞれのパターンのメリット/デメリットについて考えます。マーティン・ファウラーの『リファクタリング』に詳しく解説されています。
- 委譲の隠蔽(p.196)
- 仲介人の除去(p.199)
です。1.2.は対のパターンとして紹介されています。
1.委譲の隠蔽
「1.委譲の隠蔽」は「1.Personクラスで委譲メソッドを提供する」に該当します。
メリットは、クライアント側が委譲先のオブジェクトのことを知らなくてよいことです。
今回の例では、full_nameメソッドを呼び出したい側はNameクラスの存在を意識せずに呼ぶことができます。
『リファクタリング』では「(部下が)上司に対して会議に出席できるか尋ねると、手帳をみてから答えます(p.84)」というとても分かりやすい例で説明されています。
sequenceDiagram
autonumber
participant Subordinate as 部下
participant Boss as 上司
participant Calendar as 手帳(スケジュール)
Subordinate->>Boss: 出席できますか?(会議日時)
Boss->>Calendar: 空き確認する(会議日時)
Calendar-->>Boss: 可否(OK/NG)
Boss-->>Subordinate: 出席可否を回答(OK/NG)
この例では「上司」は「手帳」への委譲を隠蔽しているといえます。従って、「部下」は上司の手帳を見に行かなくても、上司に聞くだけでほしい回答を得ることができます。
このパターンは「直接の隣人にのみ話しかける」という「デメテルの法則」とも相性がいいです。
ja.wikipedia.org
デメリットはこのパターンを多用すると委譲するだけのメソッドが増えていくことです。例えば、Nameクラスにfull_name_kanaメソッドが追加されたら、Personクラスにもfull_name_kanaメソッドを追加する必要があります。
class Person
def full_name
@name.full_name
end
def full_name_kana
@name.full_name_kana
end
end
このように委譲を過剰に使用した結果、委譲先のオブジェクトに転送するだけのメソッドが多くを占めるようになったクラスをファウラーは「ただの仲介人」と呼んで批判しています。
加えて開発メンバーに「デメテルの法則」が好きな人が多いと、このアンチパターンに陥りやすいと述べられています。
2.仲介人の除去
「1.委譲の隠蔽」は「2.nameフィールド経由で呼ぶ」に該当します。これは委譲を過剰に使用したコードへのリファクタリング手法として紹介されています。
やることは単純で、委譲をやめて委譲先のオブジェクトのメソッドを直接呼ぶことです。
メリット/デメリットは1.の逆で、委譲先のオブジェクトがどのようなメソッドを提供しているかという知識は必要になりますが、「ただの仲介人」を削除することができます。
まとめ
「1.委譲の隠蔽」と「2.仲介人の除去」はそれぞれメリット/デメリットがあるため、混在してもよいとファウラーは主張しています。目安として、よく使われる委譲であれば「1.委譲の隠蔽」を用いてよいとのことです。
加えて、委譲をどの程度隠蔽すべきかはシステムの変化によっても変わるので、都度リファクタリングすることが重要とのことです。