LAS CLASES PUEDEN TENER AYUDANTES
Habéis leído bien, ahora las clases pueden tener ayudantes. ¿Qué es un ayudante? Los ayudantes (Helpers) permiten ampliar la funcionalidad de una clase sin tener que heredar de la misma. Con un ejemplo lo vais a ver más claro:
Supongamos que tenemos una clase para controlar el pago de un recibo:
type
TRecibo = class
private
Numero: Integer;
ImpTotal, ImpPagado, ImpPendiente: Real;
public
constructor Create( Cantidad: Real );
procedure Pagar( Cantidad: Real );
end;
Esta sería su implementación:
constructor TRecibo.Create( Cantidad: Real );
begin
ImpTotal := Cantidad;
end;
procedure TRecibo.Pagar( Cantidad: Real );
begin
ImpPagado := ImpPagado + Cantidad;
ImpPendiente := ImpTotal - ImpPagado;
end;
Si yo quisiera añadir un nuevo procedimiento o función a esta clase sin modificarla, debería heredar de la misma y añadirle las nuevas funcionalidades. Pero ahora podemos crear una nueva clase ayudante que permite ampliar funciones o procedimientos a la clase original de este modo:
TAyudanteRecibo = class helper for TRecibo
procedure Devolver( Cantidad: Real );
procedure Pagar( Cantidad: Real ); // vuelvo a redefinir el método Pagar
end;
Esta clase no es una clase normal, ya que no podemos por ejemplo añadir nuevas variables privadas (para eso hay que heredar). Pero si podemos implementar nuevos procedimientos tal como si estuvieran en la clase TRecibo:
procedure TAyudanteRecibo.Devolver( Cantidad: Real );
begin
ImpPagado := ImpPagado - Cantidad;
ImpPendiente := ImpTotal - ImpPagado;
end;
procedure TAyudanteRecibo.Pagar( Cantidad: Real );
begin
ImpPagado := ImpPagado + Cantidad;
ImpPendiente := ImpTotal - ImpPagado;
if ImpPagado >= ImpTotal then
begin
ImpPagado := ImpTotal;
ImpPendiente := 0;
end;
end;
Lo bueno de esto es que el programador que utilice esta clase no notará la diferencia a la hora de programar:
var
Recibo: TRecibo;
begin
Recibo := TRecibo.Create( 100 ); // constructor de la clase TRecibo
Recibo.Pagar( 50 ); // método de la clase TAyudanteRecibo
Recibo.Devolver( 10 ); // método de la clase TAyudanteRecibo
ShowMessage( 'Imp. pendiente=' + FloatToStr( Recibo.ImpPendiente ) );
Recibo.Free;
end;
Este sería el resultado de la operación:
De este modo cualquier programador puede ampliar las funcionalidades de una clase hechas por otro programador escribiendo menos código y ahorrándose la herencia.
LOS ATRIBUTOS ESTRICTOS
Hasta ahora teníamos tres formas de definir un atributo (variable) en una clase:
Private: Sólo se puede acceder al mismo en la misma clase. Ni siquiera las clases que heredan pueden acceder a estas variables.
Protected: Las variables que se encuentren en la sección protected podrán ser accesibles por la clase original y por sus herederas.
Public: Se podrá acceder a las variables públicas tanto en la clase original como en sus herederas así como desde fuera de la clase.
En RAD Studio 2007 ahora tenemos dos nuevos tipos de atributos:
Strict private: Sólo se pueden acceder a estos atributos mediante métodos de la misma clase y nunca de los que heredan de esta. Ni siquiera podemos acceder a estos atributos instanciando el objeto. Por ejemplo:
type
TJuego = class
private
NumeroVidas: Integer;
public
constructor Create;
end;
constructor TJuego.Create;
begin
NumeroVidas := 3;
end;
Así como está definido puedo acceder a la variable NumeroVidas desde el objeto:
var
Juego: TJuego;
begin
Juego := TJuego.Create;
Juego.NumeroVidas := 3;
Juego.Free;
End;
Pero si hago este cambio:
TJuego = class
strict private
NumeroVidas: Integer;
public
constructor Create;
end;
Entonces el compilador me da error en esta línea:
Juego.NumeroVidas := 3;
Es decir, una variable estricta sólo puede ser accesible dentro de la implementación de la clase y nunca desde su instancia (el objeto).
Ahora heredo de esta clase e intento acceder a la variable NumeroVidas:
TMatamarcianos = class( TJuego )
public
procedure QuitarVida;
end;
procedure TMatamarcianos.QuitarVida;
begin
Dec( NumeroVidas );
end;
Al compilar me daría error en la línea:
Dec( NumeroVidas );
Esta variable sólo puede ser utilizada por la implementación de la clase padre. Para solucionar esto tenemos esta otra:
Strict protected: Sólo se pueden acceder a estos atributos mediante métodos de la misma clase y de los que heredan de esta, pero nunca los objetos instanciados de ambas. Por ejemplo:
TJuego = class
strict protected
NumeroVidas: Integer;
public
constructor Create;
end;
TMatamarcianos = class( TJuego )
public
procedure QuitarVida;
end;
procedure TMatamarcianos.QuitarVida;
begin
Dec( NumeroVidas );
end;
Este último procedimiento si está permitido. Lo que no podemos es hacer esto:
var
Matamarcianos: TMatamarcianos;
begin
Matamarcianos := TMatamarcianos.Create;
Matamarcianos.NumeroVidas := 5; // me da error en esta línea
Matamarcianos.QuitarVida;
Matamarcianos.Free;
end;
Resumiendo lo que hemos visto podemos decir que la palabra reservada strict impide acceder a las variables de una clase desde el objeto instanciado.
LOS NUEVOS TIPOS DE CLASES
Mediante una clase cerrada (sealed) podemos evitar que se pueda heredar de la misma. Por ejemplo:
type
TEntidad = class sealed
private
Numero: Integer;
Nombre: String;
public
constructor Create;
procedure SetNombre( s: String );
end;
Si intento hacer esto:
TCliente = class( TEntidad )
private
CIF: String;
end;
El compilador me dirá este error:
Cannot extend sealed class ‘TEntidad’
De este modo creamos una clase hermética que no puede ser ampliada con clases descendentes de la misma. Sería algo así como una clase final.
NUEVOS TIPOS DE DATOS EN LAS CLASES
Otra nueva funcionalidad que tenemos dentro de las clases es definir constantes que pueden consultarse sin tener de instanciarse. Por ejemplo:
type
TProveedor = class
const TIPO = 'ACREEDOR';
end;
Ahora podemos hacer algo como esto sin que provoque un access violation:
ShowMessage( TProveedor.TIPO );
Esto es muy útil para crear nuestras propias constantes dentro de una clase de modo que moviendo la clase de una unidad a otra ya llevamos incorporadas las constantes que necesita.
También podemos definir nuestros propios tipos de datos dentro de una clase:
TPresupuesto = class
type
TNumeracion = record
Serie: Char;
Numero: Integer;
Fecha: TDate;
end;
private
Numeracion: TNumeracion;
public
constructor Create( S: Char; N: Integer; F: TDate );
end;
En la implementación del constructor rellenamos el registro Numeracion:
constructor TPresupuesto.Create( S: Char; N: Integer; F: TDate );
begin
Numeracion.Serie := S;
Numeracion.Numero := N;
Numeracion.Fecha := F;
end;
Esta sería la forma de utilizar la clase TPresupuesto:
var
Presupuesto: TPresupuesto;
begin
Presupuesto := TPresupuesto.Create( 'A', 25, Date );
ShowMessage( Presupuesto.Numeracion.Serie + ' ' +
IntToStr( Presupuesto.Numeracion.Numero ) + ' ' +
DateToSTr( Presupuesto.Numeracion.Fecha ) );
Presupuesto.Free;
end;
Mostrará el resultado con total normalidad:
Pero es que además podemos crear variables en la clase de modo que podamos utilizarlas sin ni siquiera instanciarla. Voy a hacer un pedido al igual que el ejemplo del presupuesto utilizando variables:
TPedido = class
type
TNumeracion = record
Serie: Char;
Numero: Integer;
Fecha: TDate;
end;
class var
Numeracion: TNumeracion;
end;
Ahora podemos asignar un valor a la variable Numeración sin instanciar la clase:
begin
TPedido.Numeracion.Serie := 'B';
TPedido.Numeracion.Numero := 48;
TPedido.Numeracion.Fecha := Date;
ShowMessage( TPedido.Numeracion.Serie + ' ' +
IntToStr( TPedido.Numeracion.Numero ) + ' ' +
DateToSTr( TPedido.Numeracion.Fecha ) );
end;
Estos valores permanecerán siempre estáticos para cualquier objeto de la clase:
Le podemos sacar partido a esto guardando en la clase el contador del último pedido y cuando instanciemos la clase incrementamos el contador. De este modo, todos los objetos siempre saben cual es el último pedido.
También podemos utilizarlo para guardar la configuración global de un programa como si se tratase de un registro (record).
Y por último y no menos importante, podemos crear métodos estáticos en la clases de modo que se pueden llamar sin crear una instancia. Por ejemplo:
TAlumno = class
class var
Nombre: String;
public
class procedure SetNombre( N: String );
class function GetNombre: String;
end;
En esta clase he definido una función y un procedimiento estático que recogen y almacenan la variable estática Nombre:
class procedure TAlumno.SetNombre( N: String );
begin
Nombre := N;
end;
class function TAlumno.GetNombre: String;
begin
Result := Nombre;
end;
Ahora viene lo más cómodo para un programador:
begin
TAlumno.SetNombre( 'JUAN' );
ShowMessage( TAlumno.GetNombre );
end;
Ni he tenido que crear la instancia del objeto ni destruirlo. Ni siquiera he tenido que declarar una variable para utilizarlo, ya que la clase TAlumno funciona como una variable global a todo el programa. ¿Cuántas veces hemos provocado access violations por olvidarnos de instanciar la clase? ¿Y cuantas pérdidas de memoria hemos tenido por olvidarnos del maldito Free? Esto no es que sea una panacea pero para ciertas rutinas es muy útil (incrementar contadores internos, acceder a las opciones del programa, etc.).
ANIDACION DE CLASES
Esto si supone una gran mejora para los que nos gusta estructurar bien la información. Ahora podemos definir una clase dentro de otra:
TAlmacen = class
private
Numero: Integer;
Descripcion: String;
public
type
TProducto = class
private
ID: Integer;
Nombre: String;
Existencias: Real;
public
constructor Create( I: Integer; N: String; E: Real );
end;
constructor Create( N: Integer; D: String );
end;
Esta sería la implementación de sus constructores:
constructor TAlmacen.Create( N: Integer; D: String );
begin
Numero := N;
Descripcion := D;
end;
constructor TAlmacen.TProducto.Create( I: Integer; N: String; E: Real );
begin
ID := I;
Nombre := N;
Existencias := E;
end;
La instanciación de ambas clases se haría de manera normal:
var
Almacen: TAlmacen;
Producto: TAlmacen.TProducto;
begin
Almacen := TAlmacen.Create( 1, 'ALMACEN GENERAL' );
Producto := TAlmacen.TProducto.Create( 1234, 'SOFA CAMA', 5 );
Producto.Free;
Almacen.Free;
end;
Esto hace que nuestro código fuente quede más elegante y con una jerarquía mejor definida.
LOS MÉTODOS FINALES
Si definimos un método como final ya no se podrá reimplementar en las clases descendentes:
type
TBiblioteca = class
private
NumLibros: Integer;
public
procedure CalcularExistencias; virtual; final;
end;
Al definir el prodecimiento CalcularExistencias como final ya no puedo reutilizarlo en las clases descendentes:
TLibreria = class( TBiblioteca )
private
NumArticulos: Integer;
public
procedure CalcularExistencias; override;
end;
El compilador me daría este error:
Cannot override a final method.
Esto tiene su utilidad para evitar que otro programador que herede de esta clase intente reimplementar un método crítico que es esencial en esa clase (reserva de memoria, apertura de archivos, etc.).
En el próximo artículo seguiremos viendo más cosas interesantes de RAD Studio 2007.
Pruebas realizadas con RAD Studio 2007.
1 comentario:
excelente post, gracias.
Publicar un comentario