The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NOMBRE

perlobj - Referencia de objetos en Perl

DESCRIPCIÓN

Este documento es una referencia de las características de programación orientada a objetos que ofrece Perl. Si busca una introducción a la programación orientada a objetos en Perl, consulte perlootut.

Antes de comprender cómo funcionan los objetos en Perl debe conocer el concepto de referencia. Encontrará más información en perlref.

Sobre esta base, este documento describe todas las características de la orientación a objetos en Perl. Si solo tiene que escribir código orientado a objetos, lo más recomendable es usar alguno de los sistemas de programación orientada a objetos disponibles en CPAN y descritos en perlootut.

Si desea escribir su propio sistema de orientación a objetos o tiene que mantener código que implementa objetos desde cero, este documento le ayudará a entender exactamente cómo funciona la orientación a objetos en Perl.

La orientación a objetos en Perl se define mediante unos pocos principios básicos:

  1. Un objeto es simplemente una estructura de datos que sabe a qué clase pertenece.

  2. Una clase no es más que un paquete. Una clase proporciona métodos que deben aplicarse a objetos.

  3. Un método es simplemente una subrutina que espera recibir una referencia a un objeto (o un nombre de paquete, en el caso de los métodos de la clase) como primer argumento.

Veamos cada uno de estos principios en profundidad.

Un objeto es simplemente una estructura de datos

A diferencia de otros lenguajes de programación que ofrecen orientación a objetos, Perl no dispone de una sintaxis especial para construir objetos. Los objetos son simplemente estructuras de datos (hashes, arrays, escalares, identificadores de archivos, etc.) que se han asociado explícitamente a una clase concreta.

Esta asociación se crea mediante la función predefinida bless, que normalmente se usa dentro del constructor de la clase.

Veamos un ejemplo de constructor sencillo:

  package Archivo;

  sub new {
      my $clase = shift;

      return bless {}, $clase;
  }

El nombre new no es especial. Podríamos haber llamado al constructor de cualquier otra forma:

  package Archivo;

  sub cargar {
      my $clase = shift;

      return bless {}, $clase;
  }

El convenio actual en los módulos orientados a objetos es usar siempre new como nombre para el constructor, aunque esto no es obligatorio. Cualquier subrutina que bendiga una estructura de datos en una clase es un constructor válido en Perl.

En los ejemplos anteriores, el código {} crea una referencia a un hash anónimo vacío. La función bless recibe esa referencia y asocia el hash a la clase indicada por $clase. En el caso más simple la variable $clase contendrá la cadena "Archivo".

También podemos usar una variable para almacenar una referencia a la estructura de datos que se bendice como objeto:

  sub new {
      my $clase = shift;

      my $self = {};
      bless $self, $clase;

      return $self;
  }

Una vez bendecido el hash al que hace referencia $self, puede empezar a utilizarse para hacer llamadas a métodos. Esto es útil si desea inicializar el objeto en un método independiente:

  sub new {
      my $clase = shift;

      my $self = {};
      bless $self, $clase;

      $self->_inicializar();

      return $self;
  }

Como el objeto también es un hash, puede tratarlo como tal, usándolo para almacenar datos asociados con el objeto. Usualmente, el código de la clase puede tratar el hash como una estructura de datos accesible, mientras que el código externo a la clase siempre debe tratar al objeto como algo opaco. Esto se denomina encapsulación. La encapsulación significa que el usuario de un objeto no tiene por qué conocer cómo está implementado. El usuario únicamente llama a los métodos documentados del objeto.

Tenga en cuenta, sin embargo, que (al contrario de lo que ocurre en otros lenguajes de orientación a objetos) no se asegura ni se fuerza la encapsulación en modo alguno. Si desea que los objetos sean realmente opacos debe conseguirlo por sí mismo. Esto puede hacerse de diversas formas; por ejemplo, mediante el uso de "Objetos Inside-Out" o con algunos módulos específicos de CPAN.

Los objetos están bendecidos; las variables no

La bendición no afecta a la variable que contiene la referencia al objeto bendecido, ni a la referencia almacenada por la variable; lo que se bendice es aquello a lo que hace referencia la variable (lo que algunas veces se denomina referente). Esto se ilustra mejor con el código siguiente:

  use Scalar::Util 'blessed';

  my $foo = {};
  my $bar = $foo;

  bless $foo, 'Clase';
  print blessed( $bar );      # imprime "Clase"

  $bar = "otro valor";
  print blessed( $bar );      # imprime undef

