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 (también he visto por internet llamarlas Singletons).
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 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:
pero no podríamos invocar la función Value sin crear una instancia de la clase.
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:
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;
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:
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.
type MiClasePseudoEstatica = class
private
m_val : integer := 43;
public
class function GetValue : integer;
end;
implementation
function MiClasePseudoEstatica.GetValue : integer;
begin
result := m_val;
end;
procedure TMainForm.Button1Click(Sender : TObject);
var MiClase : MiClasePseudoEstatica;
begin
ShowMessage(IntToStr(MiClase.GetValue));
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.