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の適応を考えます。

今号のWEB+DB PRESSはパターン特集

WEB+DB PRESS Vol.40

WEB+DB PRESS Vol.40

読むべし。

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