Cuando se produce la llamada a bless con una variable, se bendice la estructura de datos subyacente a la que la variable hace referencia. No se bendicen ni la referencia en sí ni tampoco la variable que contiene la referencia. Esto es por lo que la segunda llamada a blessed( $bar ) devuelve falso. En ese momento $bar ya no almacena una referencia a un objeto.

A veces verá en documentación o en libros antiguos que se habla de "bendecir una referencia" o se describe un objeto como una "referencia bendecida", pero esto no es correcto. No es la referencia la que se bendice como un objeto; se bendice aquello a lo que alude la referencia (es decir, el referente).

Una clase no es más que un paquete

Perl no proporciona una sintaxis especial para definir clases. Un paquete es simplemente un espacio de nombres que contiene variables y subrutinas. La única diferencia es que en una clase la subrutina recibirá como primer argumento un objeto o el nombre de una clase. Esto es solo un convenio, por lo que una clase puede contener en realidad tanto métodos como subrutinas que no operen sobre objetos o clases.

Cada paquete contiene un array especial llamado @ISA. El array @ISA contiene la lista de clases primarias (si las hubiera). Perl examina este array cuando realiza la resolución de métodos, como se verá a continuación.

Es posible establecer @ISA manualmente; puede ver esto en código Perl antiguo. En el código realmente antiguo también se usaba el pragma base. Pero en el código nuevo recomendamos usar el pragma parent para declarar las clases primarias. Este pragma se ocupará de establecer @ISA. También cargará las clases primarias y se asegurará de que el paquete no herede de sí mismo.

Independientemente de cómo se establezcan las clases primarias, la variable @ISA del paquete contendrá la lista de dichas clases primarias. Se trata de una simple lista de escalares, cada uno de los cuales es una cadena que se corresponde con un nombre de paquete.

Todas las clases heredan implícitamente de la clase UNIVERSAL. La clase UNIVERSAL está implementada en el núcleo de Perl y proporciona varios métodos predefinidos, como isa(), can() y VERSION(). La clase UNIVERSAL nunca aparecerá en la variable @ISA de ningún paquete.

Perl sólo proporciona herencia de métodos como una característica propia. La herencia de atributos debe implementarse en la clase. Encontrará más información en la sección "Implementación de métodos de acceso".

Un método no es más que una subrutina

Perl no proporciona ninguna sintaxis especial para la definición de métodos. Un método es simplemente una subrutina normal y se declara con sub. Lo que hace que un método sea especial es que espera recibir o un objeto o un nombre de clase como primer argumento.

Perl ofrece una sintaxis especial para la llamada a métodos: el operador ->. Se describirá en detalle más adelante.

La mayoría de los métodos que escriba operarán sobre objetos:

  sub guardar {
      my $self = shift;

      open my $archivo, '>', $self->ruta() or die $!;
      print {$archivo} $self->datos()      or die $!;
      close $archivo                       or die $!;
  }

Invocación de métodos

La llamada a un método de un objeto se hace de la forma siguiente: $objeto->método.

La parte situada a la izquierda del operador de invocación de método (flecha) es el objeto (o nombre de clase) y la parte situada a la derecha es el nombre del método.

  my $pod = Archivo->new( 'perlobj.pod', $datos );
  $pod->guardar();

La sintaxis -> también se usa para desreferenciar una referencia. Parece el mismo operador, pero en realidad estas dos operaciones son diferentes.

Cuando se llama a un método, lo que hay a la izquierda de la flecha se pasa como primer argumento al método. Esto significa que la llamada Bicho->new(), hace que el método new() reciba la cadena "Bicho" como primer argumento. En la llamada $alfredo->decir(), la variable $alfredo se pasa a decir() como primer argumento.

Al igual que cualquier subrutina en Perl, todos los argumentos pasados en @_ son alias del argumento original. Esto incluye al propio objeto. Si asigna un valor directamente a $_[0], cambiará el contenido de la variable que contiene la referencia al objeto. Recomendamos no hacer esto, a no ser que sepa exactamente qué está haciendo.

Perl mira lo que hay a la izquierda del operador flecha para determinar a qué paquete pertenece el método. Si hay un nombre de un paquete, busca el método en dicho paquete. Si hay un objeto, entonces Perl busca el método en el paquete usado para bendecirlo.

Si no hay ni un nombre de paquete ni un objeto, entonces la llamada al método producirá un error; consulte la sección "Variantes en las llamadas a los métodos" para obtener más información.

Herencia

