Objetos
Clases base
Cada vez que es necesario construir objetos tenemos que repetir, una y otra vez, las mismas definiciones, por lo que se han escrito numerosos módulos para paliarlo. Algunos son muy complicados, otros muy particulares y todos ellos obligan a seguir unas reglas con las que podemos estar de acuerdo o no. Esto puede resultar molesto para algunos espíritus inquietos, entre los que no quiero incluirme, pero si lo que se pretende es reducir esfuerzos no cabe otra opción que elegir uno de ellos.
Tras mucho rebuscar he elegido Class::Base, de Andy Wardley, porque proporciona lo mínimo para crear objetos heredables sin interferir demasiado en su estructura.
Para emplearlo basta con usarlo como base de un paquete, y automáticamente disponemos de un conjunto de métodos que harán el trabajo duro, y que se pueden sobrecargar sin problemas.
[!] Un par de advertencias:
- Los objetos sobre los que trabaja son los de tipo hash, y emplea éste para guardar algunos campos especiales que muestro más adelante.
- Este módulo no crea métodos de acceso, para eso prefiero emplear otro módulo distinto que no interfiere con él.
Un ejemplo es:
1 package MyPackage; 2 use strict; 3 use warnings; 4 5 use base qw(Class::Base); 6 7 sub my_method { 8 my $self = shift; 9 10 } 11 12 package main; 13 14 my $obj = MyPackage->new( email => q(yo@aqui.com), license="artistic" ); 15 16 if (not $obj) { 17 die "Error creando objeto: " . $MyPackage::ERROR; 18 } 19 20 ...
MyPackage dispone además de un mecanismo de inicialización, otro de gestión de errores y otro de depuración.
Creando objetos
Para crear nuevos objetos, Class::Base
dispone del método new()
, el cual puede
recibir un hash ó una referencia a uno, con los pares de valores para
inicializar el nuevo objeto.
Como extra, Class::Base
incluye un método llamado clone()
que sirve para crear
copias del objeto. También se puede sobrecargar para refinar el proceso.
Inicializando objetos
El nuevo paquete dispone de dos métodos para inicializarlo, init()
y
params()
, que pueden usarse conjuntamente porque cada cuál tiene su parcela.
$self->init( $config_hash )
Es método se llama desde new()
, puede sobrecargarse, y recibe una referencia
a un hash con los valores recibidos por new()
.
Su propósito es proporcionar una forma de inicialización personalizada para
cada clase, y para controlar el flujo de trabajo debe retornar la referencia
al objeto en caso de éxito ó un valor undef en caso de fracaso. Ésto último
puede realizarse vía el método error()
del mismo objeto.
1 sub init { 2 my ($self, $config) = @_; 3 4 # inicializamos tomando los valores del hash 5 ... 6 7 # en caso de error 8 if ($failure) { 9 return $self->error('Parámetros erróneos'); 10 } 11 else { 12 return $self; 13 } 14 }
$self->params( $config_hash, [ @fields | \@fields | \%values ] )
El método params
sirve para inicializar un objeto con valores
predeterminados.
Recibe una referencia a un hash similar al que recibe el método init()
y
una lista de nombres de campos con los que efectúa una búsqueda sobre el
primer parámetro. Si lo encuentra copia el valor en el objeto y, sólo en el
caso de recibir una referencia a un hash, emplea el valor correspondiente
como valor predeterminado en el objeto.
El siguiente código muestra cómo se puede asegurar la inicialización de valores en un objeto nuevo.
1 package MyPackage; 2 3 ... 4 5 my %_defaults = ( 6 email => q(root@localhost), 7 license => q(GPL), 8 version => q(0.0.1), 9 ); 10 11 sub init { 12 my ($self,$config) = @_; 13 14 if (not $self->params( $config, \%_defaults )) { 15 return $self->error('fallo de inicialización'); 16 } 17 18 return $self; 19 } 20 21 package main; 22 23 my $obj = MyPackage->new( license => 'bsd', author => 'moi' ); 24 25 # ahora $obj contiene los siguientes valores ... 26 # { 27 # email => q(root@localhost), 28 # author => q(moi), 29 # license => q(bsd), 30 # version => q(0.0.1), 31 # }
Control de errores y depuración
Class::Base
crea a su vez un puñado de métodos que permiten implementar un
mecanismo de anuncio de errores y registro de actividades (vulgo depuración)
en cada nuevo objeto heredado de él.
[$self|Clase]->error( $error_message, ... )
Este método permite establecer una situación de error dentro del objeto ó recuperarla para examinarla.
Puede llamarse de varias formas y, en el caso de recibir una lista de parámetros, los concatena antes de emplearlos y devuelve un valor undef para ser empleado como indicador de fallo por funciones superiores:
- Como método de clase cambia el valor de la variable de paquete
$ERROR
ó permite leerla. - Como método de instancia manipula el atributo
_ERROR
del objeto con los mismo efectos.
$self->id( $new_identificator )
Este método permite recuperar un identificador textual para el objeto ó cambiarlo por otra cosa.
Emplea el atributo _ID
del objeto y tiene como valor predeterminado el
nombre de la clase a la que pertenece el objeto entre corchetes:
[NombreDeLaClase]
.
Es utilizado principalmente por el método debug()
.
[$self|Clase]->debug( $message, ... )
La finalidad de éste método es enviar copia de todos los parámetros recibidos
a la salida estándar de errores STDERR
. Como semáforo emplea el atributo
_DEBUG
, si se llama como método de instancia, ó la variable de paquete
$DEBUG
, si se utiliza como método de clase; en caso de que contenga un valor
verdadero envía los textos y no hace nada en caso contrario.
[$self|Clase]->debugging( [ $debug_flag_value ] )
Dado que para controlar la salida de depuración se emplea el atributo _DEBUG
ó la variable de clase $DEBUG
, el método permite fijar su valor, si recibe
un parámetro, ó consultarlo en caso contrario.
Métodos de acceso
Dado que Class::Base
no crea métodos de acceso y modificación para los atributos de
una clase necesitamos otra herramienta que pueda trabajar con él. Tras una
búsqueda también intensiva he encontrado un paquete muy útil para ello: accessors::classic
de Steve Purkis. En realidad el paquete en sí es accessors pero
admite variantes en su comportamiento y para mi gusto la más adecuada es
accessors::classic
.
Funciona como un pragma que permite crear los métodos para la clase en tiempo de compilación, y empleando como principio que el objeto está basado en un hash.
Un ejemplo de uso con el paquete anterior sería:
1 package MyPackage; 2 use strict; 3 use warnings; 4 5 use base qw(Class::Base); 6 use accessors::classic( qw( email license version ) ); 7 8 ... 9 10 package main; 11 12 my $obj = MyPackage->new(); 13 14 $obj->license( q(Other) ); 15 $obj->email( q(spamtrap@localhost) );
Los métodos creados son del siguiente tipo:
1 sub email { 2 my $self = shift; 3 4 $self->{email} = shift if @_; 5 6 return $self->{email}; 7 }
y es conveniente tener en cuenta que siempre retornan el valor actual del atributo, y no una referencia al objeto tal y como hace el módulo principal.