문제
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 해킹에 대해 블로그에 글을 올렸는데, 이 기사는 두 부분으로 구성된 기사가 되었습니다.
네, 긴 내용이지만 매우 유익했습니다.
요약하자면 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
귀하의 장치 섹션.