reynosa - delphi technologies mexico
Constantes de clase/estáticas en Delphi (9)
Aquí es cómo lo haré usando una variable de clase, un procedimiento de clase y un bloque de inicialización:
unit MyObject;
interface
type
TMyObject = class
private
class var FLogger : TLogLogger;
public
class procedure SetLogger(value:TLogLogger);
class procedure FreeLogger;
end;
implementation
class procedure TMyObject.SetLogger(value:TLogLogger);
begin
// sanity checks here
FLogger := Value;
end;
class procedure TMyObject.FreeLogger;
begin
if assigned(FLogger) then
FLogger.Free;
end;
initialization
TMyObject.SetLogger(TLogLogger.Create);
finalization
TMyObject.FreeLogger;
end.
En Delphi, quiero poder crear un objeto privado asociado a una clase y acceder desde todas las instancias de esa clase. En Java, usaría:
public class MyObject {
private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}
O, si MySharedObject necesitaba una inicialización más complicada, en Java podría instanciarla e inicializarla en un bloque de inicializador estático.
(Tal vez hayas adivinado ... Conozco mi Java pero soy bastante nuevo para Delphi ...)
De todos modos, no quiero instanciar un nuevo MySharedObject cada vez que creo una instancia de MyObject, pero sí quiero que se pueda acceder a MySharedObject desde cada instancia de MyObject. (En realidad, el registro me ha impulsado a tratar de resolver esto; estoy usando Log4D y quiero almacenar un TLogLogger como una variable de clase para cada clase que tenga funcionalidad de registro).
¿Cuál es la mejor forma de hacer algo como esto en Delphi?
El año pasado, Hallvard Vassbotn blogueó sobre un Delphi-hack que había hecho para esto, se convirtió en un artículo de dos partes:
Sí, es una lectura larga, pero muy gratificante.
En resumen, he reutilizado la entrada VMT (en desuso) llamada vmtAutoTable como variable. Esta ranura en el VMT se puede usar para almacenar cualquier valor de 4 bytes, pero si desea almacenar, siempre puede asignar un registro con todos los campos que desee.
En Delphi, las variables estáticas se implementan como constantes de tipos variables :)
Esto podría ser algo engañoso.
procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
Y sí, otra posibilidad es usar la variable global en la parte de implementation
de su módulo.
Esto solo funciona si el conmutador del compilador "Consts asignables" está activado, globalmente o con sintaxis de {$J+}
(tnx Lars).
Las palabras clave que está buscando son "clase var": esto inicia un bloque de variables de clase en su declaración de clase. Debe finalizar el bloque con "var" si desea incluir otros campos después de él (de lo contrario, el bloque puede terminar con un especificador "privado", "público", "procedimiento", etc.). P.ej
(Editar: volví a leer la pregunta y moví el recuento de referencias a TMyClass, ya que es posible que no puedas editar la clase TMySharedObjectClass que deseas compartir, si proviene de la biblioteca de otra persona)
TMyClass = class(TObject)
strict private
class var
FMySharedObjectRefCount: integer;
FMySharedObject: TMySharedObjectClass;
var
FOtherNonClassField1: integer;
function GetMySharedObject: TMySharedObjectClass;
public
constructor Create;
destructor Destroy; override;
property MySharedObject: TMySharedObjectClass read GetMySharedObject;
end;
{ TMyClass }
constructor TMyClass.Create;
begin
if not Assigned(FMySharedObject) then
FMySharedObject := TMySharedObjectClass.Create;
Inc(FMySharedObjectRefCount);
end;
destructor TMyClass.Destroy;
begin
Dec(FMySharedObjectRefCount);
if (FMySharedObjectRefCount < 1) then
FreeAndNil(FMySharedObject);
inherited;
end;
function TMyClass.GetMySharedObject: TMySharedObjectClass;
begin
Result := FMySharedObject;
end;
Tenga en cuenta que lo anterior no es seguro para subprocesos, y puede haber mejores formas de contar referencias (como el uso de interfaces), pero este es un ejemplo simple que debe comenzar. Tenga en cuenta que TMySharedObjectClass puede ser reemplazado por TLogLogger o lo que quiera.
Por lo que quiero hacer (una constante de clase privada), la mejor solución que puedo encontrar (basada en las respuestas hasta ahora) es:
unit MyObject;
interface
type
TMyObject = class
private
class var FLogger: TLogLogger;
end;
implementation
initialization
TMyObject.FLogger:= TLogLogger.GetLogger(TMyObject);
finalization
// You''d typically want to free the class objects in the finalization block, but
// TLogLoggers are actually managed by Log4D.
end.
Tal vez un poco más orientado a objetos sería algo así como:
unit MyObject;
interface
type
TMyObject = class
strict private
class var FLogger: TLogLogger;
private
class procedure InitClass;
class procedure FreeClass;
end;
implementation
class procedure TMyObject.InitClass;
begin
FLogger:= TLogLogger.GetLogger(TMyObject);
end;
class procedure TMyObject.FreeClass;
begin
// Nothing to do here for a TLogLogger - it''s freed by Log4D.
end;
initialization
TMyObject.InitClass;
finalization
TMyObject.FreeClass;
end.
Eso podría tener más sentido si hubiera múltiples constantes de clase.
TMyObject = class
private
class var FLogger : TLogLogger;
procedure SetLogger(value:TLogLogger);
property Logger : TLogLogger read FLogger write SetLogger;
end;
procedure TMyObject.SetLogger(value:TLogLogger);
begin
// sanity checks here
FLogger := Value;
end;
Tenga en cuenta que esta variable de clase se podrá escribir desde cualquier instancia de clase, por lo tanto, puede configurarla en otro lugar del código, generalmente en función de alguna condición (tipo de registrador, etc.).
Editar: También será el mismo en todos los descendientes de la clase. Cámbielo en uno de los elementos secundarios y cambie para todas las instancias descendientes. También puede configurar el manejo de instancias predeterminado.
TMyObject = class
private
class var FLogger : TLogLogger;
procedure SetLogger(value:TLogLogger);
function GetLogger:TLogLogger;
property Logger : TLogLogger read GetLogger write SetLogger;
end;
function TMyObject.GetLogger:TLogLogger;
begin
if not Assigned(FLogger)
then FLogger := TSomeLogLoggerClass.Create;
Result := FLogger;
end;
procedure TMyObject.SetLogger(value:TLogLogger);
begin
// sanity checks here
FLogger := Value;
end;
Antes de la versión 7, Delphi no tenía variables estáticas, tendrías que usar una variable global.
Para que sea lo más privado posible, colóquelo en la sección de implementation
de su unidad.
Bueno, no es belleza, pero funciona bien en Delphi 7:
TMyObject = class
pulic
class function MySharedObject: TMySharedObject; // I''m lazy so it will be read only
end;
implementation
...
class function MySharedObject: TMySharedObject;
{$J+} const MySharedObjectInstance: TMySharedObject = nil; {$J-} // {$J+} Makes the consts writable
begin
// any conditional initialization ...
if (not Assigned(MySharedObjectInstance)) then
MySharedObjectInstance = TMySharedOject.Create(...);
Result := MySharedObjectInstance;
end;
Lo estoy utilizando para construir objetos únicos.
Dos preguntas que creo que deben ser respondidas antes de llegar a una solución "perfecta".
- El primero es si TLogLogger es seguro para subprocesos. ¿Se puede llamar al mismo TLogLogger desde múltiples hilos sin llamadas a "syncronize"? Incluso si es así, lo siguiente puede aplicarse
- ¿Las variables de clase son intrínsecas o verdaderamente globales?
- Si las variables de clase son verdaderamente globales, y TLogLogger no es seguro para subprocesos, puede que sea mejor usar un threadvar unit-global para almacenar el TLogLogger (tanto como no me gusta usar vars "globales" en cualquier forma), por ejemplo
Código:
interface
type
TMyObject = class(TObject)
private
FLogger: TLogLogger; //NB: pointer to shared threadvar
public
constructor Create;
end;
implementation
threadvar threadGlobalLogger: TLogLogger = nil;
constructor TMyObject.Create;
begin
if not Assigned(threadGlobalLogger) then
threadGlobalLogger := TLogLogger.GetLogger(TMyObject); //NB: No need to reference count or explicitly free, as it''s freed by Log4D
FLogger := threadGlobalLogger;
end;
Editar: parece que las variables de clase están almacenadas globalmente, en lugar de una instancia por subproceso. Vea esta pregunta para más detalles.