Ya hemos hablado sobre el array especial @ISA y el pragma parent.

Cuando una clase hereda de otra, cualquier método definido en la clase primaria también estará disponible en la clase derivada. Si intenta llamar a un método no definido en la clase del objeto que hace la llamada, Perl lo buscará también en las clases primarias del objeto.

  package Archivo::MP3;
  use parent 'Archivo';    # establece @Archivo::MP3::ISA = ('Archivo');

  my $mp3 = Archivo::MP3->new( 'Andvari.mp3', $datos );
  $mp3->guardar();

Como no se ha definido el método guardar() en la clase Archivo::MP3, Perl lo buscará en las clases primarias de la clase Archivo::MP3. Si Perl no encuentra un método guardar() en ninguna clase de la jerarquía de herencia, el programa finalizará.

En este caso se encuentra un método guardar() en la clase Archivo. Observe que el objeto pasado a guardar() en este caso sigue siendo un objeto de la clase Archivo::MP3, pese a que el método se encuentra en la clase Archivo.

En una clase derivada se puede sobrescribir un método de la clase primaria. En ese caso, aún es posible llamar al método de la clase primaria mediante la seudoclase SUPER.

  sub guardar {
      my $self = shift;

      say 'Preparado para el rock';
      $self->SUPER::guardar();
  }

El modificador SUPER solo se puede usar para llamar a métodos. No se puede usar para llamadas normales a subrutinas ni para métodos de clase:

  SUPER::guardar($algo);     # ERROR: busca la subrutina guardar() en el paquete SUPER

  SUPER->guardar($algo);   # ERROR: busca el método guardar() en la clase
                           #       SUPER

  $algo->SUPER::guardar(); # CORRECTO: busca el método guardar() en las
                           #       clases primarias

Forma de resolución de SUPER

La seudoclase SUPER se resuelve a partir del paquete en que se hace la llamada. NO se resuelve en base a la clase del objeto. Esto es importante, ya que permite que métodos de distintos niveles de una jerarquía de herencia de muchos niveles llamen correctamente a sus respectivos métodos en la clase primaria correspondiente.

  package A;

  sub new {
      return bless {}, shift;
  }

  sub decir {
      my $self = shift;

      say 'A';
  }

  package B;

  use parent -norequire, 'A';

  sub decir {
      my $self = shift;

      $self->SUPER::decir();

      say 'B';
  }

  package C;

  use parent -norequire, 'B';

  sub decir {
      my $self = shift;

      $self->SUPER::decir();

      say 'C';
  }

  my $c = C->new();
  $c->decir();

En este ejemplo se obtiene el siguiente resultado:

  A
  B
  C

Este ejemplo ilustra la forma de resolución de SUPER. Incluso si el objeto es bendecido en la clase C, el método decir() de la clase B puede llamar a SUPER::decir() y esperar que se busque correctamente en la clase primaria de B (es decir, la clase dónde se produce la llamada al método) y no en la clase primaria de C (es decir, la clase a la que pertenece el objeto).

Hay casos especiales en los que la resolución basada en paquetes puede ser problemática. Si se copia una subrutina de un paquete en otro, la resolución de SUPER se basará en el paquete original.

Herencia múltiple

La herencia múltiple suele indicar un problema de diseño, pero Perl siempre le dará cuerda suficiente como para ahorcarse, si es lo que desea.

Para declarar varias clases primarias basta con pasar varios nombres de clase a use parent:

  package VariosHijos;

  use parent 'Padre1', 'Padre2';

Orden de resolución de métodos

El orden de resolución de métodos solo es interesante en el caso de la herencia múltiple. En el caso de la herencia simple, Perl simplemente busca en la cadena de herencia para encontrar el método:

  Abuelo
    |
  Padre
    |
  Hijo

Si se llama a un método en un objeto de la clase Hijo y el método no está definido en la clase Hijo, Perl buscará el método en la clase Padre y después, si es necesario, en la clase Abuelo.

Si Perl no puede localizar el método en ninguna de estas clases, el programa finalizará mostrando un mensaje de error.

Cuando una clase tiene múltiples clases primarias, el orden de búsqueda de métodos se complica.

De manera predeterminada, Perl hace una búsqueda en profundidad y con recorrido de izquierda a derecha. Esto significa que se comienza buscando en la primera clase primaria del array @ISA y después se busca en todas las clases primarias de esta, y así sucesivamente. Si no hay éxito en la búsqueda del método, se pasará a la siguiente clase primaria del array @ISA de la clase original, y la búsqueda continuará desde ahí.

                BisabueloComún
            /                    \
  AbueloPaterno             AbueloMaterno
            \                    /
             Padre        Madre
                   \      /
                    Hijo

