Создание экземпляра реализации интерфейса во время выполнения
Во-первых, небольшое объяснение моей ситуации:
У меня есть образец интерфейса, который реализуется разными классами, и эти классы могут не всегда иметь общего предка:
IMyInterface = interface
['{1BD8F7E3-2C8B-4138-841B-28686708DA4D}']
procedure DoSomething;
end;
TMyImpl = class(TInterfacedPersistent, IMyInterface)
procedure DoSomething;
end;
TMyImp2 = class(TInterfacedObject, IMyInterface)
procedure DoSomething;
end;
У меня также есть метод factory, который должен создать экземпляр объекта, который реализует мой интерфейс. Мой метод factory получает имя класса в качестве его параметра:
function GetImplementation(const AClassName: string): IMyInterface;
Я попробовал два подхода для реализации этого метода factory, первый использовал расширенный RTTI:
var
ctx : TRttiContext;
t : TRttiInstanceType;
begin
t := ctx.FindType(AClassName).AsInstance;
if Assigned(t) then
Result := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsInterface as IMyInterface;
end;
В этом подходе я вызываю конструктор по умолчанию, который хорош в моем сценарии. Проблема в том, что во время выполнения я получаю сообщение об ошибке, указывающее, что объект не поддерживает IMyInterface. Более того, созданный объект не назначается переменной интерфейса; поэтому он будет просочиться. Я также попытался вернуть значение с помощью метода TValue.AsType, но он дает мне нарушение доступа:
function GetImplementation(const AClassName: string): IMyInterface;
var
ctx : TRttiContext;
rt : TRttiInstanceType;
V : TValue;
begin
rt := ctx.FindType(AClassName).AsInstance;
if Assigned(rt) then
begin
V := rt.GetMethod('Create').Invoke(rt.MetaclassType, []);
Result := V.AsType<IMyInterface>;
end;
end;
.
Второй подход, который я пробовал, заключался в использовании общего словаря для хранения пар и предоставления методов регистрации, отмены регистрации:
TRepository = class
private
FDictionary : TDictionary<string, TClass>;
public
constructor Create;
destructor Destroy; override;
function GetImplementation(const AClassName: string): IMyInterface;
procedure RegisterClass(AClass: TClass);
procedure UnregisterClass(AClass: TClass);
end;
Здесь я реализовал метод GetImplementation следующим образом:
function TRepository.GetImplementation(const AClassName: string): IMyInterface;
var
Obj : TObject;
begin
if FDictionary.ContainsKey(AClassName) then
begin
Obj := FDictionary[AClassName].Create;
Obj.GetInterface(IMyInterface, Result);
end;
end;
Это отлично работает, и я могу вызвать метод DoSomething, используя возвращаемое значение GetImplementation, но у него все еще есть проблема с утечкой памяти; Obj, который создается здесь, не привязан к какой-либо интерфейсной переменной; поэтому он не засчитывается и не протекает.
.
Теперь, мой фактический вопрос:
Итак, мой вопрос: как я могу безопасно создать экземпляр класса, который реализует мой интерфейс во время выполнения? Я видел Delphi Spring Framework, и он предоставляет такую функциональность в своем модуле Spring.Services, но у него есть свои собственные процедуры анализа и модели управления временем жизни. Я ищу легкое решение, а не целую стороннюю структуру, чтобы сделать это для меня.
Привет
Ответы
Ответ 1
Первый случай использования RTTI дает вам нарушение прав доступа, поскольку TRttiContext.FindType(AClassName)
не может найти информацию Rtti для классов, которые не зарегистрированы или не используются в приложении.
Итак, вы можете изменить свой код на
function GetImplementation(AClass: TClass): IMyInterface;
var
ctx : TRttiContext;
t : TRttiInstanceType;
begin
t := ctx.GetType(AClass).AsInstance;
if Assigned(t) then
Result := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsInterface As IMyInterface;
end;
и вызовите таким образом
AClass:=GetImplementation(TMyImp2);
Теперь, если вы хотите использовать имя класса для вызова класса, использование списка (например, вашего класса TRedository) для регистрации классов является действительным aproach. об утечке памяти. Я уверен, что вызвано тем, что класс TMyImpl
получен из TInterfacedPersistent
, который не выполняет подсчет ссылок напрямую, как TInterfacedObject
.
Эта реализация TRepository
должна работать нормально.
constructor TRepository.Create;
begin
FDictionary:=TDictionary<string,TClass>.Create;
end;
destructor TRepository.Destroy;
begin
FDictionary.Free;
inherited;
end;
function TRepository.GetImplementation(const AClassName: string): IMyInterface;
var
Obj : TObject;
begin
if FDictionary.ContainsKey(AClassName) then
begin
Obj := FDictionary[AClassName].Create;
Obj.GetInterface(IMyInterface, Result);
end;
end;
{
or using the RTTI
var
ctx : TRttiContext;
t : TRttiInstanceType;
begin
t := ctx.GetType(FDictionary[AClassName]).AsInstance;
if Assigned(t) then
Result := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsInterface As IMyInterface;
end;
}
procedure TRepository.RegisterClass(AClass: TClass);
begin
FDictionary.Add(AClass.ClassName,AClass);
end;
procedure TRepository.UnregisterClass(AClass: TClass);
begin
FDictionary.Remove(AClass.ClassName);
end;
Ответ 2
Я думаю, что я бы выбрал второй вариант, главным образом потому, что я предпочитаю избегать RTTI, если это не единственное возможное решение проблемы.
Но в обоих предлагаемых вами вариантах вы указываете, что
создаваемый здесь объект не привязан к какой-либо интерфейсной переменной
Это просто неправда. В обоих случаях вы назначаете Result
, который имеет тип IMyInterface
. Если у вас есть утечка памяти, это вызвано каким-то другим кодом, а не этим кодом.
И @RRUZ обнаружил причину утечки - а именно, используя TInterfacedPersistent
, который не реализует ссылку на подсчет времени жизни. Ваш код не будет протекать для TInterfacedObject
.
Для чего это стоит, я бы назначил непосредственно интерфейсную переменную, а не через ссылку на объект, но это только вопрос стилистических предпочтений.
if FDictionary.TryGetValue(AClassName, MyClass) then
Result := MyClass.Create as IMyInterface;
Ответ 3
Вы можете сделать это с помощью расширенного метода RTTI и TObject GetInterface
:
function GetImplementation(const AClassName: string): IMyInterface;
var
ctx: TRttiContext;
t : TRttiInstanceType;
obj: TObject;
begin
Result := nil;
t := ctx.FindType(AClassName).AsInstance;
if Assigned(t) then begin
obj := t.GetMethod('Create').Invoke(t.MetaclassType, []).AsObject;
obj.GetInterface(IMyInterface, Result)
end;
end;
Он не будет работать, если объект переопределяет QueryInterface
для выполнения пользовательской обработки, но оба метода TInterfacedPersistent и TInterfacedObject полагаются на GetInterface.