문제

Delphi에서는 클래스와 연결된 비공개 개체를 생성하고 해당 클래스의 모든 인스턴스에서 해당 개체에 액세스할 수 있기를 원합니다.Java에서는 다음을 사용합니다.

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

또는 MySharedObject가 더 복잡한 초기화가 필요한 경우 Java에서는 정적 초기화 블록에서 인스턴스화하고 초기화할 수 있습니다.

(예상하셨겠지만...Java를 알고 있지만 Delphi는 처음 접합니다...)

어쨌든 MyObject 인스턴스를 생성할 때마다 새로운 MySharedObject를 인스턴스화하고 싶지는 않지만 MyObject의 각 인스턴스에서 MySharedObject에 액세스할 수 있기를 원합니다.(실제로 이것을 알아내려고 노력하게 된 것은 로깅입니다. 저는 Log4D를 사용하고 있으며 로깅 기능이 있는 각 클래스에 대한 클래스 변수로 TLogLogger를 저장하고 싶습니다.)

델파이에서 이와 같은 작업을 수행하는 가장 좋은 방법은 무엇입니까?

도움이 되었습니까?

해결책

다음은 클래스 변수, 클래스 프로시저 및 초기화 블록을 사용하여 이를 수행하는 방법입니다.

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;

작년에 Hallvard Vassbotn은 제가 이를 위해 만든 Delphi 해킹에 대해 블로그에 글을 올렸는데, 이 기사는 두 부분으로 구성된 기사가 되었습니다.

  1. 해킹#17:가상 클래스 변수, 1부
  2. 해킹#17:가상 클래스 변수, 2부

네, 긴 내용이지만 매우 유익했습니다.

요약하자면 vmtAutoTable이라는 (더 이상 사용되지 않는) VMT 항목을 변수로 재사용했습니다.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 모듈의 일부입니다.

이는 컴파일러 스위치 "Assignable Consts"가 전역적으로 또는 다음과 같이 켜져 있는 경우에만 작동합니다. {$J+} 구문(tnx Lars).

버전 7 이전에는 Delphi에 정적 변수가 없었으므로 전역 변수를 사용해야 했습니다.

최대한 비공개로 설정하려면 implementation 귀하의 장치 섹션.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top