Understanding Interfaces/es

From Lazarus wiki
Revision as of 01:40, 9 March 2020 by Trev (talk | contribs) (Added page template; removed categories in template)

English (en) español (es)

La razón de las interfaces

Las clases que extienden otras clases pueden llamarse sub-clases. Por ejemplo, podría extenderse la clase bicicleta para tener una sub-clase montañera, y una sub-clase monareta. Ellas heredan muchas de las funciones comunes de la clase bicicleta., pero se agregan opciones únicas como ruedas estabilizadoras para la monareta. Puedes llamar métodos de bicicleta, sabiendo que aplican generalmente a todos los tipos de bicicleta.

Este es un uso tradicional y estándar para las clases, desde que las sub-clases son sólo variaciones de un tema. Pero suponiendo que tuvieras una aplicación donde tuvieras objetos de clase bicicleta, de carros, de lava-autos y otros. Y en esta aplicación quisieras que cada clase tuviera el siguiente método:

 function EsReciclable : Boolean;

Esto le dejaría saber si el objeto contiene materiales reciclables. Podríase definir una clase de alto nivel que contuviera este método, y definir todas las clases derivadas de ella. O simplemente podrías agregar el método a cada sub-clase.

O podría usar interfaces. Las interfaces simplemente pondría en orden esta situación. Definiendo una interfaz que agrupe estos métodos en común, coloca un estándar para todas las clases que implementen la interfaz. Hacen más fácil que el programador se discipline en desarrollar la aplicación; todas las clases tienen un conjunto de métodos que son idénticos y el compilador insiste que todos los métodos en una interfaz se implementen.

Regresando al escenario del objeto reciclable, cada sub-clase hereda funciones comunes de su clase padre en particular. Esto describe la estructura básica de la sub-clase. También hereda (por implementación) funciones comunes que atraviesan a todas las clases.


Ejemplo de una interfaz

Como muchas ideas, un ejemplo es la mejor forma de ilustrar los conceptos. Lo haremos por etapas para mantener las cosas tan simples como sea posible. Primero, definamos una clase carro:

 type
   // Definimos nuestra clase Carro
   TCarro = class
   private
     carroMarca: String;
     carroEdad: Byte;
   public
     // Constructor del Carro
     constructor Create(marca: String);
   published
     // Propiedades del Carro
     property Marca: String read carroMarca;
     property Edad: Byte read carroEdad write carroEdad;
   end;

Esta clase se define por defecto basado en TObject desde que no especificamos un tipo de clase base. Esta clase tiene dos propiedades, y un constructor, que se muestran aquí:

 // Constructor de la implementación para la clase Carro
 constructor TCarro.Create(marca : String);
 begin
   // Guarda el marca del carro y pone por defecto la edad
   carroMarca := marca;
   carroEdad := 0;
 end;

Aquí asignamos las dos propiedades del carro: su marca y edad. La marca se pasa como parámetro y asignamos una edad por defecto de 0. Ahora definiremos otra clase – una clase bicicleta:

 type
   // Definimos nuestra clase Bicicleta
   TBicicleta = class
   private
     ciclaEsMacho: Boolean;
     ciclaTamRueda: Byte;
   published
     // Propiedades de la Bicicleta
     property esMacho: Boolean read ciclaEsMacho;
     property tamRueda: Byte read ciclaTamRueda write ciclaTamRueda;
     // Constructor de la Bicicleta
     constructor Create(esMacho: Boolean; tamRueda: Byte);
   end;

Esta clase tiene dos propiedades y un constructor como aquí se muestra:

 // Constructor de la implementación para la clase bicicleta
 constructor TBicicleta.Create(esMacho: Boolean; tamRueda: Byte);
 begin
   // Save the passed parameters
   ciclaEsMacho := esMacho;
   ciclaTamRueda := tamRueda;
 end;

Esta clase es un poco distinta de la clase del carro, suficiente para ver que podríamos no haber basado el carro en la bicicleta o la bicicleta en el carro. Ahora definiremos una interfaz que diga si un objeto clase es reciclable:

 type
   // Una definición de interfaz
   IReciclable = Interface(IInterface)
     // Una sola función que da soporte a la propiedad
     function MiraSiEsReciclable: Boolean;
     // Una sola propiedad
     property esReciclable: Boolean read MiraSiEsReciclable;
   end;

Nuestra interfaz usa la definición estándar Iinterface como base. Las definiciones de interfaces son como definiciones de clase con todos los elementos abstractos, así que no tenemos que declararlos como abstractos – por defecto lo son.

Esta interfaz añade una propiedad esReciclable a cada clase que la implemente. Cada clase que la implementa tendrá que ser garantizada para tener exactamente la misma forma de preguntar si es reciclable. Este es el poder y el beneficio de las interfaces – uniformidad a través de clases potencialmente muy distintas.

Cualquier clase puede implementar tantas interfaces como se quiera – puede conformar cualquier estándar global en efecto.

Note que debemos definir la propiedad como usando una función – no podemos declarar un campo de datos Booleano en la interrfaz mientras las interfaces no contengan datos.

Ahora cambiemos nuestras clases para dar soporte a esta definición de interfaz:

 type
   // Definimos nuestra clase Carro
   TCarro = class(TInterfacedObject, IReciclable)
   private
     carroMarca: String;
     carroEdad: Byte;
     carroEsReciclable: Boolean; // Añadido para dar soporte a IReciclable
     function MiraSiEsReciclable : Boolean; // Añadido para IReciclable
   public
     // Constructor del Carro
     constructor Create(marca: String);
   published
     // Propiedades del Carro
     property Marca: String read carroMarca;
     property Edad: Byte read carroEdad write carroEdad;
     // Añadido para IReciclable
     property esReciclable: Boolean read MiraSiEsReciclable;
   end;

