17 octubre 2007

Implementando interfaces en Delphi (II)

Cuando creamos clases para nuestros programas de gestión uno se pregunta que ventajas pueden aportar las interfaces dentro de nuestro programa. Pues una de las ventajas es que encapsula aún más nuestra clase dejando sólo accesible los métodos y no las variables internas (ya sean private, protected o public). Por ejemplo, supongamos que deseo implementar una interfaz y una clase para el control de mis clientes:

type
ICliente = interface
function Get_ID: Integer;
function Get_Nombre: string;
function Get_NIF: string;
function Get_Saldo: Real;
procedure Set_ID( ID: Integer );
procedure Set_Nombre( sNombre: string );
procedure Set_NIF( sNIF: string );
procedure Set_Saldo( rSaldo: Real );
end;

TCliente = class( TInterfacedObject, ICliente )
private
ID: Integer;
sNombre, sNIF: string;
rSaldo: Real;
public
function Get_ID: Integer;
function Get_Nombre: string;
function Get_NIF: string;
function Get_Saldo: Real;
procedure Set_ID( ID: Integer );
procedure Set_Nombre( sNombre: string );
procedure Set_NIF( sNIF: string );
procedure Set_Saldo( rSaldo: Real );
end;

Esta clase se puede instanciar de dos formas. Si se hace en una variable de clase:

var
Cliente: TCliente;
begin
Cliente := TCliente.Create;
Cliente.Set_ID( 1 );
Cliente.Set_Nombre( 'CARLOS MARTINEZ RUIZ' );
Cliente.Set_NIF( '67876453F' );
Cliente.Set_Saldo( 145.87 );
end;

entonces se comporta como una clase normal, permitiendo acceder incluso a las variables privadas cuando en el editor de Delphi ponemos:

Cliente.

y esperamos a que el asistente nos muestre las propiedades y métodos de la clase. Y no sólo eso, sino que además nos muestra todas propiedades y métodos que hemos heredado de TObject haciendo más engorrosa la búsqueda de variables y métodos.

Pero si implementamos la clase a partir de la interfaz:

var
Cliente: ICliente;
begin
Cliente := TCliente.Create;
Cliente.Set_ID( 1 );
Cliente.Set_Nombre( 'CARLOS MARTINEZ RUIZ' );
Cliente.Set_NIF( '67876453F' );
Cliente.Set_Saldo( 145.87 );
end;

al teclear en el editor de Delphi:

Cliente.

no sólo hemos eliminado las variables privadas de la lista, sino que además sólo muestra nuestros métodos y los de las interfaz (QueryInterface, _AddRef y _Release). Esto aporta mucha mayor claridad al programador a la hora de instanciar sus clases, sobre todo cuando tenemos que distribuir nuestra librería a otros programadores (ellos sólo tienen que fijarse como se utiliza la interfaz, ni siquiera tienen porque saber como está implementada la clase).

La única desventaja es que hay que crear tantas funciones y procedimientos Set y Get como propiedades tenga nuestra clase. Pero es bueno acostumbrarse a hacerlo así como ocurre con los Beans de Java.

USANDO INTERFACES COMO PARAMETROS EN PROCEDIMIENTOS

Utilizando el polimorfismo mediante interfaces ahora podemos crear procedimientos genéricos que manejen objetos de distinta clase de una manera simple. Usando las interfaces IArticulo e IVehiculo definidas en artículo anterior podemos escribir los siguientes procedimientos:

procedure FacturarArticulos( Articulos: array of IArticulo );
var
i: Integer;
begin
for i := Low( Articulos ) to High( Articulos ) do
Articulos[i].Facturar;
end;

procedure MatricularVehiculos( Vehiculos: array of IVehiculo );
var
i: Integer;
begin
for i := Low( Vehiculos ) to High( Vehiculos ) do
Vehiculos[i].Matricular;
end;

El procedimiento FacturarArticulos no tiene porque saber como se factura internamente cada artículo. Lo mismo ocurre con el procedimiento MatricularVehiculos, ya sea de la clase TCoche o TCamion ya se encargará internamente el objeto de llamar a su método correspondiente, abstrayéndonos a nosotros de como funciona internamente cada clase.

LA INTERFAZ IINTERFACE

Al igual que todos los objetos heredan directa o indirectamente de TObject, todas las interfaces heredan de la interfaz IInterface. Esta interfaz incorpora el método QueryInterface el cual es muy útil para descubrir y usar otras interfaces que implementan el mismo objeto.

Para contar el número de referencias introduce también los métodos _AddRef y _Release. El compilador de Delphi automáticamente proporciona llamadas a estos métodos cuando las interfaces son utilizadas. Para no tener que implementar nosotros a mano estos métodos (ya que una interfaz nos obliga a implementar todos sus métodos), para ello heredamos de la clase TInterfaceObject que proporciona una implementación base para interfaces. Heredar de TInterfaceObject no es obligatorio pero muy útil.

La clase TInterfacedObject está declarada en la unidad System de la siguiente manera:

type
TInterfacedObject = class ( TObject, IInterface )
protected
FRefCount: Integer;
function QueryInterface( const IID: TGUID; out Obj ): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer; read FRefCount;
end;

Por ello en los ejemplos que he mostrado heredo de esa clase aparte de la interfaz:

type
TCliente = class( TInterfacedObject, ICliente )
...

Como puede apreciarse la clase TInterfacedObject hereda de la clase TObject y de la interfaz IUnknown. La interfaz IUnknown es utilizada para crear objetos COM.

En el próximo artículo terminaremos de ver las características más importantes de las interfaces.

Pruebas realizadas en Delphi 7.

1 comentario:

Unknown dijo...

este muy bien tu codigo y me sirvio mucho peero tengo un problema...
implemente este codigo para una dll y me dice que pirde el puntero..
espero k me ayudes ...
gracias

Publicidad