Así, dado el diagrama anterior, Perl buscará siguiendo el orden Hijo, Padre, AbueloPaterno, BisabueloComún, Madre y, por último, AbueloMaterno. Esto puede ser problemático porque ahora se busca en BisabueloComún antes de buscar en todas sus clases derivadas (es decir, antes de intentar la búsqueda en Madre y AbueloMaterno).

Se puede usar el pragma mro para solicitar un orden de resolución de métodos distinto.

  package Hijo;

  use mro 'c3';
  use parent 'Padre', 'Madre';

Este pragma permite cambiar al orden de resolución "C3". A grandes rasgos, el orden "C3" garantiza que no se exploren las clases primarias comunes antes de buscar en las clases derivadas, de forma que el orden de búsqueda será ahora: Hijo, Padre, AbueloPaterno, Madre, AbueloMaterno y, por último, BisabueloComún. Observe que no se trata ya del orden de búsqueda en anchura: todos los antecesores de Padre (excepto los antecesores comunes) se usan en la búsqueda antes de buscar en cualquiera de los antecesores de Madre.

El orden C3 también permite llamar a métodos en clases del mismo nivel mediante la seudoclase next. Consulte la documentación de mro para conocer más detalles sobre esta característica.

Resolución de métodos con almacenamiento en caché

Cuando Perl busca un método, almacena en caché la búsqueda, a fin de que llamadas posteriores a ese método no tengan que repetir la búsqueda. Cambiar la clase primaria de una clase o agregar una subrutina a una clase invalidará el contenido de la caché para dicha clase.

El pragma mro proporciona algunas funciones para manipular directamente la memoria caché de métodos.

Implementación de constructores

Como se ha mencionado previamente, Perl no incluye una sintaxis especial para los constructores. Esto significa que una clase debe implementar su propio constructor. Un constructor es sencillamente un método de la clase que devuelve una referencia a un objeto nuevo.

El constructor también puede aceptar parámetros adicionales que definen el objeto. Vamos a escribir un constructor real para la clase Archivo usada previamente:

  package Archivo;

  sub new {
      my $clase = shift;
      my ( $ruta, $datos ) = @_;

      my $self = bless {
          ruta  => $ruta,
          datos => $datos,
      }, $clase;

      return $self;
  }

Como puede verse, hemos almacenado la ruta y los datos del archivo en el mismo objeto. Hay que tener presente que el objeto es, en realidad, un hash. Más adelante escribiremos métodos de acceso para manipular estos datos.

Para la clase Archivo::MP3 podemos comprobar que la ruta termina en ".mp3":

  package Archivo::MP3;

  sub new {
      my $clase = shift;
      my ( $ruta, $datos ) = @_;

      die "No puede crear un objeto Archivo::MP3 sin la extensión mp3\n"
          unless $ruta =~ /\.mp3\z/;

      return $clase->SUPER::new(@_);
  }

Este constructor permite que sea la clase primaria la que se encargue de crear el objeto.

Atributos

Un atributo es un elemento de información que pertenece a un objeto específico. A diferencia de la mayor parte de los lenguajes orientados a objetos, en Perl no hay una sintaxis especial ni soporte para declarar y manipular atributos.

Los atributos se suelen almacenar en el mismo objeto. Por ejemplo, si el objeto es un hash anónimo, podemos almacenar los valores de los atributos en el hash usando los nombres de los mismos como claves.

Aunque es posible hacer referencia directamente a estas claves del hash fuera de la clase, es recomendable limitar el acceso a los atributos mediante métodos de acceso.

Esto ofrece varias ventajas: Los métodos de acceso facilitan el cambio posterior de la implementación de un objeto manteniendo intacta la API original.

Un método de acceso permite agregar código adicional para controlar el acceso a los atributos. Por ejemplo, podemos aplicar un valor predeterminado a un atributo al que no se asignó ningún valor en el constructor o podemos validar un valor nuevo asignado al atributo.

Además, el uso de los métodos de acceso simplifica la herencia. Las subclases pueden usar los métodos de acceso, en lugar de tener que conocer la implementación interna de una clase primaria.

Implementación de métodos de acceso

Como ocurre con los constructores, en Perl no hay una sintaxis especial para la declaración de los métodos de acceso, de forma que las clases deben proporcionar métodos de acceso escritos explícitamente para ellas. Hay dos tipos comunes de métodos de acceso: de solo lectura y de lectura y escritura.

