Qual'è la differenza tra include ed estende in Ruby?


415

Mi sto solo concentrando sulla metaprogrammazione di Ruby. I mixin / moduli riescono sempre a confondermi.

  • include : mescola i metodi del modulo specificati come metodi di istanza nella classe target
  • extension : mescola i metodi del modulo specificati come metodi di classe nella classe target

Quindi la differenza principale è solo questo o si nasconde un drago più grande? per esempio

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!"

Risposte:


249

Quello che hai detto è corretto. Comunque c'è di più.

Se hai una classe Klazze un modulo Mod, incluso Modin Klazzfornisce istanze di Klazzaccesso ai Modmetodi di. Oppure si può estendere Klazzcon Moddando la classe di Klazz accesso ai Modmetodi di 's. Ma puoi anche estendere un oggetto arbitrario con o.extend Mod. In questo caso il singolo oggetto ottiene Modi metodi anche se non tutti gli altri oggetti con la stessa classe o.


324

estende - aggiunge i metodi e le costanti del modulo specificato alla metaclasse del bersaglio (cioè la classe singleton) ad es

  • se chiami Klazz.extend(Mod), ora Klazz ha i metodi di Mod (come metodi di classe)
  • se chiami obj.extend(Mod), ora obj ha i metodi di Mod (come metodi di istanza), ma nessun'altra istanza di obj.classha quei metodi aggiunti.
  • extend è un metodo pubblico

include : per impostazione predefinita, mescola i metodi del modulo specificato come metodi di istanza nel modulo / classe di destinazione. per esempio

  • se chiami class Klazz; include Mod; end;, ora tutte le istanze di Klazz hanno accesso ai metodi di Mod (come metodi di istanza)
  • include è un metodo privato, perché deve essere chiamato dall'interno della classe / modulo contenitore.

Tuttavia , molto spesso i moduli sovrascrivono include il comportamento mediante l'applicazione di patch al includedmetodo. Questo è molto importante nel codice Rails legacy. maggiori dettagli da Yehuda Katz .

Ulteriori dettagli su include, con il suo comportamento predefinito, supponendo che tu abbia eseguito il seguente codice

class Klazz
  include Mod
end
  • Se Mod è già incluso in Klazz o in uno dei suoi antenati, l'istruzione include non ha alcun effetto
  • Include anche le costanti di Mod in Klazz, purché non si scontrino
  • Dà a Klazz l'accesso alle variabili del modulo Mod, ad es. @@fooO@@bar
  • genera ArgumentError se ci sono inclusioni cicliche
  • Collega il modulo come antenato immediato del chiamante (ovvero aggiunge Mod a Klazz.ancestors, ma Mod non viene aggiunto alla catena di Klazz.superclass.superclass.superclass. Quindi, chiamando superKlazz # foo verificherà Mod # foo prima di controllare al metodo foo della vera superclasse di Klazz. Vedi RubySpec per i dettagli.).

Naturalmente, la documentazione di base di Ruby è sempre il posto migliore dove andare per queste cose. Anche il progetto RubySpec è stata una risorsa fantastica, perché hanno documentato con precisione la funzionalità.


22
So che questo è un post piuttosto vecchio, ma la chiarezza della risposta non potrebbe trattenermi dal commentare. Grazie mille per una bella spiegazione.
MohamedSanaulla,

2
@anwar Ovviamente, ma ora posso commentare e sono riuscito a trovare di nuovo l'articolo. È disponibile qui: aaronlasseigne.com/2012/01/17/explaining-include-and-extend e penso ancora che lo schema renda la comprensione molto più semplice
systho

1
La grande vittoria in questa risposta è come extendapplicare metodi come metodi di classe o di istanza, a seconda dell'utilizzo. Klass.extend= metodi di classe, objekt.extend= metodi di istanza. Ho sempre (erroneamente) assunto i metodi di classe extende l'istanza da include.
Frank Koehl,

16

È corretto.

Dietro le quinte, include è in realtà un alias per append_features , che (dai documenti):

L'implementazione predefinita di Ruby consiste nell'aggiungere le costanti, i metodi e le variabili del modulo di questo modulo a un modulo se questo modulo non è già stato aggiunto a un modulo oa uno dei suoi antenati.


5

Quando si includeinserisce un modulo in una classe, i metodi del modulo vengono importati come metodi di istanza .

Tuttavia, quando si extendinserisce un modulo in una classe, i metodi del modulo vengono importati come metodi di classe .

Ad esempio, se abbiamo un modulo Module_testdefinito come segue:

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

Ora, per il includemodulo. Se definiamo la classe Acome segue:

class A
  include Module_test
end

a = A.new
a.func

L'output sarà: M - in module.

Se sostituiamo la linea include Module_testcon extend Module_tested eseguire di nuovo il codice, riceviamo il seguente errore: undefined method 'func' for #<A:instance_num> (NoMethodError).

Cambiare la chiamata al metodo a.funcper A.funcl'uscita cambia a: M - in module.

Dall'esecuzione del codice precedente, è chiaro che quando siamo includeun modulo, i suoi metodi diventano metodi di istanza e quando siamo extendun modulo, i suoi metodi diventano metodi di classe .


3

Tutte le altre risposte sono buone, incluso il suggerimento per scavare attraverso 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

Per quanto riguarda i casi d'uso:

Se si include il modulo ReusableModule nella classe ClassThatIncludes, vengono indicati i metodi, le costanti, le classi, i sottomoduli e altre dichiarazioni.

Se estendi la classe ClassThatExtends con il modulo ReusableModule, i metodi e le costanti vengono copiati . Ovviamente, se non stai attento, puoi sprecare molta memoria duplicando dinamicamente le definizioni.

Se si utilizza ActiveSupport :: Concern, la funzionalità .included () consente di riscrivere direttamente la classe inclusa. modulo I metodi all'interno di una preoccupazione vengono estesi (copiati) nella classe inclusa.


1

Vorrei anche spiegare il meccanismo in cui funziona. Se non ho ragione, per favore, correggi.

Quando usiamo includestiamo aggiungendo un collegamento dalla nostra classe a un modulo che contiene alcuni metodi.

class A
include MyMOd
end

a = A.new
a.some_method

Gli oggetti non hanno metodi, solo i clasi e i moduli. Quindi, quando ariceve messaggi some_method, inizia il metodo di ricerca some_methodnella aclasse eigen, quindi in Aclasse e quindi collegata ai Amoduli di classe se ce ne sono (in ordine inverso, le ultime vincite incluse).

Quando usiamo extendstiamo aggiungendo il collegamento a un modulo nella classe eigen dell'oggetto. Quindi, se usiamo A.new.extend (MyMod) stiamo aggiungendo il collegamento al nostro modulo alla classe o alla a'classe eigen dell'istanza di A. E se usiamo A.extend (MyMod) stiamo aggiungendo il collegamento all'eigenclass di A (oggetto, le classi sono anche oggetti) A'.

quindi il percorso di ricerca del metodo per aè il seguente: a => a '=> moduli collegati a a' class => A.

inoltre esiste un metodo prepend che modifica il percorso di ricerca:

a => a '=> modulo anteposto A => A => modulo incluso in A

scusa per il mio cattivo inglese.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.