16

Delphi では、クラスに関連付けられたプライベート オブジェクトを作成し、そのクラスのすべてのインスタンスからアクセスできるようにしたいと考えています。Javaでは、次を使用します。

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

または、MySharedObject でより複雑な初期化が必要な場合は、Java で静的初期化ブロックでインスタンス化して初期化できます。

(ご想像のとおりです... Java は知っていますが、Delphi は初めてです...)

とにかく、MyObject のインスタンスを作成するたびに新しい MySharedObject をインスタンス化したくはありませんが、MyObject の各インスタンスから MySharedObject にアクセスできるようにしたいと考えています。(これを理解しようと思ったのは、実際にはログです。私は Log4D を使用しており、ログ機能を持つ各クラスのクラス変数として TLogLogger を保存したいと考えています。)

Delphi でこのようなことを行う最も適切な方法は何ですか?

4

9 に答える 9

18

クラス変数、クラス プロシージャ、および初期化ブロックを使用してこれを行う方法を次に示します。

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.
于 2008-09-16T13:14:43.997 に答える
5

昨年、Hallvard Vassbotn が、私がこのために作成した Delphi ハックについてブログを書きました。それは 2 部構成の記事になりました。

  1. Hack#17: 仮想クラス変数、パート I
  2. Hack#17: 仮想クラス変数、パート II

ええ、それは長い間読んでいますが、非常にやりがいがあります。

要約すると、vmtAutoTable と呼ばれる (非推奨の) VMT エントリを変数として再利用しました。VMT のこのスロットは、任意の 4 バイト値を格納するために使用できますが、格納したい場合は、必要なすべてのフィールドを含むレコードをいつでも割り当てることができます。

于 2008-09-16T13:54:29.937 に答える
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;

このクラス変数は、任意のクラス インスタンスから書き込み可能であることに注意してください。したがって、通常は何らかの条件 (ロガーのタイプなど) に基づいて、コード内の別の場所に設定できます。

編集:クラスのすべての子孫でも同じになります。子の 1 つで変更すると、すべての子孫インスタンスで変更されます。デフォルトのインスタンス処理を設定することもできます。

 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;
于 2008-09-16T12:55:21.733 に答える
2

まあ、それは美しさではありませんが、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;

私は現在、それを使用してシングルトン オブジェクトを構築しています。

于 2009-06-24T13:13:10.200 に答える
2

探しているキーワードは「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 などで置き換えることができることに注意してください。

于 2008-09-16T13:07:08.320 に答える
1

私がやりたいこと(プライベートクラスの定数)について、(これまでの回答に基づいて)思いつくことができる最もきちんとした解決策は次のとおりです。

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.

そのようなクラス定数が複数ある場合、それはより理にかなっているかもしれません。

于 2008-09-16T15:20:38.950 に答える
1

「完璧な」解決策を見つける前に、2 つの質問に答える必要があると思います。

  • 1 つ目は、TLogLogger がスレッドセーフかどうかです。「同期化」を呼び出さずに、複数のスレッドから同じ TLogLogger を呼び出すことはできますか? その場合でも、次のことが当てはまる場合があります。
  • クラス変数はスレッド内スコープですか、それとも真にグローバルですか?
  • クラス変数が真にグローバルで、TLogLogger がスレッド セーフでない場合は、ユニット グローバル スレッド変数を使用して 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;

編集:スレッドごとのインスタンスではなく、クラス変数がグローバルに保存されているようです。詳細については、この質問を参照してください。

于 2008-09-17T09:23:48.770 に答える
0

バージョン 7 より前の Delphi には静的変数がなく、グローバル変数を使用する必要がありました。

できるだけ非公開にするためにimplementation、ユニットのセクションに入れます。

于 2008-09-16T12:45:30.120 に答える
0

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+}これは、コンパイラ スイッチ "Assignable Consts" がグローバルまたは構文 (tnx Lars)でオンになっている場合にのみ機能します。

于 2008-09-16T12:48:38.100 に答える