Abstract classes and interfaces

Abstract classes and methods

Generally we understand a pure abstract class as any class having only abstract methods.

Method abstraction is a very useful technique to define behavior patterns over the classes that are going to inherit from a given class we are defining.

An abstract method is a class method which won't be implemented but is expected to be implemented by the descendants of the class. In delphi, if you try to call that method you'll get a runtime exception as there isn't really any code there that can be called. A basic example of an abstract method and its used could be:

type TPolygon= class
  public
    function GetPerimeter: integer; virtual; abstract;
    function GetArea: integer; virtual; abstract;
end;

type TRectangle = class(TPolygon)
  private
    base, altura : integer;
  public
    function GetPerimeter: integer; override;
    function GetArea: integer; override;
end;

type TTriangle= class(TPolygon)
  private
    base, lado1, lado2, altura : integer;
  public
    function GetPerimeter: integer; override;
    function GetArea : integer; override;
end;

type TTest = class
  public
    procedure ReadInfo;
end;

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

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

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

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

procedure TTest.ReadInfo(polygon: TPolygon);
begin
  WriteLn('Area: ' + IntToStr(polygon.GetArea));
  WriteLn('Perimeter: ' + IntToStr(polygon.GetPerimeter));
end;

In that code, the ReadInfo method takes a TPolygon parameter or any descendant from TPolygon, nontheless the real executed method will be different if the method is passed a rectangle or a triangle. Really it wouldn't even make sense to use an instance of TPolygon, firstly because the class is pure abstract and thus it would raise an Abstract error on runtime and secondly becaus it just makes no sense to calculate the area of a "generic" polygon. That way we've managed to define a concept, the polygon. Triangles, squares or rectangles are types of polygons and thus for each of them we can get an area and a perimeter value but there are no object of the "polygon" type (and therefore it shouldn't have any instance).

Obviously this is not the mos illustrating example because of its more than limited usefulness. There are other patterns which fit better such as abstract+data+sources.

Interfaces

An interface is a way of defining a contract. When we talk about abstract classes we are defining characteristics of an object type, specifying what an object is but in the case of an interface we define a capability and we bond to provide that capability, we are talking about establishing a contract about what the object can do.

Let's see a more specific example that I used in a little DirectX project. The main object of the project is the scene. A scene defines everything that exists in the scenario which is being drawn. That includes different types of objects (cubes, spheres, imported meshes, particles systems) which are visible and some other which are not (wind points, magnetic forces, gravity, etc.)

The thread which is in charge of rendering the scene has to go through the objects and draw everyone of them accordingly. Now, every object gets drawn in a different way, and some of them don't even get rendered so... how do we solve this?

One of the options is to create a CRenderObject class from which every renderable object would inherit, but the problem with that solution (aside from the fact that inheritance should usually be used to mean A is a kind of B) is that most programming languages do not support multiple inheritance (and for those that do support it, it's usually considered a bad practice) so that if I want to distinguish between object that can be affected by physical force (for example) the inheritance becomes complicated and even useless to describe the problem.

Interfaces express, as I've already mentioned, a capability. They do not express something like "a Doberman is a type of dog and every dog can walk" but more like "this thing can walk"

Or said in another way, the interface defines a contract so that if a class implements an interface then it guarantees it will fulfill that contract.

That way I defined an interface called "IRenderable", that is, that can be rendered, and another one called IWeighted, that is, that have weight, so that objects that can be drawn in the screen implement the IRenderable interface while those that can be affected by physics laws implement the IWeighted interface. Contrary to what happens with inheritance, a class can implement as many interfaces as it wants, after all is just accepting a contract, so, in that way, if an object is renderable and can be affected by physics then it must implement both interfaces.

Let's see that in code

interface IRenderable =
  { Render the object in the supplied DXEngines }
  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;

That way our two interfaces specify that, if we want to be able to be renderized we must implement a Render method, accepting the specified parameters. That way if my rendering process can get the list of objects that compose the scene, for each of those object, it could verify if the object is renderable and, if it is, render it without having to worry about specific class details.

procedure DoRender;
var
  i : integer;
  iRenderIface : IRenderable;
begin
  for i := 0 to FListaObjetos.Count - 1 do
  begin
    // Comprobar si el objeto soporta el interfaz
    if Supports(FListaObjectos[i], IRenderable, iRenderIface) then
       IRenderIface.Render(FEngines);
  end;
end;

When to use abstract classes and when to use interfaces

Decission about when to use what is being arround for long and is one of those discussions with a lot of people having its opinion or backing up this or that idea. I think there's a basic rule that works almost everytime: Use abstract clases and inheritance if you can make the statement "A is a B". Use interfaces if you can make the statement "A is capable of [doing] as", or also, abstract for what a class is, interface for what a class can do.

Por example, we can say a triangle is a polygon but it makes no sense to say a triangle is capable of being a polygon.

Anyway, as ever, the rule of thumb for this is: use your common sense. Sometimes an interface just fit much better, even if the above rule tells you the contrary, if that's it just use the interface (after considering consequences of course).

7.18254
Average: 7.2 (126 votes)
Your rating: None