Forwardable 모듈

Forwardable 모듈 (Forwardable)

Forwardable 모듈은 def_delegatordef_delegators 메서드를 이용해, 지정된 메서드들을 특정 객체로 위임(delegate)하는 기능을 제공해요. 어떤 클래스가 내부에 배열 @records를 갖고 있는 상황을 생각해볼게요.

require 'forwardable'
class RecordCollection
  attr_accessor :records
  extend Forwardable
  def_delegator :@records, :[], :record_number
end

def_delegator는 첫 인자(여기서는 @records)로 받은 객체의 메서드를 호출하도록 클래스에 메서드를 만들어 줘요. 여기서는 record_number라는 이름으로 @records[](즉 서브스크립트)를 위임받은 거예요.

r = RecordCollection.new
r.records = [4,5,6]
r.record_number(0)  # => 4

size, <<, map처럼 여러 메서드를 한 번에 위임하고 싶다면 def_delegators를 쓰면 돼요.

class RecordCollection # re-open RecordCollection class
  def_delegators :@records, :size, :<<, :map
end
r = RecordCollection.new
r.records = [1,2,3]
r.record_number(0)   # => 1
r.size               # => 3
r << 4               # => [1, 2, 3, 4]
r.map { |x| x * 2 }  # => [2, 4, 6, 8]

일반 객체에도 Forwardable을 확장할 수 있어요.

my_hash = Hash.new
my_hash.extend Forwardable              # prepare object for delegation
my_hash.def_delegator "STDOUT", "puts"  # add delegation for STDOUT.puts()
my_hash.puts "Howdy!"

또 다른 예시 (Another example)

Forwardable은 상속의 대안으로 쓸 수 있어요. 슈퍼클래스의 모든 메서드를 물려받고 싶지 않을 때죠. 예를 들어 배열의 일부 인스턴스 메서드들을 새 클래스 Queue에 추가해 보겠어요.

class Queue
  extend Forwardable
  def initialize
    @q = [ ]    # prepare delegate object
  end
  # setup preferred interface, enq() and deq()...
  def_delegator :@q, :push, :enq
  def_delegator :@q, :shift, :deq
  # support some general Array methods that fit Queues well
  def_delegators :@q, :clear, :first, :push, :shift, :size
end
q = Thread::Queue.new
q.enq 1, 2, 3, 4, 5
q.push 6
q.shift    # => 1
while q.size > 0
  puts q.deq
end
q.enq "Ruby", "Perl", "Python"
puts q.first
q.clear
puts q.first

이 코드의 출력은 이렇게 돼요.

2
3
4
5
6
Ruby
nil

참고사항 (Notes)

RDoc은 위임된 메서드를 감지하지 못해요. 그리고 forwardable.rbdef_delegator/def_delegators를 통한 단일 메서드 위임을 제공하고, 클래스 전체를 위임하는 DelegateClass는 별도의 delegate.rb에서 다뤄요.

출처: Ruby 4.0 API

본문

상수와 속성 (Constants and Attributes)

  • VERSIONforwardable.rb의 버전.
  • FORWARDABLE_VERSION — 하위 호환성을 위한 버전.
  • debug [RW] — 무시되는 속성.

#def_instance_delegator (Public Instance Method)

def_instance_delegator(accessor, method, ali = method)method를 위임자(delegator) 인스턴스 메서드로 정의하되, 선택적으로 별칭 이름 ali를 줄 수 있어요. ali에 대한 메서드 호출은 accessor.method로 위임돼요. accessor는 메서드 이름, 인스턴스 변수 이름, 또는 상수 이름이어야 해요. 상수 이름을 줄 때는 전체 경로를 사용해야 하죠. 정의된 메서드의 이름을 돌려줘요.

class MyQueue
  CONST = 1
  extend Forwardable
  attr_reader :queue
  def initialize
    @queue = []
  end
  def_delegator :@queue, :push, :mypush
  def_delegator 'MyQueue::CONST', :to_i
end
q = MyQueue.new
q.mypush 42
q.queue    #=> [42]
q.push 23  #=> NoMethodError
q.to_i     #=> 1

이 메서드는 def_delegator의 별칭이에요.

#def_instance_delegators (Public Instance Method)

def_instance_delegators(accessor, *methods) — 여러 위임자 메서드를 한 번에 정의하는 지름길이에요. 단, 다른 이름을 쓰는 옵션은 없어요. 아래 두 코드 조각은 같은 효과를 가져요.

def_delegators :@records, :size, :<<, :map
def_delegator :@records, :size
def_delegator :@records, :<<
def_delegator :@records, :map

이 메서드는 def_delegators의 별칭이에요.

#instance_delegate (Public Instance Method)

instance_delegate(hash) — 메서드와 접근자 간의 매핑을 해시로 한 번에 정의해요. delegate는 이 메서드의 별칭이에요.

delegate method → accessor
delegate [method, method, ...] → accessor