Qual a diferença entre incluir e estender no Ruby?

415

Estou pensando na metaprogramação de Ruby. Os módulos / mixin sempre conseguem me confundir.

  • incluem : combina em métodos de módulo especificados como métodos de instância na classe de destino
  • estender : combina métodos de módulo especificados como métodos de classe na classe de destino

Então, a principal diferença é apenas isso ou um dragão maior está à espreita? por exemplo

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!"
Gishu
fonte
Verifique também este link: juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby
Donato

Respostas:

249

O que você disse está correto. No entanto, há mais do que isso.

Se você tem uma classe Klazze um módulo Mod, inclusive Modno Klazzfornece instâncias de Klazzacesso aos Modmétodos. Ou você pode estender Klazzcom Moddando a classe Klazz acesso a Modmétodos 's. Mas também você pode estender um objeto arbitrário com o.extend Mod. Nesse caso, o objeto individual obtém Modos métodos de mesmo que todos os outros objetos da mesma classe onão o sejam.

domgblackwell
fonte
324

estender - adiciona os métodos e constantes do módulo especificado à metaclasse do alvo (ou seja, a classe singleton), por exemplo

  • se você ligar Klazz.extend(Mod), agora o Klazz tem os métodos do Mod (como métodos de classe)
  • se você chamar obj.extend(Mod), agora obj possui os métodos de Mod (como métodos de instância), mas nenhuma outra instância de obj.classpossui esses métodos adicionados.
  • extend é um método público

include - Por padrão, ele combina os métodos do módulo especificado como métodos de instância no módulo / classe de destino. por exemplo

  • se você ligar class Klazz; include Mod; end;, agora todas as instâncias do Klazz têm acesso aos métodos do Mod (como métodos de instância)
  • include é um método privado, porque deve ser chamado de dentro da classe / módulo do contêiner.

No entanto , os módulos muitas vezes substituem include o comportamento do patch do macaco included. Isso é muito importante no código Rails herdado. mais detalhes de Yehuda Katz .

Mais detalhes sobre include, com seu comportamento padrão, assumindo que você executou o seguinte código

class Klazz
  include Mod
end
  • Se o Mod já estiver incluído no Klazz, ou em um de seus ancestrais, a instrução include não terá efeito
  • Também inclui as constantes de Mod em Klazz, desde que não colidam
  • Dá ao Klazz acesso às variáveis ​​de módulo do Mod, por exemplo, @@fooou@@bar
  • gera ArgumentError se houver inclusões cíclicas
  • Anexa o módulo como o ancestral imediato do chamador (ou seja, adiciona Mod aos Klazz.ancestors, mas Mod não é adicionado à cadeia de Klazz.superclass.superclass.superclass. Portanto, chamar superKlazz # foo verificará Mod # foo antes de verificar para o método foo da superclasse real de Klazz. Consulte o RubySpec para obter detalhes.)

Obviamente, a documentação principal do ruby é sempre o melhor lugar para essas coisas. O projeto RubySpec também foi um recurso fantástico, porque eles documentaram a funcionalidade com precisão.

John Douthat
fonte
22
Eu sei que esse post é bem antigo, mas a clareza da resposta não me impediu de comentar. Muito obrigado por uma boa explicação.
MohamedSanaulla
2
@ anwar Obviamente, mas agora posso comentar e consegui encontrar o artigo novamente. Ele está disponível aqui: aaronlasseigne.com/2012/01/17/explaining-include-and-extend e eu ainda acho que o esquema tornar a compreensão muito mais fácil
systho
1
A grande vitória nesta resposta é como extendaplicar métodos como métodos de classe ou instância, dependendo da utilização. Klass.extend= métodos de classe, objekt.extend= métodos de instância. Eu sempre (incorretamente) assumi que os métodos de classe vieram extende a instância de include.
31818 Frank Koehl
16

Está correto.

Nos bastidores, include é na verdade um alias para append_features , que (dos documentos):

A implementação padrão do Ruby é adicionar constantes, métodos e variáveis ​​de módulo deste módulo ao aModule se este módulo ainda não tiver sido adicionado ao aModule ou a um de seus ancestrais.

Toby Hede
fonte
5

Quando você includeinsere um módulo em uma classe, os métodos do módulo são importados como métodos de instância .

No entanto, quando você extendcria um módulo em uma classe, os métodos do módulo são importados como métodos de classe .

Por exemplo, se tivermos um módulo Module_testdefinido da seguinte forma:

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

Agora, para o includemódulo. Se definirmos a classe da Aseguinte maneira:

class A
  include Module_test
end

a = A.new
a.func

A saída será: M - in module.

Se substituir a linha include Module_testcom extend Module_teste executar o código novamente, nós receber o seguinte erro: undefined method 'func' for #<A:instance_num> (NoMethodError).

Alterar a chamada de método a.funcpara A.func, a saída muda para: M - in module.

A partir da execução do código acima, fica claro que, quando includeum módulo, seus métodos se tornam métodos de instância e, quando extendum módulo, seus métodos se tornam métodos de classe .

Chintan
fonte
3

Todas as outras respostas são boas, incluindo a dica para pesquisar no 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

Quanto aos casos de uso:

Se você incluir o módulo ReusableModule na classe ClassThatIncludes, os métodos, constantes, classes, submódulos e outras declarações serão referenciados.

Se você estender a classe ClassThatExtends com o módulo ReusableModule, os métodos e constantes serão copiados . Obviamente, se você não for cuidadoso, poderá desperdiçar muita memória duplicando dinamicamente as definições.

Se você usa o ActiveSupport :: Concern, a funcionalidade .included () permite reescrever a classe incluindo diretamente. O módulo ClassMethods dentro de uma preocupação é estendido (copiado) para a classe inclusive.

Ho-Sheng Hsiao
fonte
1

Eu também gostaria de explicar o mecanismo como ele funciona. Se eu não estiver certo, corrija.

Quando usamos include, estamos adicionando uma ligação da nossa classe a um módulo que contém alguns métodos.

class A
include MyMOd
end

a = A.new
a.some_method

Objetos não têm métodos, apenas classes e módulos. Portanto, quando arecebe uma mensagem, some_methodele começa o método de busca some_methodna aclasse eigen, depois na Aclasse e depois nos Amódulos da classe, se houver algum (em ordem inversa, as últimas vitórias incluídas).

Quando usamos extend, estamos adicionando ligação a um módulo na classe eigen do objeto. Então, se nós usamos A.new.extend (MyMod) estamos adicionando ligação com o nosso módulo de classe instância eigen de A ou a'classe. E se usarmos A.extend (MyMod), estamos adicionando ligação à classe própria de A (do objeto, as classes também são objetos) A'.

portanto, o caminho de pesquisa do método aé o seguinte: a => a '=> módulos vinculados a uma' classe => A.

também existe um método de prefpend que altera o caminho da pesquisa:

a => a '=> módulos anexados a A => A => módulo incluído em A

desculpe pelo meu inglês ruim.

user1136228
fonte