Creative Commons License
Excepto donde se indique otra cosa, todo el contenido de este lugar está bajo una licencia de Creative Commons.
Taquiones > perl > dev > Objetos

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:

  1. Los objetos sobre los que trabaja son los de tipo hash, y emplea éste para guardar algunos campos especiales que muestro más adelante.
  2. 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.