2013-07-09 10 views
14

Czytałem kilka artykułów o metodach mieszania Rubiego, extend i include i nadal nie jestem do końca pewien co do tego zachowania. Rozumiem, że extend doda metody instancji danego modułu jako metody singleton do modułu wykonującego rozszerzenie i że include będzie zasadniczo dołączać zawartość modułu (metody, stałe, zmienne) do tego, co robi, włączając, definiując efektywnie ich w odbiorniku.Mieszanki rubinowe: rozszerz i włącz

Jednak po kilku majsterkowaniu, próbując zrozumieć, jak to się przejawi, mam kilka pytań. Oto moja konfiguracja testowania:

module Baz 
    def blorg 
    puts 'blorg' 
    end 
end 

module Bar 
    include Baz 
    def blah 
    puts 'blah' 
    end 
end 

module Foo 
    extend Bar 
end 

class Bacon 
    extend Bar 
end 

class Egg 
    include Bar 
end 

Tak jak bym się spodziewał, moduł Bar zyskuje metody instancji zdefiniowane w Baz (#blorg), jak gdyby one zostały zdefiniowane w sobie ze względu na metodę integracji, a klasy Bacon zyski metody singleton Bacon::blah i Bacon::blorg według rozszerzenia.

Bacon.blah # => blah 
Bacon.blorg # => blorg 

I klasa Egg zyski metodach określonych w Bar (#blah a teraz #blorg) jako metody instancji.

Egg.new.blah # => blah 
Egg.new.blorg # => blorg 

Dostaję to wszystko, więc to dobrze.

Jednak nie rozumiem odpowiedzi uzyskanych dzięki zastosowaniu metod #ancestors i #is_a?.

Bacon.ancestors # => [Bacon, Object, Kernel, BasicObject] 
Bacon.is_a? Bar # => true 

Egg.ancestors # => [Egg, Bar, Baz, Object, Kernel, BasicObject] 
Egg.is_a? Bar # => false 

Wydaje się, że rozszerzenie modułu powoduje sposób #is_a? powrotu true po zapytaniu o modułu i nie dodaje się do protoplastów klasy i vice versa w odniesieniu do integracji: przodkowie klasa zawiera moduły dołączone, ale metoda #is_a? zwraca false podczas zapytania. Dlaczego to się dzieje?

+0

+1 za świetny format tego pytania. – sargas

Odpowiedz

24

Różnica polega na tym, że include doda dołączoną klasę do przodków klasy włączającej, natomiast extend doda rozszerzoną klasę do przodków klasy singleton rozszerzających. Uff.Niech najpierw obserwować, co się dzieje:

Bacon.ancestors 
#=> [Bacon, Object, Kernel, BasicObject] 

Bacon.singleton_class.ancestors 
#=> [Bar, Baz, Class, Module, Object, Kernel, BasicObject] 

Bacon.new.singleton_class.ancestors 
#=> [Bacon, Object, Kernel, BasicObject] 

Bacon.is_a? Bar 
#=> true 

Bacon.new.is_a? Bar 
#=> false 

I klasy Egg

Egg.ancestors 
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject] 

Egg.singleton_class.ancestors 
#=> [Class, Module, Object, Kernel, BasicObject] 

Egg.new.singleton_class.ancestors 
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject] 

Egg.is_a? Bar 
#=> false 

Egg.new.is_a? Bar 
#=> true 

Więc co foo.is_a? Klass faktycznie robi to, by sprawdzić, czy foo.singleton_class.ancestors zawiera Klass. Inną rzeczą jest to, że wszyscy przodkowie klasy stają się przodkami klasy singleton instancji podczas tworzenia instancji. To sprawdzi się jako prawdziwe dla wszystkich nowo utworzonych wystąpień dowolnej klasy:

Egg.ancestors == Egg.new.singleton_class.ancestors 

Co to wszystko oznacza? extend i include zrobić to samo na różnych poziomach, mam nadzieję, że poniższy przykład sprawia, że ​​to oczywiste, jak oba sposoby rozszerzyć klasę są zasadniczo równoważne:

module A 
    def foobar 
    puts 'foobar' 
    end 
end 

class B 
    extend A 
end 

class C 
    class << self 
    include A 
    end 
end 

B.singleton_class.ancestors == C.singleton_class.ancestors 
#=> true 

gdzie class << self jest tylko składnia dziwne aby dostać się do klasy singleton . Tak więc extend tak naprawdę jest skrótem dla include w klasie singleton.

0
Egg.is_a? Egg # => false 

Uwzględnia (skutecznie) zmiany instancji klasy Egg. Chociaż nie jest to samo, to jest bardzo podobne do robienia czegoś podobnego

class Egg < Bar 
end 

Gdy przedłużyć doda metod klasy, więc jest to bardzo podobne do robienia czegoś podobnego

class Bacon 
    class << self 
    include Bar 
    end 
end 

Można myślę o tym, jak włącza zmiany w instancjach klasy, gdzie tak naprawdę rozszerzenie zmienia klasę.

+0

Być może źle wpisałeś coś, ale kiedy robię "Egg.new.is_a? Jajo 'zwraca' true'. Czy chodziło Ci o 'Egg.is_a? Egg # => false'? – DesAdams

+0

Tak, zrobiłem. Zmienię to. – Olives