Manière correcte de dupliquer l'objet Delphi
Quels sont les avantages et les inconvénients de la duplication d'une instance d'objet avec un constructeur ou une fonction d'instance?
Exemple:
type
TMyObject = class
strict private
FField: integer;
public
constructor Create(srcObj: TMyObject); overload;
//alternatively:
//constructor CreateFrom(srcObj: TMyObject);
property Field: integer read FField;
end;
constructor TMyObject.Create(srcObj: TMyObject);
begin
inherited Create;
FField := srcObj.Field;
end;
Exemple B:
type
TMyObject = class
strict private
FField: integer;
public
function Clone: TMyObject;
property Field: integer read FField;
end;
function TMyObject.Clone: TMyObject;
begin
Result := TMyObject.Create;
Result.FField := FField;
end;
Une différence majeure vient immédiatement à l'esprit - dans ce dernier cas, le constructeur Create devrait être virtuel afin qu'une hiérarchie de classes supportant le Clone puisse être construite en se basant sur le TMyObject.
Supposons que ce n'est pas un problème - que TMyObject et tout ce qui en est basé est entièrement sous mon contrôle. Quelle est votre façon préférée de faire copy constructor dans Delphi? Quelle version trouvez-vous la plus lisible? Quand utiliseriez-vous l'ancienne ou la dernière approche? Discuter. :)
Modifier: Ma principale préoccupation avec le premier exemple est que l'utilisation est très lourde par rapport à la deuxième approche, c'est-à-dire
newObj := TMyObject.Create(oldObj)
Vs.
newObj := oldObj.Clone;
EDIT2 ou "pourquoi je veux une opération sur une seule ligne"
Je conviens que L'affectation est une approche raisonnable dans la plupart des cas. Il est même raisonnable d'implémenter 'copy constructor' en interne en utilisant simplement attribuer.
Je crée généralement de telles copies lors du multithreading et du passage d'objets dans la file d'attente des messages. Si la création d'objet est rapide, je passe généralement une copie de l'objet d'origine car cela simplifie vraiment les problèmes de propriété de l'objet.
IOW, je préfère écrire
Send(TMyObject.Create(obj));
Ou
Send(obj.Clone);
À
newObj := TMyObject.Create;
newObj.Assign(obj);
Send(newObj);
3 réponses
Le premier ajoute des informations sur l'objet à créer, le second non. Cela peut être utilisé pour instancier par exemple un descendant ou un ancêtre d'une classe
La voie Delphi (TPersistent
) sépare la création et le clonage:
dest := TSomeClass.Create;
dest.Assign(source);
Et a cette même propriété que vous choisissez explicitement la classe à instancier. Mais vous n'avez pas besoin de deux constructeurs, un pour une utilisation normale, et un où vous voulez cloner.
Modifier en raison de l'exigence oneline Vous pouvez le mélanger bien sûr utiliser des métaclasses Delphi (non testées)
type
TBaseSomeObject = class;
TBaseObjectClass = class of TBaseSomeObject;
TBaseSomeObject = class(TPersistent)
function Clone(t: TBaseObjectClass = nil): TBaseSomeObject; virtual;
end;
...
function TBaseSomeObject.Clone(t: TBaseObjectClass = nil): TBaseSomeObject;
begin
if Assigned(t) then
Result := t.Create
else
Result := TBaseObjectClass(Self.ClassType).Create;
Result.Assign(Self);
end;
SendObject(obj.Clone); // full clone.
SendObject(obj.Clone(TDescandantObject)); // Cloned into Descendant object
Pour le reste, implémentez simplement vos opérateurs assign()
, et vous pouvez mélanger plusieurs façons.
Edit2
J'ai remplacé le code ci - dessus par le code testé dans D2009. Il y a quelques dépendances des types qui pourraient vous avoir confus, j'espère que c'est plus clair de cette façon. Bien sûr, vous devrez étudier le mécanisme assign. J'ai également testé le paramètre par défaut metaclass=nil
et cela fonctionne, donc je l'ai ajouté.
Je ne pense pas qu'il y ait une bonne façon de dépendre simplement du style personnel. (Et comme Marco l'a souligné, il y a plus de moyens.)
- la manière du constructeur est courte mais elle viole le principe selon lequel le constructeur ne doit construire que l'objet. Ce qui n'est peut-être pas un problème.
- la manière de clone est courte bien que vous ayez besoin de fournir un appel pour chaque classe.
- la manière assign est plus Delphi. Il sépare la création et l'initialisation ce qui est bien parce que nous aimons celui méthode un concept de fonction qui rend le code mieux à maintenir.
Et si vous implémentez Assign using streams, vous n'avez qu'un seul endroit pour vous soucier des champs qui doivent être disponibles.
J'aime le style clone - mais seulement en Java (ou tout autre langage GC). Je l'ai utilisé quelques fois en Delphes, mais surtout je reste avec Create
et Assign
, car il est beaucoup plus clair qui est responsable de la destruction de l'objet.