Ruby - bound/unbound method, inherited, included, extended

Bound i unbound method

Ruby pozwala na “wyciągnięcie” pojedynczej metody z obiektu w postaci obiektu Method, który można później wywołać. Ruby dostarcza dwa rodzaje metod - Method oraz UnboundMethod. Podstawową różnicą między tymi dwoma jest to, że Method możemy wywołać, a UnboundMethod nie. Spowodowane jest to tym, iż obiekt UnboundMethod nie ma zadeklarowanego odbiorcy metody. Obiekt UnboundMethod można oczywiście “przypiąć” do odpowiedniego obiektu, ale o tym za chwilę.

Na początek prosty przykład.

class Timmy
  attr_accessor :age

  def say
    "Timmy!"
  end

  def move(x)
    "Moved #{x} meters"
  end
end

timmy = Timmy.new

say_method = timmy.method(:say) # => #<Method: Timmy#say>
move_method = timmy.method(:move) # => #<Method: Timmy#move>

Metoda method(sym) jest zdefiniowana w klasie Object i dostępna we wszystkich obiektach. Zwraca obiekt Method z metodą o podanej nazwie. Metoda ma zadeklarowanego odbiorcę, tak więc można ją wykonać. Metody wykonuje się tak samo jak obiekty Proc czyli poprzez call(parametry).

say_method.call # => "Timmy!"
move_method.call(4) # => "Moved 4 meters"

Taki obiekt metody można “odczepić” od odbiorcy:

unbound_say = say_method.unbind # => #<UnboundMethod: Timmy#say>

Tak “odczepionej” metody nie można już wywołać:

unbound_say.call
NoMethodError: undefined method `call' for #<UnboundMethod: Timmy#say>
	from (irb):30

Innym sposobem na uzyskanie obiektu UnboundMethod jest pobranie metody proste z klasy.

unbound_method = Timmy.instance_method(:say) # => #<UnboundMethod: Timmy#say>

Taki obiekt można teraz “przypiąć” do odpowiedniego obiektu:

tim = Timmy.new
unbound_method.bind(tim) # => #<Method: Timmy#say>

Jednak przypinanie metody z klasy do obiektu tej samej klasy ma trochę mały sens. Jednak można to wykorzystać na przykład w celu wywołania metody nadnadklasy:

class Person
  def say
    "HELLO"
  end
end

class Kid < Person
  def say
    "Hi"
  end
end

class Jimmy < Kid
  def say
    "Jimmy!"
  end
end

jim = Jimmy.new
jim.say # => "Jimmy!"
jim.class.superclass.superclass.instance_method(:say).bind(jim).call # => "HELLO"

included, extended, inherited

Metody included, extended oraz inherited są (jak można się domyślić) wywoływane kiedy korzystamy z include, extend albo dziedziczenia.

Kiedy dziedziczymy po jakiejś klasie, wywoływana jest na niej metoda inherited (o ile została zadeklarowana):

class Car
  def self.inherited(base)
    puts "#{base} inherits from Car"
  end
end

class Mercedes < Car; end
class Audi < Car; end
class BMW < Car; end

# >> Mercedes inherits from Car
# >> Audi inherits from Car
# >> BMW inherits from Car

Daje to większe możliwości operacji na klasach pochodnych i pozwala na wykonanie dodatkowych operacji.

Kolejną i chyba najczęściej wykorzystywaną metodą jest included. Metoda ta dotyczy modułu, który jest “includowany”. Dzięki temu zamiast pisać:

class Audi
  include Fast::InstanceMethods
  extend Fast::ClassMethods
end

można to uprościć do zapisu:

class Audi
  include Fast
end

a całą resztę pozostawić modułowi:

module Fast
  def self.included(base)
    puts "#{base} includes Fast"
    base.send(:include, InstanceMethods)
    base.extend(ClassMethods)
  end

  module InstanceMethods
    def go
      "Goooooo #{self}"
    end
  end

  module ClassMethods
    def describe
      "I`m #{self}"
    end
  end
end

class Audi
  include Fast
end

class BMW
  include Fast
end

# >> Audi includes Fast
# >> BMW includes Fast

Audi.describe # => "I`m Audi"
BMW.describe # => "I`m BMW"
Audi.new.go # => "Goooooo #<Audi:0x241bc>"
BMW.new.go # => "Goooooo #<BMW:0x24090>"

Ostatnią metodą jest extended, która jest praktycznie identyczna w działaniu jak included z tym że jest wywoływana podczas użycia extend:

module Slow
  def self.extended(base)
    puts "Slow extends #{base}"
  end

  def describe
    "I`m slow #{self}"
  end
end

class Mercedes
  extend Slow
end

# >> Slow extends Mercedes

Mercedes.describe # => "I`m slow Mercedes"

Zarówno inherited jak i included są często wykorzystywane w przeróżnych bibliotekach. Przewagą dołączania modułu nad korzystaniem z dziedziczenia jest to, że moduł możną dołączyć praktycznie zawsze, podczas gdy ustawienie nadklasy nie zawsze jest możliwe.


Looking for comments section?

Send me an email instead to [email protected]