Un método de acceso de solo lectura simplemente obtiene el valor de un único atributo:

  sub ruta {
      my $self = shift;

      return $self->{ruta};
  }

Un método de acceso de lectura y escritura permite que el código que lo llama establezca y recupere el valor:

  sub ruta {
      my $self = shift;

      if (@_) {
          $self->{ruta} = shift;
      }

      return $self->{ruta};
  }

Un inciso sobre cómo crear código más sólido y seguro

Ni el constructor ni los métodos de acceso del ejemplo anterior son muy sólidos. No comprueban si la $ruta está definida, ni si se trata de una ruta del sistema de archivos válida.

Hacer manualmente estas comprobaciones puede resultar tedioso. También es muy aburrido escribir a mano un conjunto de métodos de acceso. Hay muchos módulos de CPAN (como los módulos recomendados en el documento perlootut) que pueden ayudarle a escribir código más conciso y seguro.

Variantes en las llamadas a los métodos

Perl admite varias formas de llamar a los métodos, además de la que ya hemos usado, $objeto->metodo().

Nombres de métodos como cadenas

Perl permite usar como nombre de un método una variable escalar que contiene una cadena:

  my $archivo = Archivo->new( $ruta, $datos );

  my $metodo = 'guardar';
  $archivo->$metodo();

Funciona exactamente igual que la llamada $archivo->guardar(). Esto puede ser muy útil para escribir código dinámico. Por ejemplo, permite pasar el nombre de un método que se va a llamar como parámetro a otro método.

Nombres de clases como cadenas

Perl también permite usar una variable escalar que contiene el nombre de una clase:

  my $clase = 'Archivo';

  my $archivo = $clase->new( $ruta, $datos );

Esto también permite crear código muy dinámico.

Referencias a subrutinas como métodos

También es posible usar una referencia a una subrutina como un método:

  my $sub = sub {
      my $self = shift;

      $self->guardar();
  };

  $archivo->$sub();

Esto equivale exactamente a escribir $sub->($archivo). Puede observar este modismo en la siguiente llamada a can:

  if ( my $metodo = $objeto->can('foo') ) {
      $objeto->$metodo();
  }

Desreferencia de llamada a método

Perl también permite usar la desreferencia de una referencia escalar en la llamada a un método. Suena complicado, así que veamos algunos ejemplos de código:

  $archivo->${ \'guardar' };
  $archivo->${ devuelve_ref_a_escalar() };
  $archivo->${ \( devuelve_escalar() ) };
  $archivo->${ devuelve_ref_a_subrutina() };

Esto funciona si la desreferencia produce una cadena o una referencia a una subrutina.

Llamadas a métodos en identificadores de archivos

Internamente, los identificadores de archivos de Perl son instancias de las clases IO::Handle o IO::File. Una vez abierto un identificador de archivo, puede usarlo para llamar a métodos. Además, también puede llamar a métodos en los identificadores de archivo STDIN, STDOUT y STDERR.

  open my $archivo, '>', 'ruta/de/archivo';
  $archivo->autoflush();
  $archivo->print('contenido');

  STDOUT->autoflush();

Invocación de métodos de clase

Puesto que Perl permite usar palabras sueltas como nombres de paquetes y subrutinas, a veces se interpreta de forma incorrecta el significado de alguna de estas palabras. Por ejemplo, la construcción Clase->new() puede interpretarse como 'Clase'->new() o como Clase()->new(). La segunda interpretación significa "llamar a una subrutina denominada Clase() y después llamar a new() como un método en el valor devuelto por Clase()". Si hay una subrutina llamada Clase() en el espacio de nombres actual, Perl siempre interpretará Clase->new() como la segunda alternativa: una llamada a new() sobre el objeto devuelto por la llamada a Clase().

Puede forzar a Perl a usar la primera interpretación (es decir, como una llamada a un método de la clase llamada "Clase") de dos formas. En primer lugar, puede agregar :: al nombre de la clase:

    Clase::->new()

Perl siempre interpretará esto como una llamada a un método.

De forma alternativa, puede escribir entre comillas el nombre de la clase:

    'Clase'->new()

Por supuesto, si el nombre de la clase está almacenado en una variable escalar, Perl también hará lo correcto:

    my $clase = 'Clase';
    $clase->new();

Sintaxis indirecta de objeto

