2008-09-16 14 views
14

En Delphi, quiero poder crear un objeto privado asociado a una clase y acceder desde todas las instancias de esa clase. En Java, que haría uso:Constantes de clase/estáticas en Delphi

public class MyObject { 
    private static final MySharedObject mySharedObjectInstance = new MySharedObject(); 
} 

O, si MySharedObject necesita más complicada de inicialización, en Java que pude crear una instancia e inicializar en un bloque inicializador estático.

(Es posible que haya adivinado ... Sé que mi Java, pero estoy bastante nuevo en Delphi ...)

De todos modos, no quiero crear una instancia de un nuevo MySharedObject cada vez que crear una instancia de MyObject, pero 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 manera de hacerlo? algo como esto en Delphi?

+0

Tenga en cuenta que las variables de clase no están disponibles antes de Delphi 7. –

Respuesta

15

Aquí es cómo voy a hacer que el uso de una variable de clase, un procedimiento de la 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. 
+0

Si necesita un campo privado, necesitará el procedimiento SetLogger. Estoy de acuerdo con el comentario del procedimiento de finalización. –

+1

Creo que debería ser posible establecer un campo privado desde el inicializador, ya que se puede acceder a los campos privados desde la unidad. Tiene que ser "estrictamente privado" para que sea accesible solo desde la clase. –

0

Antes de la versión 7, Delphi no tenía variables estáticas, tendría que usar una variable global.

Para que sea lo más privado posible, colóquelo en la sección implementation de su unidad.

+0

Esto es cierto hasta Delphi 7. Hay variables de clase (ver otras respuestas) para versiones superiores. –

0

En Delphi variables estáticas se implementan como tipos de variables constantes de :)

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 implementation parte de su módulo.

Esto solo funciona si el interruptor del compilador "Consts asignables" está activado, globalmente o con la sintaxis {$J+} (tnx Lars).

+0

Esto solo funciona si el compilador cambia AssignableConsts está activado –

4
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 clase será modificable desde cualquier instancia de clase, por lo tanto, se puede establecer en otro lugar en el código, por lo general sobre la base 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 podría 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; 
+0

¿Podría tener un problema con la seguridad de subprocesos? ¿Podría el subprocesamiento múltiple llevar a una instancia de TMyObject tratando de usar un FLogger que no se ha inicializado por completo? –

+0

En teoría, sí. Agregar una sección crítica ayudaría, pero eso tiene un precio. Lo mejor sería un método de clase, como en el ejemplo de la sección init, combinado con una afirmación en la rutina get. Es posible que incluso desee una afirmación en el colocador para evitar múltiples setters. YMMV. –

2

Las palabras clave que busca 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.). Por ejemplo

(Edit: volver a leer la pregunta y se trasladó recuento de referencia en TMyClass - ya que no puede ser capaz de modificar la clase TMySharedObjectClass que desee compartir, si se trata de otra persona de la biblioteca)

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.

+1

Veo que algo así podría tener sentido si compartía el objeto fuera de la clase, pero es un código de andamiaje para un objeto que solo se comparte entre las instancias de la clase. –

2

El año pasado, Hallvard Vassbotn escribió en su blog acerca de un Delphi-hack que había hecho para esto, se convirtió en un artículo de dos partes:

  1. Hack#17: Virtual class variables, Part I
  2. Hack#17: Virtual class variables, Part 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.

+1

Utilizo este truco para mapear el RTTI para los objetos de nuestro ORM de código abierto. Es muy rápido y conveniente. Ver http://blog.synopse.info/post/2011/06/07/True-per-class-variable –

1

Por lo que yo quiero hacer (una constante de clase privada), la solución más elegante que puedo llegar a (basado 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 habría algo 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.

+0

Hay algunas desventajas con poner cosas en la sección init: incluso si no usas la clase, se vinculará mientras uses la unidad. Además, el orden de inicio puede no ser siempre lo que piensas que es, y cambiará a medida que cambies la posición de las unidades en la cláusula de usos. –

+0

¡Ew, eso suena un poco feo! ¿Es posible que se cree una instancia de una clase antes de que se ejecute la sección de inicialización de la unidad? En otras palabras, ¿podría una instancia de TMyObject tratar de usar FLogger antes de que se haya establecido en la sección de inicialización? –

+0

@MB: no, eso no es posible: antes de crear cualquier objeto TMyObject o incluso los métodos de clase, la unidad que define TMyObject se habría incluido, y es en ese punto donde se ejecuta la sección de inicialización. No se puede usar una clase en ninguna unidad antes de que se haya ejecutado la sección de inicialización de esa unidad – Graza

1

Dos preguntas que creo que necesitan ser respondidas antes de llegar a una solución "perfecta" ..

  • La primera, 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í, puede aplicarse lo siguiente:
  • ¿Las variables de clase tienen un alcance intrínseco o son realmente 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" de ninguna 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 se almacenan a nivel mundial, en lugar de una instancia por hilo. Ver this question para más detalles.

+0

No tengo idea de por qué el formato del código no funciona ... – Graza

+0

threadvar - cool, no sabía que pudieras hacer eso en Delphi. Iba a suponer que una variable de clase solo tendría más sentido que una variable de instancia * if * el objeto (por ejemplo, TLogLogger) era seguro para subprocesos. Pero parece que threadvar ofrece una opción intermedia. –

+0

Todavía me pregunto si las variables de Clase son globales o tienen un alcance. Mi suposición es global, pero tal vez debería agregar una nueva pregunta ... – Graza

2

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; 

estoy Curently usarlo para construir objetos singletons.