Forwardable 모듈
Forwardable 모듈 (Forwardable)
Forwardable 모듈은 def_delegator와 def_delegators 메서드를 이용해서, 지정된 메서드들을 특정 객체로 위임(delegation)해 주는 기능을 제공해요.
예를 들어 @records 배열을 담고 있는 RecordCollection 클래스가 있다고 해볼게요. 배열의 []를 그냥 호출하는 조회 메서드 record_number()를 이렇게 만들 수 있어요.
require 'forwardable'
class RecordCollection
attr_accessor :records
extend Forwardable
def_delegator :@records, :[], :record_number
end
조회 메서드는 이렇게 사용해요.
r = RecordCollection.new
r.records = [4,5,6]
r.record_number(0) # => 4
나아가 size, << 같은 메서드도 제공하고 싶다면 클래스를 다시 열어 위임을 추가할 수 있어요.
class RecordCollection # re-open RecordCollection class
def_delegators :@records, :size, :<<
end
r = RecordCollection.new
r.records = [1, 2, 3, 4]
r.size # => 4
r << 1 # => [1, 2, 3, 4, 1]
r.map { |x| x * 2 } # => [2, 4, 6, 8]
일반 객체에 Forwardable을 extend해서 위임을 준비할 수도 있어요.
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!"
출처: Ruby 3.3 API
본문
상속 대신 위임 (Delegate Instead of Inheritance)
슈퍼클래스의 모든 메서드를 상속받고 싶지 않다면, 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.rb는 def_delegator/def_delegators를 통한 단일 메서드 위임을 제공하고, DelegateClass를 통한 전체 클래스 위임은 delegate.rb를 사용해요.
상수와 속성 (Constants and Attributes)
FORWARDABLE_VERSION,VERSION—forwardable.rb의 버전debug—[RW]속성 (무시됨)
#def_instance_delegator (Public Instance Method)
def_instance_delegator(accessor, method, ali = method) — 메서드를 위임 인스턴스 메서드로 정의해요. 선택적으로 별칭 이름 ali를 줄 수 있어요. ali에 대한 호출은 accessor.method로 위임돼요. accessor는 메서드 이름, 인스턴스 변수 이름, 또는 상수 이름이 될 수 있어요(상수는 전체 경로). 정의된 메서드 이름을 돌려줘요. def_delegator의 별칭이에요.
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
# File lib/forwardable.rb, line 188
def def_instance_delegator(accessor, method, ali = method)
gen = Forwardable._delegator_method(self, accessor, method, ali)
# If it's not a class or module, it's an instance
mod = Module === self ? self : singleton_class
ret = mod.module_eval(&gen)
mod.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7'
ret
end
#def_instance_delegators (Public Instance Method)
def_instance_delegators(accessor, *methods) — 별칭 없이 여러 위임 메서드를 한 번에 정의하는 단축 메서드예요. 아래 두 코드는 같은 효과를 가져요(def_delegators의 별칭).
def_delegators :@records, :size, :<<
# File lib/forwardable.rb, line 156
def def_instance_delegators(accessor, *methods)
methods.each do |method|
next if /\A__(?:send|id)__\z/ =~ method
def_instance_delegator(accessor, method)
end
end
#instance_delegate / #delegate
delegate method → accessor, delegate [method, method, ...] → accessor — instance_delegate 메서드의 별칭이에요. forwardable의 def_delegator 대신 사용할 수 있어요.