Note que pusimos la función usada por la propiedad de interfaz esReciclable en la sección privada – queremos que use la propiedad sólo quien le llama.

(Nota del autor: cuando se compila, el compilador insiste en la presencia de la función MiraSiEsReciclable, pero no en la parte más importante – la propiedad. Así como lo puede ver el autor, esto no obliga la opción predominante de la interfaz – la propiedad!).

Ahora hemos basado nuestra clase en la clase TInterfaceObject, la cual provee algo de soporte estándar para las clases que implementan interfaces. Y hemos también basado nuestra clase en IReciclable, nuestra nueva interfaz.

Pero también debemos declarar la nueva función MiraSiEsReciclable:

 // Función del Carro requerida por el atributo EsReciclable
 function TCarro.MiraSiEsReciclable: Boolean;
 begin
   Result := carroEsReciclable;
 end;

Y no debemos olvidar asignar este valor reciclable. Lo haremos crudamente aquí en el constructor:

 // Constructor implmentation for the car class
 constructor TCarro.Create(marca: String);
 begin
   // Guarda la marca del carro y asigna una edad por defecto
   carroNombre := marca;
   carroEdad := 0;
   carroEsReciclable := true; // Asigna que es reciclable
 end;

Uff! Pero debemos hacer lo mismo para la clase Bicicleta para mostrar el efecto real. Veremos el código completo para definir y usar estas clases, las cuales puedes copiar y pegar en el Editor de Código:

  unit Unit1;

  {$mode objfpc}{$H+}

  interface

  uses
    Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs;

  type
    // Una definición de interfaz
    IReciclable = Interface(IInterface)
      // Una sola función que da soporte a la propiedad
      function MiraSiEsReciclable : Boolean;
      // Una sola propiedad
      property esReciclable : Boolean read MiraSiEsReciclable;
  end;

    // Definimos nuestra clase Carro
    TCarro = class(TInterfacedObject, IReciclable)
    private
        carroMarca: String;
        carroEdad: Byte;
        carroEsReciclable: Boolean;
        function MiraSiEsReciclable: Boolean; // Agregado para IReciclable
    public
        // Constructor del Carro
        constructor Create(marca: String);
    published
        // Propiedades del Carro
        property Marca: String read carroMarca;
        property Edad: Byte read carroEdad write carroEdad;
        // Agregado para IReciclable
        property esReciclable: Boolean read MiraSiEsReciclable;
    end;

    // Definimos nuestra clase Bicicleta
    TBicicleta = class(TInterfacedObject, IReciclable)
    private
        ciclaEsMacho: Boolean;
        ciclaTamRueda: Byte;
        function MiraSiEsReciclable: Boolean; // Agregado para IReciclable
    public
        // Constructor de la Bicicleta
        constructor Create(esMacho: Boolean; tamRueda: Byte);
    published
        // Propiedades de la Bicicleta
        property esMacho: Boolean read ciclaEsMacho;
        property tamRueda: Byte read ciclaTamRueda write ciclaTamRueda;
        // Agregado para IReciclable
        property esReciclable : Boolean read MiraSiEsReciclable;
    end;

  { TForm1 }

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    private 
      { private declarations }
    public
      { public declarations }
    end;

  var
    Form1: TForm1;

  implementation

  {$R *.frm}

  { TForm1 }

  procedure TForm1.FormCreate(Sender: TObject);
  var
    ciclaMama: TBicicleta;
    carroPapa: TCarro;
  begin
    // Instanciar nuestros objetos bicicleta y carro
    ciclaMama := TBicicleta.Create(false, 36);
    carroPapa := TCarro.Create('Toyota Hilux');
    // Preguntar si cada uno es reciclable
    if carroPapa.esReciclable then
        ShowMessage('El carro de papá es reciclable')
    else ShowMessage('El carro de papá no es reciclable');
    if ciclaMama.esReciclable then
        ShowMessage('La cicla de mamá es reciclable')
    else ShowMessage('La cicla de mamá no es reciclable');
  end;

  //Constructor de la implementación para la clase Carro
  constructor TCarro.Create(marca: String);
  begin
    // Guarda la marca del Carro y asigna la edad por defecto
    carroMarca := marca;
    carroEdad := 0;
    carroEsReciclable := true // Asigna que es reciclable
  end;

  //Función del Carro requerida para el atributo EsReciclable
  function TCarro.MiraSiEsReciclable: Boolean;
  begin
    Result := carroEsReciclable;
  end;

  //Constructor de la implementación para la clase bicicleta
  constructor TBicicleta.Create(esMacho: Boolean; tamRueda: Byte);
  begin
    // Guarda los parámetros que se pasaron
    ciclaEsMacho := esMacho;
    ciclaTamRueda := tamRueda;
  end;

  // Función de la bicicleta requerida para EsReciclable
  function TBicicleta.MiraSiEsReciclable: Boolean;
  begin
    // Asumiremos que sólo las bicicletas Macho son reciclables
    Result := self.esMacho;
  end;
 end.

Al ejecutar... se muestra lo siguiente:

 La función ShowMessage muestra lo siguiente:  El carro de papá es reciclable  La cicla de mamá no es reciclable


Este post fue tomado de un artículo de internet que me pareció bien interesante para modificar y entender las interfaces. Siempre ha sido un tema tabú de preguntar y cuando se usan, puede darle más claridad a tu código.

See also