Salvo en el caso de los identificadores de archivo, se desaconseja el uso de esta sintaxis, ya que puede confundir al intérprete de Perl. Siga leyendo para obtener más detalles al respecto.

Perl admite otra sintaxis para las llamadas a métodos, denominada notación "indirecta de objeto". Esta sintaxis se denomina "indirecta" porque el método aparece antes que el objeto en el que se hace la llamada.

Se puede usar con cualquier clase o método:

    my $archivo = new Archivo $ruta, $datos;
    guardar $archivo;

Se recomienda evitar el uso de esta sintaxis por varias razones.

En primer lugar, puede ser confusa al leer el código. En el ejemplo anterior no está claro si guardar es un método proporcionado por la clase Archivo o simplemente una subrutina que espera un objeto archivo como primer argumento.

Cuando se usa con métodos de clase el problema es incluso peor. Puesto que Perl permite usar palabras sueltas como nombres de subrutinas, debe adivinar si dichas palabras a continuación del método aluden a un nombre de clase o de subrutina. Es decir, Perl puede resolver la sintaxis como Archivo->new( $ruta, $datos ) o como new( Archivo( $ruta, $datos ) ).

Para analizar este código, Perl usa una heurística basada en los nombres de paquete vistos, en las subrutinas existentes en el paquete actual, en las palabras sueltas usadas y en otros datos. Huelga decir que la heurística puede producir resultados sorprendentes.

Versiones anteriores de la documentación (y algunos módulos de CPAN) recomendaban el uso de esta sintaxis, especialmente para los constructores, de forma que aún pueden encontrarse ejemplos. Sin embargo, recomendamos que no la use en el código nuevo.

Puede obligar a Perl a interpretar las palabras sueltas como nombres de clase agregándoles "::", como se vio previamente.

  my $archivo = new Archivo::$ruta, $datos;

bless, blessed y ref

Como ya se vio, un objeto no es más que una estructura de datos bendecida en una clase mediante la función bless. La función bless permite usar uno o dos argumentos:

  my $objeto = bless {}, $clase;
  my $objeto = bless {};

En la primera forma, el hash anónimo se bendice en la clase especificada por el nombre de clase almacenado en $clase. En la segunda forma, el hash anónimo se bendice en el paquete actual.

La segunda forma se desaconseja rotundamente, ya que impide que la subclase reutilice el constructor de la clase primaria, pero aún se puede ver en código existente.

Si desea averiguar si una variable escalar concreta hace referencia a un objeto, puede usar la función blessed exportada por Scalar::Util (que forma parte del núcleo de Perl).

  use Scalar::Util 'blessed';

  if ( defined blessed($algo) ) { ... }

Si $algo hace referencia a un objeto, esta función devuelve el nombre del paquete en el que se bendijo el objeto. Si $algo no contiene una referencia a un objeto bendecido, la función blessed devuelve undef.

Observe que blessed($algo) devolverá falso si se bendijo $algo en una clase llamada "0". Esto es posible, pero realmente patológico. No cree una clase llamada "0" a menos que sepa exactamente lo que está haciendo.

De forma análoga, la función predefinida ref trata de forma especial las referencias a objetos bendecidos. Si llama a ref($algo) y $algo contiene una referencia a un objeto, devolverá el nombre de la clase en la que se bendijo el objeto.

Si únicamente desea comprobar si una variable contiene una referencia a un objeto, se recomienda el uso de defined blessed($objeto), ya que ref devuelve verdadero para todas las referencias, no solo para los objetos.

La clase UNIVERSAL

Todas las clases heredan automáticamente de la clase UNIVERSAL, disponible en el núcleo de Perl. Esta clase proporciona un conjunto de métodos que se pueden llamar desde una clase o un objeto. También puede sobrescribir algunos de estos métodos en sus clases. Si lo hace, le recomendamos que siga la semántica predefinida que se describe a continuación.

isa($clase)

El método isa devuelve verdadero si el objeto es un miembro de la clase especificada en $clase, o bien de alguna de las subclases de $clase.

Si sobrescribe este método, no debe generar nunca una excepción.

DOES($rol)

El método DOES devuelve verdadero si el objeto proclama realizar el rol indicado en $rol. De manera predeterminada, equivale a isa. Este método se proporciona para su uso con extensiones del sistema de objetos que implementan roles, como Moose y Role::Tiny.

También puede sobrescribir DOES directamente en sus clases. Si sobrescribe este método, no debe generar nunca una excepción.

can($metodo)

