Лучший способ реализовать Clone() в моих конкретных классах
В моем конкретном TPersistent
классы, которые я хотел бы предоставить Clone
функция, которая возвращает независимую копию объекта.
Можно ли сделать эту работу правильно с потомками, не реализуя Clone
функция в каждом потомке?
Это не клонирование каких-либо неизвестных полей или глубокое клонирование (что может быть сделано с использованием RTTI). В моем минимальном примере ниже, вы можете увидеть, где я хотел бы поместить Clone
функция.
Так как он использует Assign()
чтобы скопировать данные, он будет работать с любым потомком. Проблема в конструкторе, см. Комментарии. Как я могу назвать правильный конструктор этого потомка? Если это очень трудно сделать, то можно предположить, что ни один из потомков не переопределяет конструктор без переопределения Clone
, тоже.
program Test;
uses System.SysUtils, System.Classes;
type
TMyClassBase = class abstract(TPersistent)
public
constructor Create; virtual; abstract;
function Clone: TMyClassBase; virtual; abstract;
end;
TMyClassBase<T> = class abstract(TMyClassBase)
private
FValue: T;
public
constructor Create; overload; override;
function Clone: TMyClassBase; override;
procedure Assign(Source: TPersistent); override;
property Value: T read FValue write FValue;
end;
TMyClassInt = class(TMyClassBase<Integer>)
public
function ToString: string; override;
end;
TMyClassStr = class(TMyClassBase<string>)
public
function ToString: string; override;
end;
constructor TMyClassBase<T>.Create;
begin
Writeln('some necessary initialization');
end;
procedure TMyClassBase<T>.Assign(Source: TPersistent);
begin
if Source is TMyClassBase<T> then FValue:= (Source as TMyClassBase<T>).FValue
else inherited;
end;
function TMyClassBase<T>.Clone: TMyClassBase;
begin
{the following works, but it calls TObject.Create!}
Result:= ClassType.Create as TMyClassBase<T>;
Result.Assign(Self);
end;
function TMyClassInt.ToString: string;
begin
Result:= FValue.ToString;
end;
function TMyClassStr.ToString: string;
begin
Result:= FValue;
end;
var
ObjInt: TMyClassInt;
ObjBase: TMyClassBase;
begin
ObjInt:= TMyClassInt.Create;
ObjInt.Value:= 42;
ObjBase:= ObjInt.Clone;
Writeln(ObjBase.ToString);
Readln;
ObjInt.Free;
ObjBase.Free;
end.
Выход
some necessary initialization
42
Итак, правильный класс вышел, он работает правильно в этом минимальном примере, но, к сожалению, моя необходимая инициализация не была выполнена (должна появиться дважды).
Я надеюсь, что смогу прояснить это, и вам понравился мой пример кода:) - Я также буду благодарен за любые другие комментарии или улучшения. Мой Assign()
реализация хорошо?
2 ответа
Вам не нужно делать не универсальный конструктор базового класса абстрактным. Вы можете реализовать клон там, потому что у вас есть виртуальный конструктор.
Кроме того, вам не нужно делать Clone
метод виртуальный.
type
TMyClassBase = class abstract(TPersistent)
public
constructor Create; virtual; abstract;
function Clone: TMyClassBase;
end;
...
type
TMyClassBaseClass = class of TMyClassBase;
function TMyClassBase.Clone: TMyClassBase;
begin
Result := TMyClassBaseClass(ClassType).Create;
try
Result.Assign(Self);
except
Result.DisposeOf;
raise;
end;
end;
Обратите внимание, что ClassType
возвращается TClass
, Мы бросили это TMyClassBaseClass
чтобы убедиться, что мы вызываем ваш виртуальный конструктор базового класса.
Я также не понимаю, почему вы сделали TMyClassBase<T>
абстрактные и производные от него спецификации. Вы должны быть в состоянии реализовать все, что вам нужно, в общем классе.
Это, кажется, делает это:
function TMyClassBase<T>.Clone: TMyClassBase;
begin
{create new instance using empty TObject constructor}
Result:= ClassType.Create as TMyClassBase<T>;
{execute correct virtual constructor of descendant on instance}
Result.Create;
{copy data to instance}
Result.Assign(Self);
end;
Тем не менее, я никогда не видел этого раньше - это очень, очень неправильно...
Я проверил, что он правильно инициализирует данные целевого объекта и действительно вызывает конструктор потомков один раз. Я не вижу проблем, также не сообщается об утечке памяти. Протестировано с использованием Delphi 10.2.2. Прокомментируйте, пожалуйста:)