Hoje vamos continuar falando de Ruby, é hora de continuar nos aprofundando um pouco mais de
Reflexão e Metaprogramação agora Encadeamento de Alias...
Encadeamento de Alias
Como já visto, metaprogramação em Ruby muitas vezes envolve a dinâmica definição de métodos. Assim como comum é a dinâmica modificação de métodos.
Métodos são modificados com uma técnica que chamaremos de encadeamento de alias. Ele funciona assim:
* Primeiro, criar um alias para o método a ser modificado. este apelido fornece um nome para
a versão não modificada do método.
* Em seguida, definem uma nova versão do método. Esta nova versão deve chamar a versão não modificada
através dos alias, mas pode adicionar qualquer funcionalidade que for necessário, antes e depois de que
faz isso.
Note-se que estes passos podem ser aplicados repetidamente (desde que um alias diferente é usado de cada vez), criando uma cadeia de métodos e aliases.
Este post inclui três exemplos de encadeamento de alias. O primeiro realiza o encadeamento de apelido estaticamente, ou seja, usando pseudônimo
regulares e declarações def. Os segundo e terceiro exemplos são mais dinâmicos; eles são apelidos que acorrentam métodos arbitrariamente nomeados
utilizando alias_method, define_method e class_eval.
Rastreando Arquivos Carregados e Classes Definidas
O Exemplo 1-1 é um código que mantém o controle de todos os ficheiros carregados e todas as classes definidas num programa. Quando o programa sai,
ele imprime um relatório. Você pode usar este código para “instrumento” de um existente programa para que você entenda melhor o que está fazendo. Uma
maneira de usar este código é inserir esta linha no começo do programa:
classtrace
1
require'classtrace'
Uma solução mais fácil, no entanto, é usar a opção -r para o seu intérprete Ruby(irb):
Opção -r
1
ruby-rclasstracemy_program.rb--traceout/tmp/trace
A opção -r carrega a biblioteca especificado antes de começar a executar o programa.
O Exemplo 1-1 usa apelido de encadeamento estático para rastrear todas as chamadas dos métodos Kernel.require e Kernel.load. Ele define um hook
Object.inherited para rastrear as definições de novas classes. E ele usa Kernel.at_exit para executar um bloco de código quando o programa termina.
Além dos encadeamentos de alias require e load e defini Object.inherited, a única modificação do espaço global feita por este código é a
definição de um módulo chamado ClassTrace. Todo o estado necessário para o rastreio é armazenado em constantes dentro deste módulo, de modo que não
poluem o namespace com variáveis globais.
Exemplo 1-1. Rastreando Arquivos Carregados e Classes Definidas
# Definimos este módulo para manter o estado global do require, de modo que# Nós não alteramos o espaço global mais do que o necessário.moduleClassTrace# Esta matriz mantém a nossa lista de arquivos carregados e classes definidas.# Cada elemento é um subarray segurando a classe definida ou o# Arquivo carregado e o quadro de pilha onde ele foi definido ou carregado.T=[]# Array para armazenar os arquivos carregados# Agora defini a constante OUT para especificar onde saída do rastreamento vai.# O padrão é stderr, mas também pode vir a partir de argumentos na linha de comandoifx=ARGV.index("--traceout")# Se existe argumentoOUT=File.open(ARGV[x+1],"w")# Abre o arquivo especificadoARGV[x,2]=nil# E remova os argumentoselseOUT=STDERR# Caso contrário, o padrão para STDERRendend# Passo 1 encadeamento Alias: definir aliases para os métodos originaisaliasoriginal_requirerequirealiasoriginal_loadload# Passo 2 encadeamento Alias 2: definir novas versões dos métodosdefrequire(file)ClassTrace::T<<[file,caller[0]]# Lembre-se de onde que estava carregadooriginal_require(file)# Chame o método originalenddefload(*args)ClassTrace::T<<[args[0],caller[0]]# Lembre-se de onde que estava carregadooriginal_load(*args)# Chame o método originalend# Este método hook é chamado de cada vez que uma nova classe é definidadefObject.inherited(c)ClassTrace::T<<[c,caller[0]]# Lembre-se onde que foi definidoend# Kernel.at_exit registra um bloco a ser executado quando o programa sai# Vamos utilizá-lo para comunicar os dados de arquivo e de classe que recolhemosat_exit{o=ClassTrace::OUTo.puts"="*60o.puts"Files Loaded and Classes Defined:"o.puts"="*60ClassTrace::T.eachdo|what,where|ifwhat.is_a?Class# Report class (with hierarchy) definedo.puts"Defined: #{what.ancestors.join('<-')} at #{where}"else# Report file loadedo.puts"Loaded: #{what} at #{where}"endend}
Métodos encadeamento de segurança da Thread
O alias de encadeamento é feito pelo método Module.synchronize_method, o qual, por sua vez usa um método auxiliar Module.create_alias para definir
um alias adequado para qualquer método dado (incluindo métodos como o operador +).
Depois de definir estes novo métodos Module, Exemplo 1-2 redefine o método synchronized novamente. Quando o método é invocado dentro de uma classe
ou de um módulo, ele chama synchronize_method em cada um dos símbolos que é passado. Curiosamente, contudo, pode também ser chamado sem argumentos,
quando utilizado desta forma, acrescenta sincronização para qualquer método de instância é definido a seguir. (Utiliza o hook para receber
notificação quando um novo método method_added é adicionado.) Note que o código deste exemplo depende do método Object.mutex e a classe
SynchronizedObject.
Exemplo 1-2. Alias de encadeamento de segurança da Thread
# Define um alias corrente Module.synchronize_method de métodos de instância# Assim que sincronizar a instância antes da execução.classModule# Esta é uma função auxiliar para o encadeamento alias.# Dado o nome de um método (como uma string ou símbolo) e um prefixo, cria# Um alias exclusivo para o método, e retornar o nome do alias# Como um símbolo. Quaisquer caracteres de pontuação em nome método original# Serão convertidos em números para que os operadores podem ser alias.defcreate_alias(original,prefix="alias")# Cole o prefixo do nome original e converter pontuaçãoaka="#{prefix}_#{original}"aka.gsub!(/([\=\|\&\+\-\*\/\^\!\?\~\%\<\>\[\]])/){num=$1[0]# Ruby 1.8 character -> ordinalnum=num.ordifnum.is_a?String# Ruby 1.9 character -> ordinal'_'+num.to_s}# Mantenha acrescentando ressalta até chegarmos a um nome que não está em usoaka+="_"whilemethod_defined?akaorprivate_method_defined?akaaka=aka.to_sym# Converter o nome de alias de um símboloalias_methodaka,original# Na verdade criar o aliasaka# Retorna o nome do aliasend# Alias correntam o método nomeado para adicionar sincronizaçãodefsynchronize_method(m)# Primeiro, fazemos um alias para a versão dessincronizado do método.aka=create_alias(m,"unsync")# Agora redefini o original para invocar o alias em um bloco sincronizado.# Queremos o método definido como sendo capaz de aceitar os blocos, de modo que# Não pode usar define_method, e deve avaliar vez uma string com# Class_eval. Note-se que tudo entre% Q {} e da correspondência# É uma string entre aspas, e não um bloco.class_eval%Q{ def #{m}(*args, &block) synchronized(self) { #{aka}(*args, &block) } end }endend# Este método global sincronizado agora pode ser usado de três maneiras diferentes.defsynchronized(*args)# Caso 1: com um argumento e um bloco, sincronizar sobre o objeto# E executar o blocoifargs.size==1&&block_given?args[0].mutex.synchronize{yield}# Caso dois: com um argumento que não é um símbolo e nenhum bloco# Devolve um invólucro de SynchronizedObjectelsifargs.size==1andnotargs[0].is_a?Symbolandnotblock_given?SynchronizedObject.new(args[0])# Caso três: quando invocado em um módulo com nenhum bloco, alias a cadeia# Chamado métodos para adicionar sincronização. Ou, se não há argumentos,# Então apelido acorrentam o próximo método definido.elsifself.is_a?Moduleandnotblock_given?if(args.size>0)# Synchronize the named methodsargs.each{|m|self.synchronize_method(m)}else# Se nenhum método é especificado pelo synchronize o método seguinte defineeigenclass=class<<self;self;endeigenclass.class_evaldo# Use eigenclass para definir métodos de classe# Define method_added para notificação quando próximo método é definidodefine_method:method_addeddo|name|# Primeiro remover esse método hookeigenclass.class_eval{remove_method:method_added}# Em seguida, sincronize o método que acabou de ser adicionadoself.synchronize_methodnameendendend# Caso 4: qualquer outra invocação é um erroelseraiseArgumentError,"Invalid arguments to synchronize()"endend
Métodos de encadeamento para Rastreamento
O Exemplo 1-3 suporta o rastreio de métodos denominados de um objeto. Ele define trace! e untrace! a cadeia e desencadeiam métodos chamados de um
objeto.
A coisa interessante sobre esse exemplo é que ele faz o seu encadeamento de um modo diferente a partir do Exemplo 1-2. Ele simplesmente define métodos
únicos no objeto e usa super dentro do singleton para a cadeia de definição do método original de exemplo. Nenhum método são criado aliases.
Exemplo 8-10. Encadeamento com métodos singleton para rastrear
# Define métodos trace! e untrace! de instância para todos os objetos.# trace! "Cadeias" os métodos chamados por definir métodos singleton# Que adiciona a funcionalidade de rastreamento e use super para chamar o original.# untrace! exclui os métodos singleton para remover o rastreamento.classeObject# os métodos trace especificados, enviando a saída para STDERR.deftrace!(*methods)@_traced=@_traced||[]# Lembre-se o conjunto de métodos traçados# Se nenhum método foi especificado, use todos os métodos públicos definidos# Diretamente (não herdado) pela classe deste objetomethods=public_methods(false)ifmethods.size==0methods.map!{|m|m.to_sym}# Converta qualquer cordas para símbolosmethods-=@_traced# remove métodos que já estão traçadasreturnifmethods.empty?# Voltar mais cedo se não há nada a fazer@_traced|=methods# Adiciona métodos para definir métodos de traçados# Trace o fato de que estamos começando a traçar estes métodosSTDERR<<"Tracing #{methods.join(', ')} on #{object_id}\n"# Singleton métodos são definidos na eigenclasseigenclass=class<<self;self;endmethods.eachdo|m|# Para cada método m# Define uma versão trace singleton do método m.# Saída de informações de rastreamento e usar super para invocar o# Método de instância que é o rastreamento.# Queremos que os métodos definidos para ser capaz de aceitar blocos, de modo que# Não pode usar define_method, e deve avaliar, em vez de uma string.# Note que tudo entre %Q{} e a correspondência é uma# Entre aspas de string, não um bloco. Observe também que há# Dois níveis de interpolações de string aqui. # {} É interpolada# Quando o método singleton é definida. E \ # {} é interpolada# Quando o método singleton é invocado.eigenclass.class_eval%Q{ def #{m}(*args, &block) begin STDERR << "Entering: #{m}(\#{args.join(', ')})\n" result = super STDERR << "Exiting: #{m} with \#{result}\n" result rescue STDERR << "Aborting: #{m}: \#{$!.class}: \#{$!.message}" raise end end }endend# Untrace os métodos especificados ou todos os métodos rastreadosdefuntrace!(*methods)ifmethods.size==0# Se nenhuma métodos especificados untracemethods=@_traced# todos os métodos atualmente rastreadosSTDERR<<"Untracing all methods on #{object_id}\n"else# Caso contrário, untracemethods.map!{|m|m.to_sym}# Converter string para símbolosmethods&=@_traced# todos os métodos especificados que são rastreadosSTDERR<<"Untracing #{methods.join(', ')} on #{object_id}\n"end@_traced-=methods# Retire-os do nosso conjunto de métodos de traçados# Remove os métodos traçados únicos do eigenclass# Note que nós class_eval um bloco aqui, não uma string(class<<self;self;end).class_evaldomethods.eachdo|m|remove_methodm# undef_method não funciona corretamenteendend# Se nenhum método são traçados mais, remover o nosso exemplo varif@_traced.empty?remove_instance_variable:@_tracedendendend