El método can comprueba si la clase o el objeto usados disponen de un método llamado $metodo. Comprueba la existencia del método en la clase y en todas sus clases primarias. Si el método existe, devuelve una referencia a la subrutina. Si no existe, devuelve undef.

Si la clase responde a llamadas a métodos a través de AUTOLOAD, puede que desee sobrescribir el método can para que devuelva una referencia a subrutina para aquellos métodos controlados por su método AUTOLOAD.

Si sobrescribe este método, no debe generar nunca una excepción.

VERSION($necesaria)

El método VERSION devuelve el número de versión de la clase (paquete).

Si se incluye el argumento $necesaria, el método comprobará si la versión actual (tal y como se define en la variable $VERSION del paquete) es mayor o igual que $necesaria; el programa finalizará si no se cumple esta condición. La forma VERSION de use llama a este método automáticamente.

    use Paquete 1.2 qw(algunas subrutinas importadas);
    #implicará:
    Paquete->VERSION(1.2);

Le recomendamos que use este método para acceder a la versión de otro paquete, en lugar de mirar directamente en $Paquete::VERSION. El paquete en que se comprueba podría haber sobrescrito el método VERSION.

También le recomendamos que use este método para comprobar si un módulo tiene la versión necesaria. La implementación interna usa el módulo version para comprobar que los diferentes tipos de números de versión se comparan de forma correcta.

AUTOLOAD

Si llama a un método que no existe en una clase, Perl generará un error. Sin embargo, si la clase (o cualquiera de sus clases primarias) define un método AUTOLOAD, entonces se llamará a ese método.

Se llama a AUTOLOAD igual que a un método normal, y desde la perspectiva del código que llama no hay ninguna diferencia. Sea cual sea el valor devuelto por el método AUTOLOAD, se devuelve al código que hace la llamada.

El nombre completo del método llamado está disponible para su clase en la variable global de paquete AUTOLOAD. Como se trata de una variable global, si desea hacer referencia a la misma sin un prefijo de nombre de paquete mediante strict 'vars', debe declararla.

  # XXX - esta es una forma pésima de implementar métodos de acceso, 
  # pero se puede usar. Veamos un ejemplo sencillo.
  our $AUTOLOAD;
  sub AUTOLOAD {
      my $self = shift;

      # Eliminar el calificador del nombre original del método...
      my $llamado =  $AUTOLOAD =~ s/.*:://r;

      # ¿Existe un atributo con este nombre?
      die "No existe el atributo $llamado"
          unless exists $self->{$llamado};

      # si es así, devolverlo...
      return $self->{$llamado};
  }

  sub DESTROY { } # ver a continuación

Si se usa el pragma strict sin la declaración our $AUTOLOAD, este código no se compilará.

Como se indica en el comentario, esta no es una forma adecuada de implementar los métodos de acceso. Es lenta y hace demasiadas suposiciones. Sin embargo, puede encontrar esta forma de proporcionar métodos de acceso en código Perl antiguo. perlootut incluye recomendaciones para la programación orientada a objetos en Perl.

Si su clase dispone de un método AUTOLOAD, le recomendamos que sobrescriba el método can en su clase también. Su versión sobrescrita de can debe devolver una referencia a la subrutina para cualquier método al que responda AUTOLOAD.

Destructores

Cuando se descarta la última referencia a un objeto, se destruye el objeto. Si sólo dispone de una referencia a un objeto almacenada en una variable escalar léxica, se destruirá el objeto cuando se salga del ámbito de la variable escalar. Si almacena el objeto en una variable global de paquete, es posible que no se salga del ámbito del objeto hasta que finalice el programa.

Si desea realizar alguna tarea específica en el momento de destruir el objeto, puede definir un método DESTROY en la clase. Perl siempre llamará a este método cuando sea necesario, a menos que esté vacío.

Se llama de la misma manera que cualquier otro método, con el objeto como primer argumento. No recibe ningún argumento adicional. Pero la variable $_[0] será de solo lectura en el destructor, por lo que no se le puede asignar un valor.

Si su método DESTROY genera un error, se omitirá. No se enviará a STDERR y no hará que el programa finalice. Sin embargo, si el destructor se ejecuta dentro de un bloque eval {}, el error cambiará el valor de $@.

Como los métodos DESTROY pueden llamarse en cualquier momento, debe localizar cualquier variable global que pueda actualizarse mediante DESTROY. En particular, si usa eval {} debería localizar $@, y si usa system o comillas invertidas, debería localizar $?.

