EDIT : Faz 9 anos desde que escrevi originalmente esta resposta, e ela merece alguma cirurgia estética para mantê-la atualizada.
Você pode ver a última versão antes da edição aqui .
Você não pode chamar o método substituído por nome ou palavra-chave. Essa é uma das muitas razões pelas quais o patch para macacos deve ser evitado e a herança preferida, pois obviamente você pode chamar o método substituído .
Evitando o patch do macaco
Herança
Portanto, se possível, você deve preferir algo assim:
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
Isso funciona, se você controlar a criação dos Fooobjetos. Basta alterar todos os lugares que cria um Foopara criar um ExtendedFoo. Isso funciona ainda melhor se você usar o Padrão de Projeto de Injeção de Dependência , o Padrão de Projeto de Método de Fábrica , o Padrão de Projeto de Fábrica Abstrato ou algo nesse sentido, pois, nesse caso, só há um lugar para mudar.
Delegação
Se você não controlar a criação doFoo objetos, por exemplo, porque eles são criados por uma estrutura que está fora do seu controle (comorubi-sobre-trilhospor exemplo), você pode usar o Padrão de Design do Wrapper :
require 'delegate'
class Foo
def bar
'Hello'
end
end
class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end
def bar
super + ' World'
end
end
foo = Foo.new # this is not actually in your code, it comes from somewhere else
wrapped_foo = WrappedFoo.new(foo) # this is under your control
wrapped_foo.bar # => 'Hello World'
Basicamente, nos limites do sistema, onde o Fooobjeto entra no seu código, você o envolve em outro objeto e, em seguida, usa esse objeto em vez do original em qualquer outro lugar do código.
Isso usa o Object#DelegateClassmétodo auxiliar da delegatebiblioteca no stdlib.
Remendo de macaco “limpo”
Os dois métodos acima requerem alteração do sistema para evitar a aplicação de patches em macacos. Esta seção mostra o método preferido e menos invasivo de correção de macacos, caso a alteração do sistema não seja uma opção.
Module#prependfoi adicionado para suportar mais ou menos exatamente esse caso de uso. Module#prependfaz a mesma coisa que Module#include, exceto que ele mistura o mixin diretamente abaixo da classe:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions
end
Foo.new.bar # => 'Hello World'
Nota: Eu também escrevi um pouco sobre Module#prepend esta questão: prefix do módulo Ruby vs derivação
Herança de Mixin (quebrada)
Eu já vi algumas pessoas tentarem (e perguntarem por que não funciona aqui no StackOverflow) algo parecido com isto, isto includeé, misturando em vez de misturar prepend:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
Infelizmente, isso não vai funcionar. É uma boa ideia, porque usa herança, o que significa que você pode usar super. No entanto, Module#includeinsere o mixin acima da classe na hierarquia de herança, o que significa que FooExtensions#barnunca será chamado (e, se fosse chamado, superele não se referiria realmente, Foo#barmas sim ao Object#barque não existe), poisFoo#bar sempre será encontrado primeiro.
Invólucro do método
A grande questão é: como podemos nos apegar ao barmétodo, sem realmente manter um método real ? A resposta está, como costuma acontecer, na programação funcional. Nós nos apossamos do método como um objeto real e usamos um fechamento (isto é, um bloco) para garantir que nós e somente nós nos apegemos a esse objeto:
class Foo
def bar
'Hello'
end
end
class Foo
old_bar = instance_method(:bar)
define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end
Foo.new.bar # => 'Hello World'
Isso é muito limpo: como old_baré apenas uma variável local, ela ficará fora do escopo no final do corpo da classe e é impossível acessá-la de qualquer lugar, mesmo usando reflexão! E como Module#define_methodpega um bloco e fecha o ambiente lexical ao redor (e é por isso que estamos usando em define_methodvez de defaqui), ele (e somente ele) ainda terá acesso aold_bar , mesmo depois de sair do escopo.
Breve explicação:
old_bar = instance_method(:bar)
Aqui estamos envolvendo o barmétodo em um UnboundMethodobjeto de método e atribuindo-o à variável local old_bar. Isso significa que agora temos uma maneira de nos manter, barmesmo depois de sobrescritos.
old_bar.bind(self)
Isso é um pouco complicado. Basicamente, no Ruby (e em praticamente todas as linguagens OO baseadas em despacho único), um método é vinculado a um objeto receptor específico, chamado selfRuby. Em outras palavras: um método sempre sabe em qual objeto foi chamado, sabe o que selfé. Mas, pegamos o método diretamente de uma classe, como ele sabe o que selfé?
Bem, isso não acontece, o que é por isso que precisamos bindnosso UnboundMethoda um objeto em primeiro lugar, que irá retornar um Methodobjeto que pode então chamar. ( UnboundMethods não podem ser chamados, porque eles não sabem o que fazer sem conhecer o seu self.)
E o que fazemos bind? Simplesmente fazemos bindisso para nós mesmos, dessa maneira, se comportará exatamente como o original barteria!
Por fim, precisamos chamar o Methodque é retornado bind. No Ruby 1.9, há uma nova sintaxe bacana para isso ( .()), mas se você estiver no 1.8, pode simplesmente usar o callmétodo; é para isso que .()é traduzido de qualquer maneira.
Aqui estão algumas outras perguntas, nas quais alguns desses conceitos são explicados:
Remendo de macaco “sujo”
O problema que estamos enfrentando com o patch de nosso macaco é que, quando sobrescrevemos o método, o método se foi, então não podemos mais chamá-lo. Então, vamos fazer uma cópia de segurança!
class Foo
def bar
'Hello'
end
end
class Foo
alias_method :old_bar, :bar
def bar
old_bar + ' World'
end
end
Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'
O problema disso é que agora poluímos o espaço para nome com um supérfluo old_bar método . Esse método será exibido em nossa documentação, será exibido na conclusão de código em nossos IDEs, será exibido durante a reflexão. Além disso, ele ainda pode ser chamado, mas, presumivelmente, nós o corrigimos porque, em primeiro lugar, não gostamos de seu comportamento; portanto, talvez não desejemos que outras pessoas o chamem.
Apesar de possuir algumas propriedades indesejáveis, infelizmente se tornou popular através do AciveSupport Module#alias_method_chain.
Caso você precise apenas do comportamento diferente em alguns locais específicos e não em todo o sistema, use Refinements para restringir o patch do macaco a um escopo específico. Vou demonstrar aqui usando o Module#prependexemplo acima:
class Foo
def bar
'Hello'
end
end
module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end
refine Foo do
prepend FooExtensions
end
end
Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!
using ExtendedFoo
# Activate our Refinement
Foo.new.bar # => 'Hello World'
# There it is!
Você pode ver um exemplo mais sofisticado do uso de refinamentos nesta pergunta: Como ativar o patch de macaco para um método específico?
Idéias abandonadas
Antes da comunidade Ruby se estabelecer Module#prepend, havia várias idéias diferentes por aí que você pode ver ocasionalmente referenciadas em discussões mais antigas. Todos estes são incluídos em Module#prepend.
Combinadores de métodos
Uma ideia foi a de combinadores de métodos do CLOS. Esta é basicamente uma versão muito leve de um subconjunto da Programação Orientada a Aspectos.
Usando sintaxe como
class Foo
def bar:before
# will always run before bar, when bar is called
end
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
você seria capaz de "conectar-se" à execução do barmétodo.
No entanto, não está claro se e como você obtém acesso ao barvalor de retorno de dentro bar:after. Talvez possamos (ab) usar a superpalavra - chave?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
Substituição
O combinador anterior é equivalente prependa um mixin com um método de substituição que chama superno final do método. Da mesma forma, o combinador posterior é equivalente prependa um mixin com um método de substituição que chama superno início do método.
Você também pode fazer coisas antes e depois da chamada super, você pode ligar supervárias vezes e recuperar e manipular supero valor de retorno, tornando prependmais poderoso que os combinadores de métodos.
class Foo
def bar:before
# will always run before bar, when bar is called
end
end
# is the same as
module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end
class Foo
prepend BarBefore
end
e
class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
# is the same as
class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end
class Foo
prepend BarAfter
end
old palavra chave
Essa ideia adiciona uma nova palavra-chave semelhante a super, que permite chamar o método substituído da mesma maneira que superpermite chamar o método substituído :
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
O principal problema disso é que ele é incompatível com versões anteriores: se você tiver chamado o método old, não poderá mais chamá-lo!
Substituição
superem um método de substituição em um prependmix ed é essencialmente o mesmo que oldnesta proposta.
redef palavra chave
Semelhante ao anterior, mas em vez de adicionar uma nova palavra-chave para chamar o método substituído e deixar em defpaz, adicionamos uma nova palavra-chave para redefinir métodos. Isso é compatível com versões anteriores, pois a sintaxe atualmente é ilegal de qualquer maneira:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
Em vez de adicionar duas novas palavras-chave, também podemos redefinir o significado de superinside redef:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
Substituição
redefIning um método é equivalente a substituir o método em uma prependmistura ed. superno método de substituição se comporta como superou oldnesta proposta.