Jaka jest różnica między włączaniem i rozszerzaniem w Ruby?


415

Właśnie omijałem metaprogramowanie Ruby. Mixin / moduły zawsze potrafią mnie pomylić.

  • include : miksy w określonych metodach modułów jako metody instancji w klasie docelowej
  • ext : miksuje w określonych metodach modułowych jako metodach klasy w klasie docelowej

Więc czy jest to główna różnica, czy może czai się większy smok? na przykład

module ReusableModule
  def module_method
    puts "Module Method: Hi there!"
  end
end

class ClassThatIncludes
  include ReusableModule
end
class ClassThatExtends
  extend ReusableModule
end

puts "Include"
ClassThatIncludes.new.module_method       # "Module Method: Hi there!"
puts "Extend"
ClassThatExtends.module_method            # "Module Method: Hi there!"

Odpowiedzi:


249

To, co powiedziałeś, jest poprawne. Jest jednak coś więcej.

Jeśli masz klasę Klazzi moduł Mod, w tym Modin Klazzdaje przypadki Klazzdostępu do Modmetod. Czy można przedłużyć Klazzz Modpodaniem klasy Klazz dostępu do Mod„s metod. Ale możesz także rozszerzyć dowolny obiekt za pomocą o.extend Mod. W tym przypadku pojedynczy obiekt pobiera Modmetody, mimo że wszystkie inne obiekty z tą samą klasą, co onie.


324

expand - dodaje metody i stałe określonego modułu do metaklasy celu (tj. klasy singleton) np

  • jeśli zadzwonisz Klazz.extend(Mod), teraz Klazz ma metody Mod (jako metody klasowe)
  • jeśli zadzwonisz obj.extend(Mod), teraz obj ma metody Mod (jako metody instancji), ale żadna inna instancja nie obj.classma tych metod dodanych.
  • extend jest metodą publiczną

include - Domyślnie miesza się w metodach określonego modułu jako metod instancji w module / klasie docelowej. na przykład

  • jeśli zadzwonisz class Klazz; include Mod; end;, teraz wszystkie instancje Klazz mają dostęp do metod Mod (jako metod instancji)
  • include jest metodą prywatną, ponieważ ma być wywoływana z klasy / modułu kontenera.

Jednak moduły bardzo często zastępują include zachowanie, dokonując łatania includedmetody małp . Jest to bardzo widoczne w starszym kodzie Railsów. więcej szczegółów od Yehuda Katz .

Więcej informacji na temat includedomyślnego zachowania przy założeniu, że uruchomiłeś następujący kod

class Klazz
  include Mod
