2010-08-11 9 views
6

No estoy pensando demasiado claro en este momento y posiblemente pasando por alto algo simple. He estado pensando en esto por un tiempo y he estado buscando, pero realmente no puedo pensar en ninguna consulta de búsqueda sensata que me lleve a lo que busco.¿Cuál es la forma más limpia de duplicar la funcionalidad de base/parent.pm para módulos perl sin objetos?

En resumen, me pregunto cómo hacer la herencia del módulo, en la forma en que base.pm/parent.pm lo hace para los módulos orientados a objetos; solo para módulos basados ​​en Exportador.

Un ejemplo hipotético de lo que quiero decir:

Aquí está nuestro guión. Originalmente cargó Foo.pm y llamó a baz(), pero baz() tiene un error terrible (como veremos enseguida), por lo que estamos usando Local/Patched/Foo.pm ahora, que debería arreglar el error. Estamos haciendo esto, porque en este caso hipotético no podemos cambiar a Foo (es un módulo de Cpan en desarrollo activo, como ve), y es enorme (en serio).

#!/usr/bin/perl 

# use Foo qw(baz [... 100 more functions here ...]); 
use Local::Patched::Foo qw(baz [... 100 more functions here ...]); 
baz(); 

Aquí está Foo.pm. Como puede ver, exporta baz(), que llama a qux, que tiene un error terrible, que hace que las cosas se cuelguen. Sin embargo, queremos mantener Baz y el resto de Foo.pm, sin hacer una tonelada de copiar y pegar, especialmente porque podrían cambiar más adelante, debido a que Foo aún está en desarrollo.

package Foo; 
use parent 'Exporter'; 
our @EXPORT = qw(baz [... 100 more functions here ...]); 
sub baz { qux(); } 
sub qux { print 1/0; }   # !!!!!!!!!!!!! 
[... 100 more functions here ...] 
1; 

Por último, ya Foo.pm se utiliza en lugares MUCHOS, no queremos utilizar Sub :: exportador, ya que eso significaría copiar y pegar una solución curita a todas aquellas muchos lugares. En cambio, estamos tratando de crear un nuevo módulo que actúe y se parezca a Foo.pm, y de hecho cargue el 99% de su funcionalidad aún de Foo.pm y simplemente reemplace el sub de feo qux por uno mejor.

Lo que sigue es lo que tal cosa se vería como si Foo.pm estaba orientado a objetos:

package Local::Patched::Foo; 
use parent 'Foo'; 
sub qux { print 2; } 
1; 

Ahora bien, esto obviamente no va a funcionar en nuestro caso actual, ya parent.pm simplemente no hace esta algo.

¿Existe un método simple y limpio para escribir Local/Patched/Foo.pm (usando cualquier módulo de CPAN aplicable) de una manera que funcione, excepto copiar el espacio de nombres de funciones de Foo.pm manualmente?

+1

La moraleja de la historia es que se ahorrará mucho dolor más adelante si se llama a todas sus funciones de biblioteca como 'Class-> foo', en lugar de' Class :: foo'. :) – Ether

+0

Estoy confundido con lo que quieres decir con eso, ya que yo tampoco estoy aquí. : o Realmente estoy tratando de encontrar lo que dice el título de la pregunta, un equivalente sensato de parent/base.pm en el mundo funcional. (Ya que realmente no me gusta la programación orientada a objetos.;)) – Mithaldu

+0

@mithaldu: se aclarará en mi post a continuación. – Ether

Respuesta

3

Simplemente añadiendo en otra forma de mono-parche Foo 's qux función, ésta sin ninguna manipulación manual de typeglob.

package Local::Patched::Foo; 
use Foo(); # load but import nothing 

sub Foo::qux { 
    print "good qux"; 
} 

Esto funciona porque los paquetes de Perl son siempre mutable, y siempre que aparece el código anterior después de cargar Foo.pm, se anulará la rutina existente baz. Es posible que también necesite no warnings 'redefine'; para silenciar cualquier advertencia.

Luego de usarlo:

use Local::Patched::Foo; 
use Foo qw(baz); 

baz(); # calls the patched qux() routine 

Se podría acabar con los dos use líneas escribiendo un método de importación personalizada en Local::Patched::Foo de la siguiente manera:

# in the Local::Patched::Foo package: 

sub import { 
    return unless @_;    # return if no imports 
    splice @_, 0, 1, 'Foo';  # change 'Local::Patched::Foo' to 'Foo' 
    goto &{ Foo->can('import') }; # jump to Foo's import method 
} 

Y entonces es simplemente:

use Local::Patched::Foo qw(baz); 

baz(); # calls the patched qux() 
+0

Falta la canción y el baile de Exporter y, como tal, no funcionará como está, sin embargo, tengo que decir que me gusta mucho más el módulo de parches: http://gist.github.com/519673 Gracias. :) – Mithaldu

+0

Buen punto ... fijación. –

+0

OK, actualizado para incluir el código original fijo, y un nuevo método de importación si desea esa funcionalidad. Además, tenga en cuenta que el cuerpo de 'qux' está en el paquete' Local :: Patched :: Foo', por lo que usar '__PACKAGE__' no devolverá' Foo'. Puede cambiar esto ingresando el paquete 'Foo' antes de la declaración, o bien dentro del cuerpo del submarino. No es que importe en la mayoría de los casos. –

4

Si se trata de una subrutina desea anular, se puede hacer un poco de mono parches:

*Foo::qux = \&fixed_qux;

No estoy seguro si esto es la solución más limpia o mejor, pero para un substituto temporal hasta aguas arriba corrige el error en qux, debería hacer.

