Alphasite

The programmers site

Estadísticas web

Estadisticas web

Publicidad

Languages

Inicio de sesión

Google AdSense

Encuesta

En línea

En este momento hay 0 usuarios y 14 invitados en línea.

Clases abstractas e Interfaces


Metodos y clases abstractas

En general entendemos por clase totalmente abstracta cualquier clase en la que todos sus metodos son abstractos.

La abstracción de metodos es una técnica muy util para definir patrones de comportamiento de aquellas clases que hereden de la clase que estamos definiendo.

Un metodo abstracto es un metodo de una clase para el cual no se va a proporcionar implementación sino que se espera que que las clases que heredan de ella implementen dicho metodo. En delphi, si se intenta ejecutar un metodo abstracto obtendremos una excepción puesto que realmente no hay un código definido para ese metodo. El ejemplo más clasico de metodo abstracto es algo así:

type TPoligono = class
  public
    function ObtenerPerimetro : integer; virtual; abstract;
    function ObtenerArea : integer; virtual; abstract;
end;

type TRectangulo = class(TPoligono)
  private
    base, altura : integer;
  public
    function ObtenerPerimetro : integer; override;
    function ObtenerArea : integer; override;
end;

type TTriangulo = class(TPoligono)
  private
    base, lado1, lado2, altura : integer;
  public
    function ObtenerPerimetro : integer; override;
    function ObtenerArea : integer; override;
end;

type TPrueba = class
  public
    procedure LeeInfo;
end;

function TRectangulo.ObtenerPerimetro;
begin
  result := base*2 + altura*2;
end;

function TRectangulo.ObtenerArea;
begin
  result := base * altura;
end;

function TTriangulo.ObtenerPerimetro;
begin
  result := base * altura / 2;
end;

function TTriangulo.ObtenerArea;
begin
  result := base + lado1 + lado2;
end;

procedure TPrueba.LeeInfo(poligono : TPoligono);
begin
  WriteLn('Area: ' + IntToStr(poligono.ObtenerArea));
  WriteLn('Perimetro: ' + IntToStr(poligono.ObtenerPerimetro));
end;

En el código anterior, el metodo LeeInfo acepta como parametro un TPoligono o cualquier clase hija de TPoligono, sin embargo el metodo ejecutado será distinto si entra un rectangulo o si entra un triangulo. Es más, no tendrá sentido pasar un objeto de la clase TPoligono (puesto que la clase es abstracta de forma que si lo hacemos dará un error de abstracto). De esta forma hemos conseguido definir un concepto, el póligono. Triangulos, cuadrados, rectangulos son tipos de poligonos y por tanto de todos ellos se puede calcular el area y el perimetro pero sin embargo no existe algo como un poligono, nunca se va a instanciar, sirve sencillamente de modelo para otras clases.

Evidentemente este ejemplo no es el más prometedor de los ejemplos por que su utilidad es más bien limitada. Hay ejemplos más adecuados como por ejemplo la definición de origenes de datos (que iba a ser parte de este artículo pero que al final, dada su importancia y extensión se convirtió en un artículo aparte).

Interfaces

Un interface es otra forma de definir un modelo de comportamiento. Cuando hablamos de clases abstractas definimos las características básicas de un tipo de objeto, sin embargo cuando definimos interfaces estamos hablando de capacidades o habilidades de un objeto.

Veamos un ejemplo concreto que me surgió en un pequeño proyecto de DirectX. El principal objeto del proyecto es la escena. Una escena define todo aquello que existe ahora mismo en el escenario que se va a dibujar. Esto incluye distintos tipos de objetos (cubos, esferas, objetos importados) visible y otros que no lo son (puntos de viento, mágnéticos, etc).

La tarea que se encarga de renderizar la escena tiene que recorrer los objetos y dibujar cada uno de forma correcta, ahora bien, cada objeto se dibuja de distinta forma (y algunos incluso no se dibujan).

Una de las opciones que tenía era haber creado una clase padre CRenderObject de la que heredaran todos los objetos que se pueden renderizar ... pero el problema de esta solución (a parte del hecho de que la herencia se utiliza con el significado semántico A es un tipo de B ) es que solo nos sirve una vez (la mayor parte de los lenguajes de programación no soporta herencia multiple y aunque así fuera no es una técnica que personalmente me guste lo más minimo) de forma que si quiero distinguir también entre objetos que puedan verse afectados por fuerzas físicas (por ejemplo) ya no me sirve.

Para suplir esto (entre otras cosas) están los interfaces. Un interfaces no expresa un modelo de comportamiento, no es un tipo de herencia de forma que no implica "un doberman es un perro y todos los perros andan" sino, como ya he dicho, expresan una capacidad.

De esta forma me definí un interfaz llamado IRenderable que en lenguaje coloquial quiere decir, un objeto que puede Renderizarse, y otro llamado IWeighted (del ingles "que tiene peso") de forma que, los objetos que pueden dibujarse implementan el interfaz IRenderable (y definen ellos mismos como se dibujan) y aquellos que se ven afectados por las leyes físicas implementan el interfaz IWeighted (que provee una serie de propiedades físicas inherentes como el indice de rozamiento, peso, volumen ...). Por último obviamente si un objeto es dibujable y se ve afectado por la física implementa los dos interfaces.

Lo anterior en código:

interface IRenderable =
  { Renderiza el objeto dados los engines DX }
  procedure Render(DXEngineCollection engines);
end;

interface IWeighted =
  function GetWeight : Double;
  function GetFrictionCoeficient : Double;
  function GetVolume : Double;
end;

type TCube = class(TInterfacedObject, IRenderable, IWeighted)
  procedure Render(DXEngineCollection engines);
  function GetWeight : Double;
  function GetFrictionCoeficient : Double;
  function GetVolume : Double;
end;

type TBlackHole = class(TInterfacedObject, IWeighted);
  { Peso del agujero negro --> fuerza con la
    que atrae al resto de objetos de la escena }

  function GetWeight;
  { Rozamiento será infinito }
  function GetFrictionCoeficient;
  { Sin efecto --> no se mueve }
  function GetVolume : Double;
end;