Encore un blocage a cause du typage fort de l'AS2
Par -Alexandre LEGOUT aka LAlex- le vendredi, octobre 10 2003, 16:17 - AS2 - Lien permanent
Je profite de mes dernières heures de trial MX 2004 au bureau (eh eh, je l'ai pas installé à la maison 8)) pour pousser un (autre) coup de gueule sur la traduction de l'AS2 en AS1 ... ![]()
Décidemment, AS2 a beau être plus sympa à utiliser, le fait de poser les limitations du typage fort au moment de la compilation n'arrête pas de m'embêter. Il oblige à réécrire deux fois le même code, ce qui va entièrement à l'encontre des principes de base de la POO ... :?
Je veux créer deux classes dont l'une hérite de l'autre. Les constructeurs de ces deux classes prennent les mêmes paramètres. Je veux alors créer une méthode clone qui va me retourner un clone de l'objet (jusque là, rien de bien original). Pourtant, je vais être obligé de réécrire cette méthode pour la classe fille, car le typage fort m'empêche de créer une méthode dans la classe mère qui fonctionnera pour la classe fille. Dans le pire des cas, je veux bien me passer du typage fort pour la fonction clone (ou juste réécrire le typage de la méthode), mais pas réécrire la méthode entière ! ![]()
Voila ce que je suis obligé de faire :
// classe Parent
Et voila ce que j'aimerais (je devrais ?) pouvoir faire
class Parent {
var _prop1:Number;
var _prop2:Number;
function Parent(p1:Number) {
this._prop1 = p1;
this._prop2 = p1;
}
function clone() : Parent {
var ret : Parent = new Parent(this._prop1);
return ret;
}
}
// classe Child
class Child extends Parent {
function Child(p1:Number) {
super(p1);
this._prop2 = p1 + 10;
}
function clone() : Child {
var ret : Child = new Child(this._prop1);
return ret;
}
}// classe Parent
La deuxième possibilité serait possible avec un typage fort à l'execution (runtime) et pas à la compilation. J'ai bien essayé d'enlever le typage de la méthode clone (typage du retour et typage de la variable ret), mais il ne m'autorise pas à faire le new this.constructor, prétextant que this.constructor n'est pas une fonction, et en m'affichant comme message :
class Parent {
var _prop1:Number;
var _prop2:Number;
function Parent(p1:Number) {
this._prop1 = p1;
this._prop2 = p1;
}
function clone() : Parent {
var ret : Parent = new this.constructor(this._prop1);
return ret;
}
}
// classe Child
class Child extends Parent {
function Child(p1:Number) {
super(p1);
this._prop2 = p1 + 10;
}
function clone() : Child {
return super.clone();
}
}A function call on a non-function was attempted.
var ret = new this.constructor(this._prop1);
Pourtant, le script suivant ne peut pas mentir :roll:trace(typeof this.constructor); // Affiche : function
Commentaires
salut Lalex
J'ai le même probléme, j'arrive à réinitialiser l'objet a partir de la classe derivee :
function reset() {
super.constructor.apply(this, arguments);
}
Mais pas a le cloner, car ceci marche pas dans la classe parent:
public function clone() {
var temp;
constructor.call(temp, _prop1);
return temp;
}
Salut Lalex, je crois que c'est un probleme de raisonnement. Au vu de ton code, tout me parait normal et ca marcherait exactement pareil en Java.
La methode clone de la classe Parent renvoit une instance de Parent. Dans la methode clone de la classe fille, tu ne peux pas retrouner super.clone si le type de la methode clone fille est de type Child. Est-ce que tu vois ce que je veux dire? le downcasting n'est pas possible en POO, seulement le upcsating (i.e.: une instance de Child est une instance de Parent mais une instance de Parent n'est pas une instance de Child).
Donc dans ce cas-ci, je pense que le compilateur fait bien son boulot: il ne t'oblige pas a faire de la duplication de code mais t'informe que ce que tu veux faire c'est bien avoir deux methodes pour faire deux choses differentes: une pour retourner une instance de Parent en une pour retrouner une instance de Child. Comme je te disais, ce serait pareil en Java.
Je pense qu'en programmant an AS2, avec des classes propres, il vaut mieux eviter "constructor". Perso, je trouve ca un peu litigieux de creer une methode dans une classe mere qui va appeler le constructeur d'une classe fille qu'il n'est pas sense connaitre. Ca ne me semble pas super POO.
Sinon un truc a part: pas la peine de creer une variable locale ret dans tes methodes
Tu as raison sur le fait qu'utiliser constructor n'est pas la meilleure solution, c'est pour ca qu'une methode clone() devrait faire partie intégrante de Object ... :roll:
En fait, dans la partie de "ce que je voudrais pouvoir faire", j'ai un peut tout mélangé c'est vrai !
En fait, l'idéal serait de ne même pas avoir besoin de de réécrire la méthode clone() dans la classe Child, en ne précisant pas de typage de retour ... 
J'ai en fait solutionné mon problème en créant une méthode copyFrom qui ressemblerait à ca
class Parent {// ...
function copyFrom(o : Parent) {
this._prop1 = o._prop1;
this._prop2 = o._prop2;
}
function clone() : Parent {
var ret : Parent = new Parent();
ret.copyFrom(this);
return ret;
}
}
class Child {
// ...
function clone() : Child {
var ret : Child;
ret.copyFrom(this);
return ret;
}
}
Quand a la variable ret, elle est issue de mon code original, que je n'ai pas donné ici, car le code est un simple exemple (je n'utilise pas de classe nommée Parent en général ;)). C'est d'ailleurs le cas pour ma méthode copyFrom aussi, qui ici n'a pas grand intêret ...
Et il ne faut pas oublier l'erreur de compilation sur le fait qu'il ne considère pas à la compilation que this.constructor est une fonction ... :? La, c'est bien une erreur !
Salut Lalex,
> Et il ne faut pas oublier l'erreur de compilation sur le fait qu'il ne
> considère pas à la compilation que this.constructor est une fonction ...
> La, c'est bien une erreur !
Oui tu as raison. J'avais zappe cette partie et concentre sur le code, excuse moi :o. J'ai jete un coup d'oeil a la declaration et effectivement, constructor est declare en tant qu'Object dans le fichier "Object.as" dans le repertoire "Classes".
Je me demande si c'est une erreur ou si ca a ete mis en place volontairement pour eviter qu'on n'utilise pas "constructor". Parce que meme en le declarant en tant que fonction, comme c'est generique, on ne peut pas lui attribuer de type et donc une instance cree a partir de:
. Voila le code:
o = new this.constructor();n'aurait pas de type ou il faudrait faire un casting. En tout cas ca n'a l'air ni facile ni propre. J'ai essaye quand meme de modifier le fichier "Object.as" (et redemarrer MX 2004), en mettant:// var constructor:Object; -> comment out defaultApres ca je n'ai plus d'erreur de compilation mais ca ne marche pasfunction constructor();
// classe Parent
class Parent {
var _prop1:Number;
var _prop2:Number;
function Parent(p1:Number) {
this._prop1 = p1;
this._prop2 = p1;
}
function clone() {
return (new this.constructor(this._prop1));
}
}
// classe Child
class Child extends Parent {
function Child(p1:Number) {
super(p1);
this._prop2 = p1+10;
}
function doIt() {
trace("doIt");
}
}
// et le fla
import Parent;
import Child;
var c1:Child = new Child();
var c2:Child = c1.clone();
trace(c2._prop1); // undefined
trace(c2._prop2); // undefined
c1.doIt(); // doIt
c2.doIt(); // nothing happens
Bref, je crois que je n'utiliserai jamais constructor avec AS2, ou alors peute etre juste pour faire des tests d'egalites.
> En fait, l'idéal serait de ne même pas avoir besoin de de réécrire la
> méthode clone() dans la classe Child, en ne précisant pas de typage de retour ...
Hmm, je ne sais pas... Si tu ne precises pas le type de retour, alors tu perd tous les avantages de la compilation pour cette instance du moins. Et puis ca resterait le meme probleme qu'avant: une methode de la classe mere appelle un constructeur d'une classe fille, pas tres propre. Je pense qu'il faut faire un choix relativement drastique: soit on utilise AS1 et la on peut faire practiquement n'importe quoi, meme des hacks OO un peu crades mais qui marchent; soit on utilise AS2 et la faut se plier a du OO plus strict (en zigzagant entre les bug, cela dit :?). J'aime beaucoup ta methode copyFrom, ca marche et ca me semble nettement plus propre :).
A+
Timoth'
Un petit truc en plus (en restant dans les hacks crades ;)): toujours an ayant change la declaration de constructor et en utilisant les classes ci-dessus, voila un test en plus dans le fla (j'ai aussi rajoute une methode doIt dans la classe Parent qui trace "doIt - Parent"):
import Parent;import Child;
var c1:Child = new Child();
var c2:Child = c1.clone();
trace(c1); // [object Object]
trace(c1 instanceof Child); // true
trace(c1 instanceof Parent); // true
trace(c2); // [object Object]
trace(c2 instanceof Child); // false
trace(c2 instanceof Parent); // true
trace(c2._prop1); // undefined
trace(c2._prop2); // undefined
c1.doIt(); // doIt
c2.doIt(); // doIt - Parent
Vraiment bizarre: en utilisant this.constructor sur une instance de classe Child a partir d'une methode de la classe Parent, c'est le constructor de la classe Parent qui a ete utilise mais aucune des deux proprietes n'a ete initialisee??? On peut quand meme appeler les methodes de Parent sur la nouvelle instance clone.
Beurk! Non vraiment, constructor est a eviter je pense :|.
Merci de tes précisions Timothée !
Décidemment, la methode clone() devrait réellement être implémentée nativement dans les classes intrinsèques, comme c'est le cas en C# (euh ... en Java aussi non ?). Et en plus, si on veut rester propre en AS2, on ne peut plus utiliser Object.prototype pour résoudre le problème ... :roll:
Sinon, le this.constructor serait donc un objet à part entière ? Ne dépendant pas du this ?!? 8O C'est un comportement difficile à comprendre et je suis en train de m'embrouiller complètement
D'ailleurs, un comportement encore plus bizarre est que même dans la classe Child, this.constructor pointe sur la classe Parent :
class Child extends Parent {// ....
function doIt() {
trace(this.constructor == Parent);
}
}
var c:Child = new Child();
c.doIt(); // true
J'en arrive donc a la même conclusion que toi : constructor à fuir !!!
Salut Lalex,
> Sinon, le this.constructor serait donc un objet à part
> entière ? Ne dépendant pas du this ?!?
Arg, non, c'est pas possible. Le dot syntaxe peut pas avoir un comportement special juste pour constructor. Tout ca m'a fait me rappeler que constructor avait des bugs mais je pensais que tout etait regle depuis Flash MX. En recherchant mes bookmarks, je suis retombe sur l'article de Dave Yang qui documente le probleme avec constructor:
http://www.quantumwave.com/flash/inheritance.html
Normallement le probleme etait resolu sous Flash MX, au moins a partir d'une instance (this.constructor pointe sur la bonne classe). C'est bizarre que le probleme resurface comme ca sous MX 2004. On a mis le doigt sur un bug, c'est sur. Je suis tente de porter ca sur Flashcoders voir ce que les autres developeurs en pensent.
Sous Flash 5 et MX, on pouvait toujours re-assigner le constructor manuellement sur la fonction qu'on voulait. A la rigueur, on peut essayer de faire la meme chose sous MX 2004 mais ca veut dire qu'il faut le faire pour chaque classe cree, ca ne devrait vraiment pas avoir a le faire, genre:
class Parent {function Parent() {
this.constructor = Parent;
}
// ...
}
class Child extends Parent {
function Child() {
this.constructor = Child;
}
// ...
}
Sinon, j'ai aussi rejette un coup d'oeil a clone en Java et tu as raison. La methode clone est implementee dans Object, je l'avais oublie. Elle retourne une instance de la bonne classe mais upcastee vers Object est il faut faire un downcast explicite pour utiliser l'instance comme une instance de la classe. C'est donc bien une pratique acceptee, desole de l'avoir denigre plus haut. Je dois dire que ca me herisse quand meme le poil un peu, mais c'est vrai que c'est super utile.
Si constructor n'etait pas bugue, on pourrait implementer une methode clone dans la classe Object comme en Java. J'espere qu'on va trouver une solution :|.
Sinon, a partir d'aujourd'hui, je suis super emmerde. Mon trial MX 2004 a expire sur mon ordi et au boulot. Je peux plus rien tester et j'ai pas de thunes pour un upgrade :(. Va falloir que je trouve une solution.
Je suis pas sûr de ce que j'avance, mais le problème viendrait alors du player non ? Pour info, la syntaxe AS1 a le même problème :?
_global.Parent = function() {};_global.Child = function() {};
Child.prototype = new Parent();
Child.prototype.doIt = function() {
trace("isParent ? " + (this.constructor == Parent));
trace("isChild ? " + (this.constructor == Child));
}
var obj = new Child();
obj.doIt();
// isParent ? true
// isChild ? false
Tu devrais en effet le soumettre sur FlashCoders (tu causes tellement bien l'anglais :D)
Sinon, pour la trial, c'est moche
Tu bosses pas avec au boulot ? Moi, j'ai installé la trial chez moi une fois la trial du bureau expirée ... 
Hmm, le resultat que tu donnes c'est AS1 sous MX 2004?
Je viens d'essayer sous Flash MX (la j'ai ma licence a moi ;)) et c'est different, je copie ton code et j'obtient (ce que j'attend):
isParent ? falseisChild ? true
Bravo Macromedia!
Ben disons que l'inverse n'était pas gênant de Flash 5 à Flash MX, mais la niveau compatibilité ascendante, ca la fout mal ... :? Surtout vu le nombre de programmeurs POO depuis Flash MX ! :roll:
Eg ben, ca se dispute pas sur Flashcoders pour parler de ce bug ... :? La remarque est passée totalement inapercue !!! 8O
Ouaip, z'en ont rien a cirer les mecs :). Je relancerai ce soir. Je suis quand meme super handicape sans pouvoir faire de tests :(. Je sens que ca va se finir en formattage du disque.
Sinon, dans le morceau de code qu'on avait, en creant manuellement la propriete constructor dans le constructeur, est-ce que ton code d'origine marchait bien? Je demande parce qu'en en plus de juste le probleme de constructor, les deux parametres n'etaient pas initialise dans mon code plus haut quand constructor pointait sur parent...
Voici le code (on considère que le fichier Object.as a été rectifié pour avoir constructor en tant que fonction)
// classe Parentclass Parent {
var _prop1:Number;
var _prop2:Number;
function Parent(p1:Number) {
this._prop1 = p1;
this._prop2 = p1;
}
function clone() {
return (new this.constructor(this._prop1));
}
}
// classe Child
class Child extends Parent {
function Child(p1:Number) {
super(p1);
this._prop2 = p1+10;
this.constructor = Child;
}
function doIt() {
trace("doIt");
}
}
// et le fla
import Parent;
import Child;
var c1:Child = new Child();
var c2:Child = c1.clone();
trace(c2._prop1); // undefined
trace(c2._prop2); // NaN
c1.doIt(); // doIt
c2.doIt(); // doIt
On a donc bien une instance de Child qui est créee, mais les propriétés ne sont pas crées ... même si on enlève le super du constructeur de Child et qu'on se limite à une initalisation de variables, ca ne fonctionne pas ... 8O Je pense que c'est du au fait que Flash crée un objet qu'il va downcaster vers le type Function, et donc le constructor en tant que fonction n'a pas toutes ses fonctionnalités ... :?
De toutes facons, même si une solution était trouvée, on n'a pas le droit de la distribuer (voir Grant Skinner et l'EULA : http://www.gotoandplay.ca/archives/2003/10/14/grant_skinner_et_leula_suite.html )... :?
Bizarre quand meme que les proprietes ne soient pas creent... Le casting intervient pour que nous on puisse programmer tranquille vis a vis du compilateur mais au run time, le casting n'intervient pas et il ne devrait pas y avoir de probleme... Vraiment je ne comprend pas ce qui se passe...
Ha oui, le coup de l'EULA, je ne m'en fait pas pour ce que nous faison dans ce thread. Au pire on modifie trois lignes dans les classes de bases alors c'est facile de mettre un how-to sur le web et laisser ceux qui en ont besoin faire les changement eux-meme. Nous on distribue rien
Bref, apres ton post sur super et la remarque sur __constructor__, c'est vrai qu'on peut l'utiliser, sauf que meme dans MX2004, il est toujours non-documente (je crois) alors que constructor l'est :? (vraiment dommage qu'il ne marche pas).
Bref, si on veut faire une methode clone comme en java dans object, on peut modifier le fichier Object.as et ajouter ca:
function clone():Object {apres il faut faire un cast explicite pour utiliser l'instance:var o:Object = Object(this["__constructor__"]());
for (var i in this) o[i] = this[i];
return o;
}
// the classclass myClass {
function myClass() {}
}
var c1:myClass = new MyClass();
var c2:myClass = MyClass(c1.clone());
Sans MX2004, je peux pas tester, alors je te laisse ce plaisir ;).
C'est l'equivalent de ce que fait java: du clonage "shallow copy". On ne copie que les references dans le cas d'objects ou d'array (alors faut faire super gaffe). Sinon, on peux aussi reutiliser la methode clone de Arul Kuraman pour faire du clonage un peu plus mieux (deep copy):
http://www.shockwave-india.com/blog/actionscript/?asfile=Object-clone.as
La reference sur Java 1.3.1:
http://java.sun.com/j2se/1.3/docs/api/java/lang/Object.html#clone()
C'est à peu prés ce que je pensais faire ... mais il y a 2-3 problèmes pour AS2 dans ton code ...
Alors, aprés avoir testé, la méthode clone() correcte est la suivante :
class Dummy {var _prop:Object;
function Dummy(prop:Object) {
this._prop = prop;
}
function clone():Object {
var o:Object = new this["__constructor__"]();
for (var i in this) o[i] = this[i];
return o;
}
function doIt() {
trace("doIt : _prop = '" + this._prop + "'");
}
}
// le fla
var obj = new Dummy("Une chaine");
var clo = obj.clone();
trace(obj._prop + "/" + clo._prop); // Une chaine/Une chaine
clo._prop = "Une chaine clonée";
trace(obj._prop + "/" + clo._prop); // Une chaine/Une chaine clonée
trace(clo instanceof Dummy); // true
clo.doIt(); // doIt : _prop = 'Une chaine clonée'
Même pas besoin d'upcatser ... et en plus l'upcatsing ne peut pas se faire comme tu le marques ... En fait, le constructeur reste une fonction (en "prototype-based"), et en plus il ne peut rien retourner (sinon, erreur à la compilation) ... :?
Par contre, ca ne marche pas si on la met dans Object.as ... Aparemment, le fichier .as des classes intrinsèques sert uniquement au typage, mais pas au code (c'est un fichier header, comme en C :)) ...
Mais pour en revenir à l'inconvénient du typage fort tel qu'il est dans Flash MX 2004 (à la compilation), on voit bien que si ce typage existait uniquement à l'execution, on n'aurait pas ce problème (voir la ligne avec instanceof) ... :roll:
En tout cas, voila une bonne chose de faite ...
Salut Lalex, petite note rapide, au niveau de constructor qui ne marche pas, on a ete un peu con (surtout moi) :P: c'est normal que la propriete _prop1 soit undefined: on a pas passe de valeur a la premiere instance. En fait ca marche tres bien:
// classe ParentDonc je revois ma conclusion: si on change le fichier Object.as . Tout a l'air de marcher, c'est con de ne pas pouvoir mettre la methode clone dans Object parce que ce serait vraiment sa place. Je pense qu'on doit pouvoir la declarer la puis creer une classe "utilitaires" qui peut rajouter des methodes aux classes intrinseques. Je verrai si j'ai le temps d'essayer demain soir. Tu es sur? Object est sans doute special effectivement, mais il me semble avoir lu que le casting explicit se faisait bien en passant un object a la classe (au moins pour les custom classes). Par exemple, en utilisant la methode clone:class Parent {
var _prop1:Number;
var _prop2:Number;
function Parent(p1:Number) {
this._prop1 = p1;
this._prop2 = p1;
}
function clone() {
return (new this.constructor(this._prop1));
}
}
// classe Child
class Child extends Parent {
function Child(p1:Number) {
super(p1);
this._prop2 = p1+10;
this.constructor = Child;
}
function doIt() {
trace("doIt");
}
}
// et le fla
import Parent;
import Child;
var c1:Child = new Child(10); // <- faut pas oublier de passer une valeur
var c2:Child = c1.clone();
// ouais ca marche!
trace(c2._prop1); // 10
trace(c2._prop2); // 20
c1.doIt(); // doIt
c2.doIt(); // doIt
// et le flaalors que:import Parent;
import Child;
var c1:Child = new Child(10);
var c2:Child = c1.clone(); // <- boom, ereur du compilateur
// et le flaimport Parent;
import Child;
var c1:Child = new Child(10);
var c2:Child = Child(c1.clone()); // <- pas de probleme, ca compile bien
Bien sur, si tu ne declares pas c2 en tant que Child alors il n'y a plus de probleme mais tu perds les avantages d'attraper les erreurs des la compilation.
salut
après avoir "reactualiser le constructor de Child" pour pallier au pb d'héritage, il semblerait que passer constructor en "dynamique" suffirait à résoudre le problème à la compliation :
// classe Parentclass Parent {
var _prop1:Number;
var _prop2:Number;
function Parent(p1:Number) {
this._prop1 = p1;
this._prop2 = p1;
}
function clone() {
return (new this["constructor"](this._prop1));
}
}
//classe Childclass Child extends Parent {
function Child(p1:Number) {
super(p1);
this._prop2 = p1+10;
this.constructor = Child;
}
function doIt() {
trace("doIt");
}
}
// et le flavar c1:Child = new Child(10);
var c2:Child = c1.clone();
trace(c2._prop1); // 10
trace(c2._prop2); // 20
c1.doIt(); // doIt
c2.doIt(); // doIt
tout a l'air de fonctionner... ^^
ciao
bonjour, le typage fort est une grosse avancée d'AS2.
Pour résoudre votre problème : rien de plus simple : utiliser les interfaces
Cordialement
Alain
wuastc > Le problème de ta méthode clone est tout simplement qu'on ne peut utiliser le typage fort en retour ....
alain > Comme je l'ai dit dans le post "Bug de constructor", le typage fort sans une instruction de clonage me parait assez "bizarre". On est alors obligé d'utiliser certains hacks, associé à du transtypage pour arriver à ses fins ... :\
De toutes façons, un typage fort à la compilation me semble bien peu approprié même aujourd'hui alors que mes habitudes de codage ont évolué vers l'utilisation de ce typage fort justement ...
Un typage à l'execution serait un pur bonheur par contre ! 
++ ^^
Fil des commentaires de ce billet