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í:
{
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:
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:
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:
- Delphi recorre el uses de la unidad en cuestión y procesa cada unidad.
- 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
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.
Comentarios recientes
hace 13 semanas 6 días
hace 23 semanas 4 días
hace 45 semanas 2 días
hace 45 semanas 2 días