technologies reynosa parts mexico empresa autopartes automotive aptiv delphi

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:

  1. Hack # 17: Variables de clases virtuales, Parte I
  2. Hack # 17: Variables de clase virtual, Parte II

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.