Si define AUTOLOAD en la clase, Perl llamará a su método AUTOLOAD para controlar el método DESTROY. Puede evitar este comportamiento definiendo un método DESTROY vacío, como hicimos en el ejemplo de carga automática. También puede comprobar el valor de $AUTOLOAD y volver sin hacer nada cuando se realice la llamada para controlar DESTROY.

Destrucción global

El orden en que se destruyen los objetos durante la destrucción global antes de que finalice la ejecución del programa no se puede predecir. Esto significa que es posible que se destruyan antes los objetos contenidos en su objeto. Antes de llamar a un método en el objeto, debe comprobar si se han definido objetos contenidos:

  sub DESTROY {
      my $self = shift;

      $self->{controlador}->cerrar() if $self->{controlador};
  }

Puede usar la variable ${^GLOBAL_PHASE} para comprobar si está actualmente en la fase de destrucción global:

  sub DESTROY {
      my $self = shift;

      return if ${^GLOBAL_PHASE} eq 'DESTRUCT';

      $self->{controlador}->cerrar();
  }

Esta variable se incorporó en Perl 5.14.0. Si desea detectar si el programa está en la fase de destrucción global en versiones anteriores de Perl, puede usar el módulo Devel::GlobalDestruction, disponible en CPAN.

Si el método DESTROY genera una advertencia durante la fase de destrucción global, el intérprete de Perl agregará la cadena "during global destruction" (durante la destrucción global) a la advertencia.

Durante la fase de destrucción global, Perl siempre reclamará la memoria usada por los objetos antes que la de las referencias no bendecidas. Encontrará más información sobre la fase de destrucción global en la sección "PERL_DESTRUCT_LEVEL" de perlhacktips.

Objetos no basados en hash

En todos los ejemplos considerados hasta ahora se han usado objetos basados en un hash bendecido. Sin embargo, es posible bendecir cualquier tipo de estructura de datos o referente, incluidos escalares, globs y subrutinas. Puede encontrarse con este tipo de cosas al examinar código Perl ajeno.

Veamos un ejemplo de módulo basado en un escalar bendecido:

  package Hora;

  use strict;
  use warnings;

  sub new {
      my $clase = shift;

      my $hora = time;
      return bless \$hora, $clase;
  }

  sub tiempo_Unix {
      my $self = shift;
      return ${ $self };
  }

  my $hora = Hora->new();
  print $hora->tiempo_Unix();

Objetos Inside-Out

En el pasado, la comunidad Perl experimentó con una técnica llamada "objetos inside-out". Un objeto inside-out almacena sus datos fuera de la referencia del objeto indexados mediante una propiedad única del objeto (como su dirección de memoria), en lugar de almacenarlos en el propio objeto. Esto presenta la ventaja de forzar la encapsulación de los atributos del objeto, ya que sus datos no se almacenan en el propio objeto.

Esta técnica se popularizó durante un tiempo (y se recomendó en el libro Perl Best Practices de Damian Conway), aunque no se llegó a adoptar de forma universal. El módulo Object::InsideOut de CPAN proporciona una implementación completa de esta técnica y puede que encuentre esta implementación u otras en código de otros programadores.

Veamos un ejemplo simple de esta técnica, basado en el módulo Hash::Util::FieldHash del núcleo. Este módulo se agregó al núcleo para permitir implementaciones de objetos inside-out.

  package Hora;

  use strict;
  use warnings;

  use Hash::Util::FieldHash 'fieldhash';

  fieldhash my %hora_para;

  sub new {
      my $clase = shift;

      my $self = bless \( my $objeto ), $clase;

      $hora_para{$self} = time;

      return $self;
  }

  sub tiempo_Unix {
      my $self = shift;

      return $hora_para{$self};
  }

  my $hora = Hora->new();
  print $hora->tiempo_Unix();

Seudohashes

Los seudohashes se incluyeron de forma experimental en las primeras versiones de Perl y se eliminaron en la versión 5.10.0. Un seudohash es una referencia a un array al que se puede acceder mediante claves con nombre, como si fuera un hash. Podría ver código de otros programadores con esta característica. Vea la documentación del pragma fields para obtener más información.

VEA TAMBIÉN

Si desea leer una introducción más accesible y sencilla a la programación orientada a objetos en Perl, vea el documento perlootut. También debería consultar perlmodlib, que le proporcionará unas directrices para la construcción de módulos y clases.

TRADUCTORES

  • Manuel Gómez Olmedo

  • Joaquín Ferrero (Tech Lead)

  • Enrique Nell (Language Lead)