Cloner : le clonage non-thérapeutique d'objets
Par -Alexandre LEGOUT aka LAlex- le jeudi, août 5 2004, 15:18 - AS2 - Lien permanent
Suite à la discussion sur la "deep copy" et la "shallow copy" qu'a introduit zwetan, je me suis attaqué à une ébauche de classe permettant de faire du clonage. Je l'ai testée sur quelques types de bases et quelques classe maisons, et pour l'instant seules les classes MovieClip et Color m'ont posé un problème ... ce qui est logique à priori ! ![]()
Le principe court-circuite l'instanciation avec new, et se sert des proriétés __proto__ et __constructor__ pour créer le clone :/**
Au début, pour créer la "base" du clone, j'utilisais l'instruction suivante :
* Cloner class
*
* @version 0.1
* @author <a href="http://www.lalex.com/">LAlex</a>
* @since 05/08/2004
*/
class net.lalex.core.Cloner {
/**
* Create a clone of an object
* Works for all datatypes, except MovieClip and Color
*
* @param o Object to get a clone
* @param deep Allow to disable deep copy
*/
public static function clone(o:Object, deep:Boolean):Object {
// Set the "deep" param to true by default
if (deep == undefined) {
deep = true;
}
// Can't clone MovieClip instances
if (o instanceof MovieClip || o instanceof Color) {
trace("Cloner error : can't clone MovieClip or Color instances !");
// If object, create a clone and return it
} else if (typeof o == "object") {
// Return objet
var ret:Object = {};
// Object's constructor
var cst:Function = o["__constructor__"];
// Assign original prototype to clone
ret.__proto__ = o.__proto__;
// Objects created with {} have no __constructor__ property
if (cst != undefined) {
ret["__constructor__"] = cst;
cst.apply(ret);
}
// Copy all properties from the original to the clone
// Only "object" properties need to be cloned for deep copy
// Also works for arrays ... <img src="http://common.lalex.com/themes/devblog/smilies/icon_cool.gif" alt="8-)" class="smiley" />
for (var curProp:String in o) {
ret[curProp] = typeof o[curProp] == "object" && deep ? Cloner.clone(o[curProp]) : o[curProp];
}
return ret;
}
// If simple type, returns it
return o;
}
}
// Utilisation
import net.lalex.core.Cloner;
var monTableau:Array = [1,2,3,4,5];
// monClone contient un clone de monTableau
var monClone = Cloner.clone(monTableau);var ret:Object = new o["__constructor__"]();Cela ne marchait pour les objets créés avec les accolades, étant donné que ces objets ne possèdent pas de propriété __constructor__ comme Timothée Groleau nous le signale dans les commentaires de mon article sur le prototypes ... ![]()
Wala, je suis bien évidemment ouvert à tout feedback, que ce soit pour signaler un disfonctionnement sur certaines classes ou types de données, ou pour suggérer une amélioration de cette implémentation du clonage ... ^^
Commentaires
cela marche aussi sur TextField et Button ?
perso je suis plus pour implementer le cloning en utilisant le polymorphisme objet
chaque objet pouvant etre cloné posse une methode "clone",
apres meme si on tombe sur un MovieClip cela permet de gerer la situation speciale
Eh bien à ce moment là, tu n'a qu'à utiliser la méthode avec un Object.prototype et en remplacant o par this ... Ce qui est déjà fait (pas vraiment comme moi) là : http://www.shockwave-india.com/blog/actionscript/?asfile=Object-clone.as
++ ^^
nan nan je parlais bien de polymorphisme
l'objet Object possede sa methode clone
l'objet Array possede sa methode clone
l'objet String possede sa methode clone
etc...
et comme ca pour tous les objets qui peuvent etre cloné
le gros probleme de tout faire dans Object.prototype c'est justement de tester l'instanceof de l'objet,
si la class Blah herite de la class Array les 2 class sont de l'instanceof Array.
_global.Blah = function()l'autre probleme est de tester le typeof car tout ce qui n'est pas une primitive en ECMAscript est forcément du type "object" (note: dans l'environnement flash il faut considerer un MovieClip comme une primitive ..enfin sujet a long debat). Alors que si on applique le polymorphisme on est plus dans une logique OO, qui cerise sur le gateau s'applique tres facilement a un langage basé sur les prototypes http://c2.com/cgi/wiki?PolyMorphism cela donne aussi le bonus de pouvoir traiter les cas speciaux comme le clonage de MovieClip et Color. Et puis quand on parle de "clone" il faut aussi parler de "copy" et bien penser au comportement "naturel" de ECMAscript, par exemple prenons la class Array. exemple:{
}
Blah.prototype = new Array();
test = new Blah();
trace( test instanceof Array ); //true
toto = [ "un", "deux", "trois" ];si on voullait copier l'array toto est-ce qu'il faudrait aussi copier la propriete "hello" ? un oui ou un non ne suffit pas, il faut vraiment se poser des questions de fond. si je copie un certain type d'objet est-ce que je me limite a copier l'objet en fonction de son type ou est-ce que je dois le considérer comme un objet generique et copier tout ce qu'il y a dedans ? toto = [ "un", "deux", "trois" ]; on est sur que c'est bien un array, les valeurs sont indexées mais avec en plus toto.hello = "world"; on ne traite plus toto comme juste un array on le considere aussi comme un objet c'est en meme temps un array et un objet mais revenons a comment ECMAscript traite les copies d'objets par defaut: - si c'est une primitive on copie sa value - si c'est un objet on copie sa reference mais là où ca devient vicieux c'est que certains type peuvent etre soit une primitive soit un objet et pourtant etre du meme type, voir meme pire etre une primitive d'un certain type qui dans des cas speciaux sera dynamiquement transformé en objet voir les excellents articles du blog de Eric Lippert http://blogs.msdn.com/ericlippert/archive/2003/11/06/53367.aspx titi1 = "hello world"; est du type String mais est une primitive titi2 = new String( "hello world" ); est aussi du type String mais est un objettoto.hello = "world";
titi1 = "hello world";est-ce que titi1 et titi2 doivent etre copié ou cloné de la meme maniere ? doit on se basé sur le type string et ne s'occuper de copier que ce qui classifie un objet ou une primitive en tant que type string, ou prendre en compte la difference entre l'objet et la primitive pour copier l'instance ? il y a forcément des choix à faire, et meme des choix qui s'imposent de part la nature et les limitations du langage lui-meme, par exemple si on voullait differencier la copy du type String en fonction que ce soit un objet ou une primitive on ne pourrait pas le faire.titi2 = new String("hello world");
trace( typeof(titi1) ); //string
trace( titi1 instanceof String ); //false
trace( typeof(titi2) ); //object
trace( titi2 instanceof String ); //true
String.prototype.copy = function()avec toto1 qui est une primitive on pourrait appeler toto1.copy(), mais dans ce cas la primitive serait wrappée dans un objet du type String pour justement pouvoir appeler les methodes de cet objet donc meme si c'est une primitive a la base des qu'on appelle une de ses methodes ce n'est plus une primitive mais un objet. Cette particularité de ECMAscript vient du fait que en mémoire une primitive coute moins de place que un objet, plutot logique pour un langage dit de script tendant donc vers la legereté. Et c'est pour ce genre de choses que j'insiste sur la nature ECMAscript de ActionScript, car face a ce genre de comportement on peut soit etre contre (bla bla un langage OO ne devrait pas avoir ce comportement abhérant bla bla ...), soit au contraire etre pour et embrassé ce genre de philosophie et donc ajouter des fonctionnalités au langage en allant dans le meme sens que celui-ci. Pour en revenir sur la copie et le clonage, a quoi sert reelement une copie ? obtenir ce qui caracterise vraiment la valeur d'une instance et comment on obtient la valeur d'une instance en ECMAscript ? avec valueOf ce qui importe quand on veut copier un type String, c'est de surtout recuperer cela: sa valeur, et donc est-ce si important de faire la différence entre un type String primitif et un type String objet ? amha non plus vraiment, dans les 2 cas le valueOf retournera la meme valeur, et autant suivre la philosophie du langage qui promouvoie l'utilisation de primitives pour ce genre de type afin de rester leger. Et pour ce qui est du clonage de ce meme type String, là l'important est de passer une reference pour justement pointer sur l'objet de reference et ne surtout pas copier sa valeur. Mais la encore, la nature du langage intervient, si on a un type String primitif on aura automatiquement un passage par value, mais en fait ca nous arrange pas mal nous ce qu'on veut c'est juste dans le cas ou le type String soit un objet passer sa reference avec la methode clone, et si le type String est une primitive et bien le comportement naturel du langage sera de passer une primitive et on n'aura pas besoin de le verifier, c'est le langage qui fera la selection de lui-meme. ce qui nous donne cela:{
...
}
String.prototype.copy = function(){
return this.valueOf();
}
String.prototype.clone = function()
{
return this;
}
et c'est là où le polymorphisme prends toute son importance, ce sont les objets eux-meme qui decideront de comment ils sont copiés pas une fonction "generale" clone ou meme copy qui va tester le typeof et l'instanceof de l'objet.
Cela ne veut pas dire que Object.prototype.clone ne doit pas exister, si il doit etre là mais son comportement doit etre de cloner generiquement n'importe quel objet en tant que type objet et cela donne une nuance importante car en faisant de la sorte on peut de la meme manière implémenter une methode clone pour les types primitifs et les types objet ayant une ou des characteristiques tres precises comme l'objet Array.
si Array.prototype.clone n'existe pas alors le clonage du type Array se passe comme si on clonait un type Object et non pas un type Array (car Array herite par defaut de Objet.prototype),
par contre si justement on implémente une methode clone dédiée spécialement au type Array on fera un clonage specifique a ce type.
Et si on definit une class Toto, on pourra choisir de lui implementer ou non une methode clone selon justement quel comportement de clonage on veut donner a cet objet particulier.
Apres la copie d'objet, il faut pouvoir verifier la copie d'objet et donc s'interesser a des methodes comme equals, compare etc...
car les operateurs comme == et === ne suffisent plus a valider l'égalité lorsqu'on utilise autre chose que les mecanismes par defaut pour copier les objets.
Et pour finir ne toujours pas oublier que l'on est dans du ECMAscript, en JAVA ou C# on a pas besoin de definir des methodes clone, copy, equals etc... elles existent deja dans le langage.
ECMAscript n'inclut pas ces fonctionnalités par defaut, mais permet lui de modifier les objets par defaut du langage en ajoutant des choses comme String.prototype.copy et donc dans ce contexte qu'est ce qui est le plus abhérant au niveau d'un design OO:
- creer une class String2 qui etend String pour pouvoir implementer une methode clone
ou
- directement ajouter ce qui manque dans le core object String
et là c'est plus une question de choix entre AS1 et AS2 vue que les deux peuvent utiliser l'une ou l'autre des solutions, cest une question de savoir quel est le meilleur et le logique choix de design OO dans le contexte d'un langage prototype-based comme ECMAscript.
Pour illustrer tout ca, il faudrait que toi et/ou Arul testiez votre code avec humm je sais pas un objet Date par exemple, le clone (ou la copy) obtenu devrait vous surprendre
Hi, thanks for the class, sorry for no français.
Somehow, though I can't believe you didn't test this, it doesn't work for arrays.
I would replace the line...
var ret:Object = {};
with...
var ret = o instanceof Array ? [] : {};
I wonder why your version didn't work for me. Very weird.
Hmmmm
Fil des commentaires de ce billet