0
package Local::Patched::Foo; 
use Foo qw/:all_without_qux/; #see Exporter docs for tags or just list all functions 
use Exporter 'import'; #modern way 
our @EXPORT = qw(baz [... 100 more functions here ...] qux); 
sub qux { print 2; } 
1; 
+0

Esto no funciona. baz() en script.pl todavía llama a Foo :: qux() en lugar de Local :: Patched :: Foo :: qux(). – Mithaldu

+0

No debería, siempre y cuando 'uses Local :: Patched :: Foo' en script.pl sin' use Foo'. – Ether

+0

¿Lo ejecutó con éxito entonces? Si es así, ¿podría publicar una constelación de archivos de trabajo en gist.github? :) – Mithaldu

1

Un enfoque es simplemente reemplazar la referencia secundaria. si puede instalarlo, use el módulo CPAN Sub::Override.En ausencia de eso, esto va a hacer:

package Local::Patched::Foo; 

use Exporter; 

sub baz { print "GOOD baz!\n" }; 

sub import() { 
    *Foo::baz = \&Local::Patched::Foo::baz; 
} 

1; 
+0

Esto (con algunas modificaciones: http://gist.github.com/519673) parece ser bastante agradable. ¿Puedo preguntar por qué reemplazas importación así? – Mithaldu

+0

@mithaldu: solo otra forma de garantizar que su sustitución se ejecutará con "' use' ". Me gusta tu forma de ser. – DVK

1

En lugar de secuestro de respuesta de Alexandr (que era correcto, pero incompleto), he aquí una solución en virtud de copia por separado:


package Foo; 
use Exporter 'import'; 
our @EXPORT = qw(foo bar baz qux); 
our %EXPORT_TAGS = (
    'all' => [ qw(foo bar baz qux) ], 
    'all_without_qux' => [ qw(foo bar baz) ], 
); 

sub foo { 'foo' } 
sub bar { 'bar' } 
sub baz { 'baz' } 
sub qux { 'qux' } 

1; 

package Foo::Patched; 
use Foo qw(:all_without_qux); 
use Exporter 'import'; 
our @EXPORT = qw(foo bar baz qux); 

sub qux { 'patched qux' } 

1; 

package main; 
use Foo::Patched; 

print qux(); 

También puede use Foo; en su programa, siempre que lo use antes de Foo::Patched, o sobrescribirá el parche qux con la versión original rota.

Hay algunas costumbres aquí (al menos están en mi humilde opinión):

  1. no exportan al espacio de nombres de la persona que llama sin ser dicho explícitamente (es decir, mantener @EXPORT vacío, y el uso de @EXPORT_OK y %EXPORT_TAGS para permitir la persona que llama para especificar exactamente lo que desea o, alternativamente, no exportar en absoluto, y utilizar nombres totalmente calificados para todas las funciones de la biblioteca.
  2. Escriba sus bibliotecas para que las funciones se llamen OO-style: Foo->function en lugar de Foo::function Esto hace que sea mucho más fácil anular una función usando el estándar use base sintaxis que todos conocemos y amamos, sin tener que perder el tiempo con las tablas de simbolos de monkeypatching o manipulando las listas de exportadores.
+0

Gracias por el esfuerzo. Lo aprecio, pero pasaste por alto un problema sutil. En el código original Foo :: baz() llama a Foo :: qux() y script.pl llamado Foo :: baz(). Esto es un problema, ya que el error está en Foo :: qux(), que puede que ni siquiera sea exportador. Así que para solucionarlo uno tendría que parchar tanto baz() como qux(), lo cual deseo evitar. :) – Mithaldu

+0

@mithaldu: bueno, es por eso que usar nombres totalmente calificados sin sintaxis OO es algo malo, ver el punto 2 :) – Ether

+0

Buen punto en @exportar allí, lo estaba usando con lo correcto en mente, pero el error nombre. En cuanto a los nombres completamente calificados, sí, son una posible solución. Sin embargo, estoy buscando aplicar esto a algo sobre lo que esencialmente no tengo control, por lo que no puedo cambiarlo aunque lo quisiera. :) (Además, he visto un proyecto que no usaba Exporter en ninguna parte y nombres secundarios totalmente calificados en TODAS PARTES. Puedo decir que no era bonito o mantenible en lo más mínimo.;)) – Mithaldu

0

Yo sugeriría que reemplazar el archivo ofensivo.

mkdir Something 
cp Something.pm Something/Legacy.pm # (or /Old.pm or /Bad.pm) 

y luego ir a ese archivo y editar la línea del paquete:

package Something::Legacy; 

entonces usted tiene un lugar al paso frente al código heredado. Crea un nuevo Algo.pm y obtenga todas sus exportaciones:

use Something::Legacy qw<:all>; 
our @EXPORT  = @Something::Legacy::EXPORT; 
our @EXPORT_OK = @Something::Legacy::EXPORT_OK; 
our %EXPORT_TAGS = %Something::Legacy::EXPORT_TAGS; 

Después de tener todo eso en su paquete actual, simplemente vuelva a implementar el sub.

sub bad_thing { ... } 

Cualquier cosa que su código heredado que llama Something::do_something se llama el viejo código a través de el nuevo módulo. Cualquier código heredado que llame al Something::bad_thing llamará al nuevo código.

Además, puede manipular *Something::Legacy de otras maneras. Si su código no utiliza una llamada local, tendrá que llamar al &Something::Legacy::bad_thing.

my $old_bad_thing = \&Something::Legacy::bad_thing; 
*Something::Legacy::bad_thing = \&bad_thing; 

Así bad_thing todavía se permite usar ese comportamiento, si lo desea:

sub bad_thing { 
    ... 
    eval { 
     $old_bad_thing->(@_); 
    }; 
    unless ($EVAL_ERROR =~ /$hinky_message/) { 
     ... 
    } 
    ... 
} 
Cuestiones relacionadas