Класс/Статические константы в Delphi
В Delphi я хочу создать частный объект, связанный с классом, и получить доступ к нему из всех экземпляров этого класса. В Java я бы использовал:
public class MyObject {
private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}
Или, если MySharedObject нуждался в более сложной инициализации, в Java я мог бы создать и инициализировать его в статическом блоке инициализации.
(Возможно, вы догадались... Я знаю свою Java, но я довольно новичок в Delphi...)
Во всяком случае, я не хочу создавать экземпляр нового MySharedObject каждый раз, когда создаю экземпляр MyObject, но я хочу, чтобы MySharedObject был доступен из каждого экземпляра MyObject. (Фактически это протоколирование, которое побудило меня попытаться понять это - я использую Log4D, и я хочу хранить TLogLogger как переменную класса для каждого класса, имеющего функции ведения журнала.)
Какой самый простой способ сделать что-то подобное в Delphi?
Ответы
Ответ 1
Вот как я это сделаю, используя переменную класса, процедуру класса и блок инициализации:
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.
Ответ 2
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;
Обратите внимание, что эта переменная класса будет доступна для записи из любого экземпляра класса, поэтому вы можете установить ее где-то еще в коде, как правило, на основе некоторого условия (тип регистратора и т.д.).
Изменить: он также будет таким же во всех потомках класса. Измените его у одного из детей, и он изменится для всех экземпляров потомков.
Вы также можете настроить обработку экземпляра по умолчанию.
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;
Ответ 3
Ключевыми словами, которые вы ищете, являются "класс var" - это начинает блок переменных класса в объявлении класса. Вам нужно закончить блок с помощью "var", если вы хотите включить в него другие поля (в противном случае блок может быть завершен спецификатором "private", "public", "procedure" и т.д.). Например,
(Edit: я перечитаю вопрос и переместил ссылку в TMyClass - так как вы не сможете редактировать класс TMySharedObjectClass, который хотите разделить, если он исходит от другой библиотеки)
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;
Обратите внимание, что приведенное выше не является потокобезопасным, и могут быть лучшие способы подсчета ссылок (например, использование интерфейсов), но это простой пример, который должен вас запустить. Обратите внимание, что TMySharedObjectClass можно заменить на TLogLogger или что угодно.
Ответ 4
В прошлом году, Hallvard Vassbotn blogged о Delphi-хак, который я сделал для этого, стал двухкомпонентной статьей:
Да, это долго читается, но очень полезно.
Итак, я повторно использовал (устаревшую) запись VMT, названную vmtAutoTable в качестве переменной.
Этот слот в VMT можно использовать для хранения любого 4-байтового значения, но если вы хотите сохранить, вы всегда можете выделить запись со всеми полями, которые вы могли бы пожелать.
Ответ 5
Ну, это не красота, но отлично работает в 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;
Я использую его для создания объектов с одиночными именами.
Ответ 6
Для того, что я хочу сделать (константа частного класса), самым ясным решением, которое я могу предложить (на основе ответов до сих пор), является:
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.
Возможно, более объектно-ориентированное будет выглядеть примерно так:
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 freed by Log4D.
end;
initialization
TMyObject.InitClass;
finalization
TMyObject.FreeClass;
end.
Это может иметь большее значение, если существует несколько таких констант класса.
Ответ 7
Два вопроса, на которые я думаю, должны ответить, прежде чем придумать "идеальное" решение.
- Во-первых, является ли TLogLogger потокобезопасным. Может ли тот же TLogLogger быть вызван из нескольких потоков без вызовов на "syncronize"? Даже если это так, все еще могут применяться:
- Являются ли переменные класса нитями в области видимости или действительно глобальными?
- Если переменные класса действительно глобальны, а TLogLogger не является потокобезопасным, возможно, вам лучше использовать единый глобальный threadvar для хранения TLogLogger (насколько мне не нравится использование "глобальных" варов в любой форме), например
код:
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 freed by Log4D
FLogger := threadGlobalLogger;
end;
Изменить: Кажется, что переменные класса хранятся глобально, а не экземпляр на поток. Подробнее см. этот вопрос.
Ответ 8
До версии 7 у Delphi не было статических переменных, вам нужно было бы использовать глобальную переменную.
Чтобы сделать его как можно более приватным, поместите его в раздел implementation
вашего устройства.
Ответ 9
В Delphi статические переменные реализуются как константы переменных типов:)
Это может быть несколько вводить в заблуждение.
procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
И да, другая возможность - использовать глобальную переменную в части implementation
вашего модуля.
Это работает, только если включен компилятор "Assignable Consts", глобально или с синтаксисом {$J+}
(tnx Lars).