Класс / Статические константы в Delphi

StackOverflow https://stackoverflow.com/questions/71766

  •  09-06-2019
  •  | 
  •  

Вопрос

В Delphi я хочу иметь возможность создавать закрытый объект, связанный с классом, и получать к нему доступ из всех экземпляров этого класса.В Java я бы использовал:

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

Или, если MySharedObject нуждался в более сложной инициализации, в Java я мог бы создать его экземпляр и инициализировать в блоке статического инициализатора.

(Вы могли бы и сами догадаться...Я знаю свою Java, но я довольно новичок в Delphi ...)

В любом случае, я не хочу создавать экземпляр нового MySharedObject каждый раз, когда я создаю экземпляр MyObject, но я хочу, чтобы MySharedObject был доступен из каждого экземпляра MyObject.(На самом деле именно ведение журнала побудило меня попытаться разобраться в этом - я использую Log4D и хочу сохранить TLogLogger в качестве переменной класса для каждого класса, у которого есть функция ведения журнала.)

Какой самый аккуратный способ сделать что-то подобное в Delphi?

Это было полезно?

Решение

Вот как я это сделаю, используя переменную класса, процедуру класса и блок инициализации:

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.

Другие советы

 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;

В прошлом году Халвард Вассботн написал в блоге о взломе Delphi, который я сделал для этого, это была статья из двух частей:

  1. Взлом №17:Переменные виртуального класса, Часть I
  2. Взлом №17:Переменные виртуального класса, Часть II

Да, это долгое чтение, но очень полезное.

Таким образом, я повторно использовал (устаревшую) запись VMT под названием vmtAutoTable в качестве переменной.Этот слот в VMT можно использовать для хранения любого 4-байтового значения, но если вы хотите сохранить, вы всегда можете выделить запись со всеми полями, которые вы пожелаете.

Ключевыми словами, которые вы ищете, являются "class var" - это запускает блок переменных класса в вашем объявлении класса.Вам необходимо завершить блок "var", если вы хотите включить после него другие поля (в противном случае блок может заканчиваться спецификатором "private", "public", "procedure" и т.д.).Например

(Редактировать:Я перечитал вопрос и переместил количество ссылок в 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 или как вам угодно.

Ну, это не красота, но прекрасно работает в 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;

В настоящее время я использую его для создания одиночных объектов.

Для того, что я хочу сделать (константа частного класса), самым опрятным решением, которое я могу придумать (на основе ответов на данный момент), является:

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's freed by Log4D.
end;

initialization
  TMyObject.InitClass;
finalization
  TMyObject.FreeClass;

end.

Это могло бы иметь больше смысла, если бы существовало несколько таких констант класса.

Я думаю, что необходимо ответить на два вопроса, прежде чем вы придете к "идеальному" решению..

  • Первый - является ли TLogLogger потокобезопасным.Может ли один и тот же TLogLogger вызываться из нескольких потоков без призывает к "синхронизации"?Даже если это так, все равно может применяться следующее
  • Являются ли переменные класса потоковыми в области видимости или действительно глобальными?
  • Если переменные класса действительно глобальные, а 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's freed by Log4D
  FLogger := threadGlobalLogger;
end;

Редактировать:Похоже, что переменные класса хранятся глобально, а не по экземпляру для каждого потока.Видишь этот вопрос за подробностями.

В Delphi статические переменные реализованы следующим образом переменные типы констант :)

Это может ввести в заблуждение.

procedure TForm1.Button1Click(Sender: TObject) ;
const
   clicks : Integer = 1; //not a true constant
begin
  Form1.Caption := IntToStr(clicks) ;
  clicks := clicks + 1;
end;

И да, еще одна возможность - использовать глобальную переменную в implementation часть вашего модуля.

Это работает только в том случае, если переключатель компилятора "Назначаемые константы" включен глобально или с {$J+} синтаксис (tnx Lars).

До версии 7 в Delphi не было статических переменных, вам пришлось бы использовать глобальную переменную.

Чтобы сделать его как можно более приватным, поместите его в implementation секция вашего подразделения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top