Лучший способ реализовать 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. Прокомментируйте, пожалуйста:)

Другие вопросы по тегам