[Cascavel-pm] O código mais bizonho que eu já fiz na minha vida (v. 2.0)
Daniel Ruoso
daniel em ruoso.com
Quarta Junho 16 13:07:53 CDT 2004
Em Qua, 2004-06-16 às 13:51, Flavio S. Glock escreveu:
> Daniel:
>
> Isso não poderia ser feito utilizando o Class::ClassDecorator ?
> O Class::ClassDecorator parece que poderia ser uma maneira de fazer
> "plugins" para classes, embora eu acho que não resolve completamente
> o problema.
Hmmm... não conheço o Class::ClassDecorator, mas vou dar uma olhada.
> Colocando de outra forma, se eu tenho uma hierarquia de classes:
> Classe::A
> Classe::B isa Classe::A
> Classe::C isa Classe::B
> Eu queria uma maneira de poder alterar a Classe::B, de forma que a
> Classe::C pudesse herdar estas alterações ...
Seus problemas se acabaram-se... :) é exatamente isso que o código que
eu mandei faz...
Agora que eu estou com um pouco mais de tempo, vou explicar o meu
código...
Vou começar de baixo para cima, pra ficar mais fácil...
*** ATENÇÃO ***
*** Perigoso, pode causar danos mentais severos, prossiga por sua
própria conta e risco *** :)
package main;
# primeiro eu crio um objeto da classe A1
my $a = bless {}, "A1";
# Então eu vou chamar o método doit do objeto $a
# neste caso vai acontecer o esperado (o método A1::doit ser executado)
print "---- primeira chamada ---\n";
$a->doit;
# Agora eu vou personalizar a classe A1 com a classe
# A2, método doit.
print "---- segunda chamada ---\n";
customize A1 with A2 qw(doit);
# reescrevendo
# A1::customize("A1",Customizer::with("A2","doit"))
# ou ainda...
# my @methods = qw(doit)
# my $source = "A2";
# my @customize_data = Customizer::with($source, em methods)
# A1::customize(@customize_data)
# E agora, a surpresa... ele vai chamar o A2::doit, mesmo
# chamando através do objeto $a que é da classe A1.
$a->doit;
# o método Customizer::with
package Customizer;
sub with {
# recebe o nome da classe customizadora,
# no caso A2
my $customizer = shift;
# e o nome dos métodos a serem personalizados
my @methods = @_;
my @ret;
push @ret, $customizer;
if (@methods) {
# se o usuário já disse quais são os métodos,
# apenas retorna
@ret = (@ret, em methods);
} else {
# senão, vou descobrir todos os métodos de A2,
# exceto o próprio with
no strict 'refs';
# varrendo a symboltable de A2
foreach my $symname (%{$customizer."::"}) {
# pulando o método with
next if $symname eq "with";
# atribui ao typeglob local sym o
# símbolo em questao
local *sym = *{$customizer."::".$symname};
# e vê se o método desse símbolo está definido
if (defined &sym) {
# se tiver, adiciona seu nome ao retorno
push @ret, $symname;
}
}
}
# retorna o nome do customizer e o nome dos métodos
return @ret;
}
# e o customize espera exatamente o retorno do with
sub customize {
# A classe a ser personalizada (A1),
my $target = shift;
# que pode ser ou um objeto ou o nome da classe
$target = ref $target || $target;
# O segundo parâmetro é a classe de origem (A2)
my $source = shift;
# que também pode ser objeto ou nome de classe
$source = ref $source || $source;
# o nome dos metodos a serem customizados
# sao passados como lista (doit)
my @methods = @_;
# Para cada método
foreach my $m (@methods) {
# dizer para o perl que eu estou fazendo algo
# meio fora do normal
no strict 'refs';
# supondo o método A1::doit, se A1::ORIG::doit
# jah estiver definido, vou personalizar o
# A1::ORIG::doit usando o A1::doit, deixando o
# A1::ORIG::doit disponível de novo, que permite
# que depois eu personalize o A1 com o A3, fazendo com
# que o A1::ORIG::doit seja movido para
# A1::ORIG::ORIG::doit e o A1::doit seja movido para
# A1::ORIG::doit
defined *{$target."::ORIG::".$m}
&& customize($target."::ORIG",$target,$m);
# Mexendo no symbol table, eu copio o metodo a
# ser personalizado para dentro de ORIG, por exemplo
# A1::doit é copiado para A1::ORIG::doit
*{$target."::ORIG::".$m} = *{$target."::".$m};
# Agora eu sobrescrevo o A1::doit com o A2::doit
*{$target."::".$m} = *{$source."::".$m};
# Preciso garantir que as variáveis
# A1::doit::ORIG e a A2::doit::ORIG
# sejam as mesmas, isso é necessário porque
# aqui eu não sei o nome dos personalizadores
# anteriores, mas ao executar $self->ORIG::doit
# eu não tenho como saber se é A1::ORIG::doit,
# A1::ORIG::ORIG::doit, sei apenas que é A2::doit
# ou A3::doit
*{$target."::".$m."::ORIG"}
= *{$source."::".$m."::ORIG"};
# para então definir que o A1::doit::ORIG
# e o A2::doit::ORIG sejam A1::ORIG::doit
# isso é fundamental para permitir duas
# customizações no mesmo método. e para
# eu poder chamar o método original através de
# $self->ORIG::doit();
$ {$source."::".$m."::ORIG"} = $target."::ORIG::".$m;
}
}
# Esse é o método de teste do A1...
sub doit {
print "Definido no A1.\n";
}
# esse é o método de teste do A2, que chama o ORIGinal
package A2;
@A2::ISA="Customizer";
sub doit {
my $self = shift;
$self->ORIG::doit;
print "Definido no A2.\n";
}
# para tornar o $self->ORIG::doit possível, é só uma
# questão de definir o pacote e usar AUTOLOAD
package ORIG;
sub AUTOLOAD {
# $self
my $obj = shift;
# ORIG::doit
my $name = $ORIG::AUTOLOAD;
# possiveis parametros
my @params = @_;
# retiro o ORIG:: do ORIG::doit
$name =~ s/^ORIG:://;
# A2
my $pack = caller();
no strict 'refs';
# lembra do A2::doit::ORIG?, é aqui que eu uso ele.
my $orig = $ {$pack."::".$name."::ORIG"};
# A1::ORIG::doit($obj, em params)
&{$orig}($obj, em params);
}
e aí?
Mais detalhes sobre a lista de discussão Cascavel-pm