Singletons en Delphi

Cuando programas en delphi y has programado en un lenguaje como C++ o C# que permiten clases estáticas llegas a echarlas un poco de menos puesto que son un recurso muy util.

¿Qué es una clase estática?

Una clase estática es, simplificando, una clase que proporciona una serie de metodos pero que no necesita ser instanciada, es decir, no necesitas crear un objeto de la clase para poder llamar a sus metodos.

En realidad en c# está separación llega al nivel de metodo, es decir, es posible definir determinados metodos de una clase como estáticos y otros no, de forma que se pueda llamar a dichos metodos sin necesidad de instanciar la clase, más o menos en C# viene a ser algo así:

public class MiClaseEstatica
{
  private int m_value;

  public MiClaseEstatica()
  {
    m_value = 0;
  }

  public int Value(){ return m_value; }

  public static int RestoDeDivisionConBucle(int numerador, int denominador)
  {
    int resultado = numerador;
    while (resultado < denominador)
    {
      resultado = resultado - denominador;
    }
    return resultado;
  }
}

De está forma la clase tiene dos funciones una de las cuales es estática y otra no, de forma que podemos hacer una invocación como:

MiClaseEstatica.RestoDeDivisionConBucle(30,4);

pero no podríamos invocar la función Value sin crear una instancia de la clase.

Clases estáticas en Delphi

Por desgracia delphi no proporciona ningún metodo implicitamente para trabajar con clases estáticas, proporciona un mecanismo similar, lo que viene a llamarse class procedure o class function que ofrecen una funcionalidad parecida pero requieren la existencia de una declaración de variable del tipo adecuado (ver código en el apendice). Sin embargo podemos simular dicha funcionalidad con un sencillo truco ayudandonos de dos secciones de una unidad de delphi, la sección de inicialización y la sección de finalización.

El truco consiste en declarar la clase normalmente, declarar una variable privada que recoja la clase, crear una función que permita el acceso a dicha clase y por último instanciar la clase en las secciones mencionadas. Veamos un ejemplo:

interface
type TMiClaseEstatica = class
  private
    m_value;
  public
    function Value : integer;
    function RestoDivisionConBucle(numerador,
                                   denominador : integer) : integer;
end;

function MiClaseEstatica : TMiClaseEstatica;

implementation

var cInstance : TMiClaseEstática;     // Variable que guarda nuestra
                                      // instancia de la clase
    refCount : integer;               // Numero de referencias




// Implementar el acceso como una función,
// (previene la sobreescritura de la instancia)
function MiClaseEstatica : TMiClaseEstatica;
begin
  if Assigned(cInstance) then
    result := cInstance
  else
    raise EInvalidPointer.Create('Referencia invalida a la clase estática');
end;

{ La implementación de los metodos de la clase vendría aqui ... }

{ ************************************************************* }

initialization
begin
  // Asegurarse de que el objeto Application está actualizado
  Application.ProcessMessages;
  // Si al llegar aqui la aplicación no esta cerrandose
  if not Application.Terminated then
  begin
    if (refCount = 0) then
    begin
      if cInstance = nil then
      begin
        // Crear la instancia si no estaba creada
        cInstance := TMiClaseEstatica.Create;    
      end;
    end;
    Inc(refCount); // Incrementar el numero de referencias
  end;
end;

finalization
begin
  // Decrementamos el numero de referencias
  Dec(RefCount);
  // Si no hay ninguna referencia liberamos la instancia
  if RefCount = 0 then
  begin
    cInstance.Free;
    cInstance := nil;
  end;
end;

Un pequeño analisis

En el código anterior, la secciones de inicialización (initialization) y de finalización (finalization) son la clave. Cuando delphi carga la aplicación principal (el dpr) recorre todas las unidades especificadas en la sentencia uses y "procesa" dichas unidades.

El proceso que delphi realiza consiste (a grosso modo) en dos pasos:

  1. Delphi recorre el uses de la unidad en cuestión y procesa cada unidad.
  2. Delphi ejecuta la seccion de inicialización de la unidad.

A efectos prácticos esto se traduce en que toda unidad que use nuestra clase ejecutará la sección de inicialización antes de ejecutar ningún otro código y por tanto habrá ya una instancia creada de la clase a la que podremos acceder.

Notas

  • '''RefCount.''' La variable refCount se utiliza para controlar que solo haya una instancia y, fundamentalmente, que dicha instancia se destruya tan solo cuando nadie vaya a necesitarla más. Tiene un comportamiento muy similar al de un interface.
  • '''Application.Terminated.''' Puede darse el caso de que al llegar a nuestra sección de inicialización alguna de las unidades que nuestra clase utiliza también tenga una sección de inicialización y en dicha sección (o en alguna de las jerarquicas por debajo) se decida terminar la aplicación (por ejemplo por que la versión del sistema operativo no es correcta o por que no se ha encontrado alguna librería). Si esto ocurre es conveniente no inicializar la instancia puesto que realmente, si la aplicación esta cerrandose, no esta garantizado que las unidades de las que depende nuestra clase estén completamente inicializadas.

Apendice I

interface

type MiClasePseudoEstatica = class
  private
    m_val : integer := 43;
  public
    class function HacerSuma(val1, val2 : integer) : integer;
end;

implementation

function MiClasePseudoEstatica.HacerSuma(val1, val2 : integer) : integer;
begin
  result := val1 + val2;
end;

procedure TMainForm.Button1Click(Sender : TObject);
var MiClase : MiClasePseudoEstatica;
begin
  ShowMessage(IntToStr(MiClase.HacerSuma(3,4)));
end;

De esta forma es necesario hacer la declaración var MiClase : MiClasePseudoEstatica para poder llamar a la función lo cual, aunque valido, es más engorroso que el metodo de clases estáticas.

6
Average: 6 (2 votes)
Your rating: None