When I first took a look at the prototype design pattern in GoF(years ago), I realized that there was a big obstacle (challenge) to implement it in Delphi: How to write a routine to really clone (not just recreate) an object? In other words, how to perform a deep-copy of a living object in Delphi.
There are approaches out there mimicking the deep copy by simply calling the constructor and reassigning the state of the object by hand (I don’t like it). There are others exposing that a deep copy could be accomplished for the descendants of TPersistent by calling the Assign method (I don’t like it either).
With the new RTTI extensions it seemed to me (and to others) that a deep copy could be accomplished using reflection.
I was reluctant to write the routine myself since the work is not trivial. It could get really nasty because there might be composition, aggregation and God knows what within an arbitrary object.
So I waited….
Just a few days ago, I realized that I could use the JSON marshalling and unmarshalling features introduced in Delphi (2010?) to write the deep copy method. So I came with this:
.....
uses
DBXJSON, DBXJSONReflect;
.....
function DeepCopy(aValue: TObject): TObject;
var
MarshalObj: TJSONMarshal;
UnMarshalObj: TJSONUnMarshal;
JSONValue: TJSONValue;
begin
Result:= nil;
MarshalObj := TJSONMarshal.Create;
UnMarshalObj := TJSONUnMarshal.Create;
try
JSONValue := MarshalObj.Marshal(aValue);
try
if Assigned(JSONValue) then
Result:= UnMarshalObj.Unmarshal(JSONValue);
finally
JSONValue.Free;
end;
finally
MarshalObj.Free;
UnMarshalObj.Free;
end;
end;
You can now use it like this:
.....
var
MyObject1,
MyObject2: TMyObject;
begin
//Regular object construction
MyObject1:= TMyObject.Create;
//Deep copying an object
MyObject2:= TMyObject(DeepCopy(MyObject1));
try
//Do something here
finally
MyObject1.Free;
MyObject2.Free;
end;
end;
I tested it with some complex cases and it seems to be working quite well. Anyhow, if you find any problems or limitations, please, let me know.
Now that you get the idea we can do more crazy things like patching TObject (or any other class hierarchy) by using helpers. Look at this:
.....
interface
uses
DBXJSON, DBXJSONReflect;
type
TObjectHelper = class helper for TObject
function Clone: TObject;
end;
implementation
function TObjectHelper.Clone: TObject;
var
MarshalObj: TJSONMarshal;
UnMarshalObj: TJSONUnMarshal;
JSONValue: TJSONValue;
begin
Result:= nil;
MarshalObj := TJSONMarshal.Create;
UnMarshalObj := TJSONUnMarshal.Create;
try
JSONValue := MarshalObj.Marshal(Self);
try
if Assigned(JSONValue) then
Result:= UnMarshalObj.Unmarshal(JSONValue);
finally
JSONValue.Free;
end;
finally
MarshalObj.Free;
UnMarshalObj.Free;
end;
end;
All of a sudden, TObject has a Clone method! Call it like this:
.....
var
MyObject1,
MyObject2: TMyObject;
begin
//Regular object construction
MyObject1:= TMyObject.Create;
//Cloning an object
MyObject2:= TMyObject(MyObject1.Clone);
try
//Do something here
finally
MyObject1.Free;
MyObject2.Free;
end;
end;
If you think that helpers are an aberration, you can still create a TCloneable class with a Clone method and inherit from it, right? You can even use the decorator pattern to attach a Clone method to an object. You can do more…Share it with me, please. Thanks!