Simple Delegation
呼び出し元に依存しない委譲を行いたい場合は、メッセージをそのまま委譲しましょう。
委譲先のオブジェクトが十分に独立していて、委譲元から付加的な情報を貰わなくても自身の仕事を行える場合は、Forwardableによりメッセージをそのまま委譲しましょう。
以下に、Collection Accessor Methodで示したコードに、コレクションを操作するためのいくつかの基本的な操作の委譲を追加した例を示します。
require 'forwardable' class Department extend Forwardable def initialize @employee = [] end def employs(employee) @employee.push(employee) end ... def_delegators :@employee, :each, :size, :to_a, :sort end general_affairs = Department.new general_affairs.employs("foo") general_affairs.employs("bar") general_affairs.size # => 2 general_affairs.each do |employee| puts employee end general_affairs.to_a # => ["foo", "bar"] general_affairs.sort # => ["bar", "foo"]
Forwardableモジュールをextendし、def_delegatorsを使うことで、@employeeへの処理の委譲を実現しています。このようにForwardableを利用することで、必要な操作のみ委譲することが出来ます。
Delegation
継承なしに実装を共有させたい場合は、仕事の一部を直接他のオブジェクトに任せましょう。
あるオブジェクトの機能を別のオブジェクトを通して利用したいけれど、継承はコストがかかりすぎる気がする、もしくは継承することが意味的に言って適切ではない、というような場合があります。そのような場合は、処理を委譲することを考えましょう。
具体的には、Simple DelegationやSelf Delegationの適応を考えます。
RubySapporoNight vol.2
9/5日(水)に、アップルストア札幌にてRubySapporoNight vol.2を行います。
開催概要
http://jp.rubyist.net/?SapporoWorkshop3
* 日時:9月5日(水) 19:00 〜 20:00
* 場所:アップルストア札幌
* 内容:Rails特集
* 費用:無料
札幌の方は仕事帰りにでもどうぞお立ち寄りください(・e・)/
今号のWEB+DB PRESSはパターン特集
Collection Accessor Method
インスタンス変数がコレクションを保持する場合、コレクションに処理を委譲するアクセサを用意しましょう。
インスタンス変数に関するパターンです。
インスタンス変数がコレクションを保持する場合、Getting Methodを使ってコレクション自体をクライアントに返してしまうと以下のようなリスクが発生します。
- オブジェクトがどのようにデータを管理しているかが外部に出てしまうため、クライアントが内部構造に依存するコードを書く危険があり、内部構造が柔軟に改善できなくなる可能性があります。
- オブジェクトの知らないところでコレクション自体、もしくはコレクションの中身を変更されてしまう可能性があります。オブジェクトが管理しなければならないのはコレクションの器ではなくコレクションの中身なのですが、それに対してオブジェクトは責任を保てません。
class Department def initialize @employee = ["foo", "bar", "baz"] end attr_accessor :employee end general_affairs = Department.new general_affairs.employee = "破壊された" # => "破壊された" general_affairs.employee # => "破壊された"
コレクションを直接クライアントに見せないようにし間接的な操作をいくつか用意することで、このようなリスクを回避することが出来ます。
class Department def initialize @employee = [] end def employs(employee) @employee.push(employee) end def dismisses(employee) @employee.delete(employee) end def employed?(employee) @employee.include?(employee) end end general_affairs = Department.new general_affairs.employed?("foo") # => false general_affairs.employs("foo") # => ["foo"] general_affairs.employed?("foo") # => true general_affairs.dismisses("foo") # => "foo" general_affairs.employed?("foo") # => false
Setting Method
インスタンス変数と同じ名前の変数の値を設定するメソッドを作りましょう。
インスタンス変数に関するパターンです。
Indirect Variable Accessで使用するアクセサのうち、設定を行うものをSetting Methodといいます。
class Point attr_accessor :x, :y def initialize(x, y) @x = x @y = y end end point = Point.new(10, 5) point.x = 5 # => 5
Setting Methodのみを実現したい場合は、attr_writerを使用します。
class Point attr_writer :x, :y def initialize(x, y) @x = x @y = y end end point = Point.new(10, 5) point.x = 5 # => 5
Getting Method
インスタンス変数と同じ名前の変数の値を返すメソッドを作りましょう。
インスタンス変数に関するパターンです。
Indirect Variable Accessで使用するアクセサのうち、参照を行うものをGetting Methodといいます。
class Point attr_accessor :x, :y def initialize(x, y) @x = x @y = y end end point = Point.new(10, 5) point.x # => 10
Getting Methodのみを実現したい場合は、attr_readerを使用します。
class Point attr_reader :x, :y def initialize(x, y) @x = x @y = y end end point = Point.new(10, 5) point.x # => 10