Digamos que eu esteja corrigindo um método em uma classe, como eu poderia chamar o método substituído pelo método de substituição? Ou seja, algo um pouco comosuper
Por exemplo
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
James Hollingworth
fonte
fonte
Foo
e o uso deFoo::bar
. Então você tem que corrigir o método.Respostas:
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:
Isso funciona, se você controlar a criação dos
Foo
objetos. Basta alterar todos os lugares que cria umFoo
para criar umExtendedFoo
. 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 do
Foo
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 :Basicamente, nos limites do sistema, onde o
Foo
objeto 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#DelegateClass
método auxiliar dadelegate
biblioteca no stdlib.Remendo de macaco “limpo”
Module#prepend
Anterior: Mixin PrependingOs 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#prepend
foi adicionado para suportar mais ou menos exatamente esse caso de uso.Module#prepend
faz a mesma coisa queModule#include
, exceto que ele mistura o mixin diretamente abaixo da classe:Nota: Eu também escrevi um pouco sobre
Module#prepend
esta questão: prefix do módulo Ruby vs derivaçãoHeranç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 misturarprepend
:Infelizmente, isso não vai funcionar. É uma boa ideia, porque usa herança, o que significa que você pode usar
super
. No entanto,Module#include
insere o mixin acima da classe na hierarquia de herança, o que significa queFooExtensions#bar
nunca será chamado (e, se fosse chamado,super
ele não se referiria realmente,Foo#bar
mas sim aoObject#bar
que não existe), poisFoo#bar
sempre será encontrado primeiro.Invólucro do método
A grande questão é: como podemos nos apegar ao
bar
mé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: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 comoModule#define_method
pega um bloco e fecha o ambiente lexical ao redor (e é por isso que estamos usando emdefine_method
vez dedef
aqui), ele (e somente ele) ainda terá acesso aold_bar
, mesmo depois de sair do escopo.Breve explicação:
Aqui estamos envolvendo o
bar
método em umUnboundMethod
objeto de método e atribuindo-o à variável localold_bar
. Isso significa que agora temos uma maneira de nos manter,bar
mesmo depois de sobrescritos.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
self
Ruby. Em outras palavras: um método sempre sabe em qual objeto foi chamado, sabe o queself
é. Mas, pegamos o método diretamente de uma classe, como ele sabe o queself
é?Bem, isso não acontece, o que é por isso que precisamos
bind
nossoUnboundMethod
a um objeto em primeiro lugar, que irá retornar umMethod
objeto que pode então chamar. (UnboundMethod
s não podem ser chamados, porque eles não sabem o que fazer sem conhecer o seuself
.)E o que fazemos
bind
? Simplesmente fazemosbind
isso para nós mesmos, dessa maneira, se comportará exatamente como o originalbar
teria!Por fim, precisamos chamar o
Method
que é retornadobind
. No Ruby 1.9, há uma nova sintaxe bacana para isso (.()
), mas se você estiver no 1.8, pode simplesmente usar ocall
mé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”
alias_method
cadeiaO 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!
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
.Um aparte: Refinamentos
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#prepend
exemplo acima: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 emModule#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
você seria capaz de "conectar-se" à execução do
bar
método.No entanto, não está claro se e como você obtém acesso ao
bar
valor de retorno de dentrobar:after
. Talvez possamos (ab) usar asuper
palavra - chave?Substituição
O combinador anterior é equivalente
prepend
a um mixin com um método de substituição que chamasuper
no final do método. Da mesma forma, o combinador posterior é equivalenteprepend
a um mixin com um método de substituição que chamasuper
no início do método.Você também pode fazer coisas antes e depois da chamada
super
, você pode ligarsuper
várias vezes e recuperar e manipularsuper
o valor de retorno, tornandoprepend
mais poderoso que os combinadores de métodos.e
old
palavra chaveEssa ideia adiciona uma nova palavra-chave semelhante a
super
, que permite chamar o método substituído da mesma maneira quesuper
permite chamar o método substituído :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
super
em um método de substituição em umprepend
mix ed é essencialmente o mesmo queold
nesta proposta.redef
palavra chaveSemelhante ao anterior, mas em vez de adicionar uma nova palavra-chave para chamar o método substituído e deixar em
def
paz, adicionamos uma nova palavra-chave para redefinir métodos. Isso é compatível com versões anteriores, pois a sintaxe atualmente é ilegal de qualquer maneira:Em vez de adicionar duas novas palavras-chave, também podemos redefinir o significado de
super
insideredef
:Substituição
redef
Ining um método é equivalente a substituir o método em umaprepend
mistura ed.super
no método de substituição se comporta comosuper
ouold
nesta proposta.fonte
bind
na mesmaold_method
variável?UnboundMethod#bind
retornará um novo, diferenteMethod
, portanto, não vejo nenhum conflito surgindo, independentemente de você chamá-lo duas vezes seguidas ou duas ao mesmo tempo em threads diferentes.old
eredef
? Meu 2.0.0 não os possui. Ah, é difícil não perder as outras idéias concorrentes que nãoDê uma olhada nos métodos de alias, isso é meio que renomear o método para um novo nome.
Para obter mais informações e um ponto de partida, consulte este artigo sobre métodos de substituição (especialmente a primeira parte). Os documentos da API Ruby também fornecem um exemplo (menos elaborado).
fonte
A classe que fará a substituição deve ser recarregada após a classe que contém o método original, portanto
require
, no arquivo que fará a substituição.fonte