end
  • Jeśli Mod jest już uwzględniony w Klazz lub jednym z jego przodków, instrukcja include nie działa
  • Obejmuje również stałe Mod w Klazz, o ile się nie kolidują
  • Daje Klazzowi dostęp do zmiennych modułu Mod, np. @@fooLub@@bar
  • podnosi ArgumentError, jeśli występują cykliczne dołączenia
  • Dołącza moduł jako bezpośredniego przodka dzwoniącego (tzn. Dodaje Mod do Klazz.ancestors, ale Mod nie jest dodawany do łańcucha Klazz.superclass.superclass.superclass. Zatem wywołanie superKlazz # foo sprawdzi Mod # foo przed sprawdzeniem do metody foo prawdziwej nadklasy Klazza. Szczegółowe informacje można znaleźć w RubySpec.).

Oczywiście dokumentacja ruby jest zawsze najlepszym miejscem na te rzeczy. Projekt RubySpec był również fantastycznym zasobem, ponieważ dokładnie dokumentowali funkcjonalność.


22
Wiem, że to dość stary post, ale jasność odpowiedzi nie powstrzymała mnie przed komentowaniem. Wielkie dzięki za miłe wyjaśnienie.
MohamedSanaulla,

2
@anwar Oczywiście, ale teraz mogę komentować i udało mi się znaleźć artykuł ponownie. Jest dostępny tutaj: aaronlasseigne.com/2012/01/17/explaining-include-and-extend i nadal uważam, że schemat znacznie ułatwia zrozumienie
systho

1
Wielką wygraną w tej odpowiedzi jest sposób extendzastosowania metod jako metod klasy lub instancji, w zależności od wykorzystania. Klass.extend= metody klas, objekt.extend= metody instancji. Zawsze (błędnie) zakładałem, że metody klas pochodzą z extendinstancji include.
Frank Koehl,

16

To jest poprawne.

Za kulisami, include to tak naprawdę alias dla append_features , który (z dokumentacji):

Domyślną implementacją Ruby jest dodawanie stałych, metod i zmiennych modułu tego modułu do modułu, jeśli moduł ten nie został jeszcze dodany do modułu lub jednego z jego przodków.


4

Po przejściu includemodułu do klasy metody modułu są importowane jako metody instancji .

Jednak po przejściu extendmodułu do klasy metody modułu są importowane jako metody klasy .

Na przykład, jeśli mamy moduł Module_testzdefiniowany w następujący sposób:

module Module_test
  def func
    puts "M - in module"
  end
end

Teraz dla includemodułu. Jeśli zdefiniujemy klasę Aw następujący sposób:

class A
  include Module_test
end

a = A.new
a.func

Wyjście będzie: M - in module.

Jeśli mamy zamienić linię include Module_testz extend Module_testi ponownie uruchomić kod, otrzymujemy następujący błąd: undefined method 'func' for #<A:instance_num> (NoMethodError).

Zmiana wywołanie metody a.funcna A.funcwyjście zmienia się na: M - in module.

Z powyższego wykonania kodu jasno wynika, że ​​gdy jesteśmy includemodułem, jego metody stają się metodami instancji, a gdy jesteśmy extendmodułem, metody stają się metodami klasy .


3

Wszystkie pozostałe odpowiedzi są dobre, w tym wskazówka pozwalająca przekopać się przez RubySpecs:

https://github.com/rubyspec/rubyspec/blob/master/core/module/include_spec.rb

https://github.com/rubyspec/rubyspec/blob/master/core/module/extend_object_spec.rb

Jeśli chodzi o przypadki użycia:

Jeśli to moduł ReusableModule w ClassThatIncludes klasy, metody, stałe zajęcia, Submoduły i inne deklaracje dostaje odwoływać.

Jeśli rozszerzysz klasę ClassThatExtends o moduł ReusableModule, wówczas metody i stałe zostaną skopiowane . Oczywiście, jeśli nie jesteś ostrożny, możesz marnować dużo pamięci, dynamicznie powielając definicje.

Jeśli używasz ActiveSupport :: Concern, funkcja .included () pozwala bezpośrednio przepisać klasę włącznie. moduł ClassMethods wewnątrz koncernu zostaje rozszerzony (skopiowany) do klasy włącznie.


1

Chciałbym również wyjaśnić mechanizm, który działa. Jeśli nie mam racji, proszę poprawić.

Kiedy używamy include, dodajemy link z naszej klasy do modułu, który zawiera niektóre metody.

class A
include MyMOd
end

a = A.new
a.some_method

Obiekty nie mają metod, tylko klauzule i moduły. Kiedy więc aotrzyma wiadomość some_method, rozpoczyna metodę wyszukiwania some_methodw aklasie własnej, następnie w Aklasie, a następnie w Amodułach klasy, jeśli są dostępne (w odwrotnej kolejności, ostatnie zawarte wygrane).

Kiedy używamy extend, dodajemy link do modułu w klasie własnej obiektu. Jeśli więc użyjemy A.new.extend (MyMod), dodamy link do naszego modułu do klasy lub a'klasy własnej instancji A. A jeśli użyjemy A.extend (MyMod), dodajemy powiązanie z eigenclass klasy A (obiekty, klasy są również obiektami) A'.

więc ścieżka wyszukiwania metody ajest następująca: a => a '=> połączone moduły z klasą' = = A.

istnieje również metoda przedpremierowa, która zmienia ścieżkę wyszukiwania:

a => a '=> moduł wstępny do A => A => dołączony moduł do A.

Przepraszam za mój zły angielski.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.