Pregunta
En Delphi, quiero ser capaz de crear un objeto privado que está asociado con una clase, y acceder a ella desde todas las instancias de esa clase.En Java, yo uso:
public class MyObject {
private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}
O, si MySharedObject necesitaba más complicado de inicialización, en Java me puede instanciar e inicializar en un inicializador estático bloque.
(Usted podría haber adivinado...Sé que mi Java pero soy bastante nuevo en Delphi...)
De todos modos, no quiero crear una instancia de una nueva MySharedObject cada vez que crear una instancia de MyObject, pero yo quiero un MySharedObject para ser accesibles desde cada instancia de MyObject.(En realidad es el registro que me ha impulsado a intentar averiguarlo - estoy usando Log4D y quiero guardar un TLogLogger como una variable de clase para cada clase que tiene la funcionalidad de registro.)
¿Cuál es el más bonito manera de hacer algo como esto en Delphi?
Solución
Aquí es cómo voy a hacer que el uso de una variable de clase, una clase de procedimiento 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.
Otros consejos
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 será de escritura de cualquier instancia de la clase, por lo que usted puede configurar en algún otro lugar en el código, generalmente basadas en alguna condición (tipo de registrador etc.).
Editar:También será el mismo en todos los descendientes de la clase.Cambio en uno de los niños, y cambios para todos los descendientes de los casos.También se podría configurar la instancia predeterminada de la manipulación.
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;
El año pasado, Hallvard Vassbotn escribió en su blog acerca de un Delphi-hack yo había hecho para esto, se convirtió en un artículo de dos partes:
- Hack#17:Virtual de las variables de la clase, Parte I
- Hack#17:Virtual de las variables de la clase, Parte II
Sí, es una lectura larga, pero muy gratificante.
En resumen, yo he reutilizado el (obsoleto) VMT entrada llamada vmtAutoTable como una variable.Esta ranura en el VMT puede ser utilizado para almacenar cualquier valor de 4 bytes, pero si desea almacenar, siempre se puede asignar un registro con todos los campos que usted podría desear.
Las palabras clave que usted está buscando son de "clase var" - esto inicia un bloque de variables de clase en su declaración de la clase.Usted necesita para terminar el bloque con "var", si usted desea incluir otros campos después de ella (de lo contrario, el bloque puede ser terminado por un "privado", "público", "procedimiento", etc especificador).Por ejemplo
(Edición:Volver a leer la pregunta y se trasladó recuento de referencia en TMyClass - como usted no puede ser capaz de modificar el TMySharedObjectClass clase que desea compartir, si se trata de alguien 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;
Por favor, tenga en cuenta lo anterior no es thread-safe, y puede haber mejores maneras de referencia de conteo (tales como el uso de Interfaces), pero esto es un simple ejemplo que debe empezar.Nota el TMySharedObjectClass puede ser sustituido por TLogLogger o lo que quieras.
Bueno, no es la 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 actualmente usando para construir los únicos objetos.
Para lo que yo quiero hacer (una clase privada constante), el más bonito de la solución que se me ocurre (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 serí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.
Que podría tener más sentido si hay varias constantes de clase.
Dos preguntas que creo que deben ser respondidas antes de venir para arriba con un "perfecto" solución..
- La primera, es si TLogLogger es thread-safe.Puede el mismo TLogLogger ser llamado desde varios subprocesos sin las llamadas a "sincronice"?Incluso si es así, el siguiente todavía se pueden aplicar
- Son las variables de la clase thread-en-el alcance o verdaderamente global?
- Si las variables de la clase son verdaderamente global, y TLogLogger no es seguro para subprocesos, puede ser mejor utilizar una unidad global threadvar para almacenar la TLogLogger (como mucho ya que no me gusta el uso de "global" vars 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 la clase a nivel mundial se almacena en vez de una instancia por cada subproceso.Ver esta pregunta para obtener más detalles.
En Delphi variables estáticas se implementan como tipos de variables constantes :)
Esto podría ser un poco 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 el uso de una variable global en implementation
parte de su módulo.
Esto sólo funciona si el modificador de compilador "Asignables Consts" está activada, a nivel mundial, o con {$J+}
sintaxis (tnx Lars).
Antes de la versión 7, Delphi no tienen variables estáticas, usted tendría que usar una variable global.
Para hacerlo tan privada como sea posible, ponerlo en el implementation
sección de la unidad.