Mi imbatto in un problema mentre scrivo una serie di classi che ereditano l'una dall'altra.
ho ridotto il codice all'esempio di base riportato di seguito per semplicità.
Basically i have a series of classes inheriting from each other in cascade (called MyBaseClass, MyDerivedClass, MyDoubleDerived)and they differ for the value assigned to the F_Type property (value 1 2 and 3 respectively). i am creating an instance of MyDoubleDerived Class assigning it to a variable (and also storing into an Associative array where i can get to it by its ID).
Vedo che durante l'istanziazione F_Type è impostato correttamente su 3 così come quando provo a raggiungere la proprietà dall'oggetto stesso.
Tuttavia, quando mi trovo all'interno di un metodo, il valore di this: F_Type o F_Type è per qualche motivo 2 (ma se accedo dall'array associativo che memorizza l'oggetto, il valore è correttamente 3). Abbastanza strano, il metodo è definito nella classe Base (che ha F_Type= 1 quindi non riesco a capire perché il valore è 2. potrebbe trattarsi di un bug di qualche tipo? (lo vedo sia in JMP 17.1.0 che in JMP 18 Early release, non ho una versione precedente disponibile da testare)
If(!Namespace Exists( "MyNameSp" ),
NS=New Namespace( "MyNameSp" );
);
MyNameSp:ARList=associative array(); // i store here the object that i can access via its key for convenience
<BR />// below the fucntion to instantiate the object from the class
MyNameSp:CreateNewObj=Function({ClassName, NewKey},
{Default Local},
ObjRef=NewObject(ClassName,{NewKey});
show(ObjRef:ObjID);
MyNameSp:ARList[ObjRef:ObjID]=ObjRef;
return(ObjRef);
);
Define Class( "MyBaseClass",
F_Type=1;
ObjID="";
_init_=Method({NewKey},
this:ObjID=NewKey;
show("value during initialization");
show(this:F_Type);
show(F_Type);
);
MyMethod=Method({},
show("value in method");
show(F_Type);
show(this:F_Type);
show(this:ObjID);
show(MyNameSp:ARList[this:ObjID]:F_Type);
);
);
Define Class( "MyDerivedClass",
baseclass( MyBaseClass ),
F_Type=2;
);
////////////////////////////////////////////////////////////////////////////
Define Class( "MyDoubleDerived",
baseclass( MyDerivedClass ),
F_Type=3;
);
<BR />// start of the actual script
SampleObj=MyNameSp:CreateNewObj("MyDoubleDerived","Key1");
show("value From script");
show(SampleObj:F_Type);
SampleObj:MyMethod();
Ho ricontrollato e lo stesso comportamento vale anche per i metodi.
se chiami un metodo specifico da un altro metodo e il metodo "target" differisce nel diverso livello di ereditarietà, viene utilizzato il metodo della classe "di un livello superiore" nello stack (non sono sicuro che la dicitura abbia senso).
allo stesso modo se definisco il metodo della classe derivata dal metodo "_init_" tutto funziona come previsto
Mentre per la variabile posso capire la logica del fare nel metodo "_init_", per i metodi sembra controintuitivo.
C'è qualche documentazione a riguardo?
qui l'esempio modificato:
If(!Namespace Exists( "MyNameSp" ),
NS=New Namespace( "MyNameSp" );
);
MyNameSp:ARList=associative array();
MyNameSp:CreateNewObj=Function({ClassName, NewKey},
{Default Local},
ObjRef=NewObject(ClassName,{NewKey});
show(ObjRef:ObjID);
MyNameSp:ARList[ObjRef:ObjID]=ObjRef;
return(ObjRef);
);
Define Class( "MyBaseClass",
F_Type=1;
K_Type=1;
ObjID="";
_init_=Method({NewKey},
this:ObjID=NewKey;
);
MyChangeInitMethod=Method({PassedType, passedID},
show("INIT Base Level method (1) ",PassedType, passedID);
);
MyMethod=Method({},
this:MyChangeMethod(this:F_Type,this:ObjID);
this:MyChangeInitMethod(this:F_Type,this:ObjID);
);
MyChangeMethod=Method({PassedType, passedID},
show("Base Level method (1) ",PassedType, passedID);
);
);
Define Class( "MyDerivedClass",
baseclass( MyBaseClass ),
F_Type=2;
_init_=Method({NewKey},
this:ObjID=NewKey;
K_Type=2;
MyChangeInitMethod=Method({PassedType, passedID},
show("INIT Derived class Method (2)",PassedType, passedID);
);
);
MyChangeMethod=Method({PassedType, passedID},
show("Derived class Method (2)",PassedType, passedID);
);
);
////////////////////////////////////////////////////////////////////////////
Define Class( "MyDoubleDerived",
baseclass( MyDerivedClass ),
F_Type=3;
_init_=Method({NewKey},
this:ObjID=NewKey;
K_Type=3;
MyChangeInitMethod=Method({PassedType, passedID},
show("INIT double Derived class method (3)",PassedType, passedID);
);
);
MyChangeMethod=Method({PassedType, passedID},
show("double Derived class method (3)",PassedType, passedID);
);
);
////////////////////////////////////////////////////////////////////////////
Define Class( "MyTripleDerived",
baseclass( MyDoubleDerived ),
F_Type=4;
_init_=Method({NewKey},
this:ObjID=NewKey;
K_Type=4;
MyChangeInitMethod=Method({PassedType, passedID},
show("INIT Triple Derived class method (4)",PassedType, passedID);
);
);
MyChangeMethod=Method({PassedType, passedID},
show("Triple Derived class method (4)",PassedType, passedID);
);
);
SampleObj1=MyNameSp:CreateNewObj("MyBaseClass","Key1");
SampleObj2=MyNameSp:CreateNewObj("MyDerivedClass","Key2");
SampleObj3=MyNameSp:CreateNewObj("MyDoubleDerived","Key3");
SampleObj4=MyNameSp:CreateNewObj("MyTripleDerived","Key4");
show("----------------Calling the method via a method-------------------");
show(" 1 ");
SampleObj1:MyMethod();
show(" 2 ");
SampleObj2:MyMethod();
show(" 3 ");
SampleObj3:MyMethod();
show(" 4 ");
SampleObj4:MyMethod();
show("----------------Clling the method directly------------------------");
show(" 1 ");
SampleObj1:MyChangeMethod(SampleObj1:F_Type,SampleObj1:ObjID);
show(" 2 ");
SampleObj2:MyChangeMethod(SampleObj2:F_Type,SampleObj2:ObjID);
show(" 3 ");
SampleObj3:MyChangeMethod(SampleObj3:F_Type,SampleObj3:ObjID);
show(" 4 ");
SampleObj4:MyChangeMethod(SampleObj4:F_Type,SampleObj4:ObjID);</CODE></P><P> </P></LINGO-BODY><LINGO-SUB id="lingo-sub-725797" slang="en-US" mode="CREATE">Ri: Ereditarietà di classe, cosa mi manca?</LINGO-SUB><LINGO-BODY id="lingo-body-725797" slang="en-US" mode="CREATE"><P>Grazie per la risposta.</P><P>Sebbene riadattare il codice sia abbastanza semplice da poter continuare a lavorare, non riesco veramente a capire cosa si nasconde dietro le quinte.</P><P>quando chiamo direttamente le proprietà ottiene il valore giusto (3), è solo all'interno del metodo che il valore è 2 (e non 1) ho anche provato ad aggiungere un altro livello (ed è sempre il livello n-1 quello è considerato).</P><P> </P><P>Inoltre questo solleva un'altra domanda: per quanto riguarda l'ereditarietà dei metodi che sono anche memorizzati in variabili... dovrei definire anche i metodi specifici dall'interno di"_init_"?.</P><P> </P><P> </P></LINGO-BODY><LINGO-SUB id="lingo-sub-725714" slang="en-US" mode="CREATE">Ri: Ereditarietà di classe, cosa mi manca?</LINGO-SUB><LINGO-BODY id="lingo-body-725714" slang="en-US" mode="CREATE"><P>Questo è probabilmente più vicino a quello che vuoi</P>
<PRE><CODE class=" language-jsl">If(!Namespace Exists( "MyNameSp" ),
NS=New Namespace( "MyNameSp" );
);
MyNameSp:ARList=associative array(); // i store here the object that i can access via its key for convenience
// below the fucntion to instantiate the object from the class
MyNameSp:CreateNewObj=Function({ClassName, NewKey},
{Default Local},
ObjRef=NewObject(ClassName,{NewKey});
show(ObjRef:ObjID);
MyNameSp:ARList[ObjRef:ObjID]=ObjRef;
return(ObjRef);
);
Define Class( "MyBaseClass",
F_Type=1;
ObjID="";
_init_=Method({NewKey},
this:ObjID=NewKey;
show("value during initialization");
show(this:F_Type);
show(F_Type);
);
MyMethod=Method({},
show("value in method");
show(F_Type);
show(this:F_Type);
show(this:ObjID);
show(MyNameSp:ARList[this:ObjID]:F_Type);
);
);
Define Class( "MyDerivedClass",
baseclass( MyBaseClass ),
// F_Type=2;
_init_=Method({NewKey},
super:_init_(NewKey);
F_Type=2;
show("MyDerivedClass",F_Type);
);
);
////////////////////////////////////////////////////////////////////////////
Define Class( "MyDoubleDerived",
baseclass( MyDerivedClass ),
// F_Type=3;
_init_=Method({NewKey},
super:_init_(NewKey);
F_Type=3;
show("MyDoubleDerived",F_Type);
);
);
// start of the actual script
SampleObj=MyNameSp:CreateNewObj("MyDoubleDerived","Key1");
show("value From script");
show(SampleObj:F_Type);
SampleObj:MyMethod();
"value during initialization";
this:F_Type = 1;
F_Type = 1;
// above, the base class is constructed (at beginning of intermediate)
"MyDerivedClass";
F_Type = 2;
// above, the intermediate class is constructed (at the beginning of most derived)
"MyDoubleDerived";
F_Type = 3;
// above, the most derived class is constructed
ObjRef:ObjID = "Key1";
"value From script";
// below: there is really only one variable F_Type, belonging to the base class
SampleObj:F_Type = 3;
"value in method";
F_Type = 3;
this:F_Type = 3;
this:ObjID = "Key1";
MyNameSp:ARList[this:ObjID]:F_Type = 3;
le classi derivate utilizzano super:_init_ per assicurarsi che la loro classe base sia completamente costruita, quindi continuano a inizializzare la classe derivata. Ridefinire F_Type su ogni livello nasconde la classe base F_Type, non sostituirla. Non capisco perché il tuo esempio mostri F_Type=2 invece di F_Type=1 all'interno della chiamata al metodo, ma il problema scompare con il modello di utilizzo sopra.
Grandi domande. Il documento, così com'è, non lo copre nei dettagli che stai cercando... almeno nessuno che io abbia trovato.
Credo che tu abbia la corretta comprensione intuitiva su come sovrascrivere un metodo nella classe base; l'assegnazione di un nuovo metodo nell'init derivato funzionerà, per lo stesso motivo per cui F_Type funziona quando F_Type non viene "ridichiarato" nella classe derivata.
Credo anche che tu stia osservando un bug, precedentemente descritto da @hardner https://community.jmp.com/t5/Uncharted/How-to-use-Define-Class/bc-p/694620/highlight/true#M384 che L'ho in parte dimenticato, ma all'epoca ero convinto che a volte un puntatore a un oggetto potesse essere all'oggetto completamente derivato e altre volte potesse essere all'oggetto super:.
@julian : questo tipo di documento deve essere scritto da uno sviluppatore che capisca perché è utile un metodo virtuale.
Brama,
Grazie ancora per la risposta molto rapida.
Più ci gioco, più sono convinto che il mio "override" originale di variabile e metodo dovrebbe essere come dovrebbe essere e il comportamento attuale dovrebbe essere modificato.
Sfortunatamente mi sono reso conto che aggiungere il metodo in _init_ non è proprio una soluzione utile per l'ereditarietà "multilivello".
se un metodo dovesse essere naturalmente descritto nella classe "middle" non sarebbe accessibile dai livelli inferiori in quanto aggiungendo solo _init_ lo imposterà per l'oggetto attivo che creo ma non fungerà da prototipo per la classe che ne deriva.
Stesso dovrebbe essere il comportamento anche per gli immobili. Penso che dovrebbe essere in grado di dichiararli senza la necessità di _Init_. _Init_ dovrebbe essere utilizzato solo quando il valore è impostato in fase di esecuzione e non predefinito (a mio parere), la definizione in _Init_ richiederebbe di specificarlo per tutte le classi derivate non solo al livello di cui ho bisogno con un maggiore sforzo di manutenzione
Facciamo l'esempio delle forme. ho un poligono generale per il quale è necessario specificare "lati" come input in fase di esecuzione, quindi lo avrei nell'input e definito quando l'oggetto viene avviato. mi aspetto che per il quadrilatero codifichi i "lati" in modo che siano 4. per il quadrato tuttavia vorrei ereditare dal quadrilatero senza definirlo nuovamente in 4.
per i lati va bene... ma se devo inizializzare una lunga lista o un array associativo, è fastidioso ripeterlo per ogni classe.
Qual è la tua opinione?
Ho provato a utilizzare le classi più volte e ho trovato alcuni buoni casi d'uso (pass-by-reference), alcuni bug e alcune funzionalità mancanti (questo).
Sto ancora perfezionando il mio modello per come funziona, grazie per le domande e la discussione.
- Penso che sarà utile pensare che ciascuna sottoclasse abbia un proprio insieme di variabili membro simile a uno spazio dei nomi e un collegamento nascosto al successivo insieme di variabili membro della classe base successiva. E pensa a questo come a quello spazio dei nomi. Ti piacerebbe che questa fosse la classe più derivata, perché da lì puoi seguire i collegamenti alla classe più base e imparare tutto. Ciò che può andare storto, o almeno contro le aspettative, è che a volte quando viene chiamato un metodo, questo diventa lo spazio dei nomi in cui risiede il metodo. Non esiste alcun collegamento alla sottoclasse. Non avrà importanza, a meno che il metodo non preveda di chiamare funzioni virtuali nella sottoclasse. Quindi farà la differenza se la sottoclasse utilizza _init_ per sostituire la funzione virtuale nella classe base (OK) o la assegna allo spazio dei nomi della funzione virtuale (Fails).
- Sono abbastanza certo che ci siano più copie in corso di quanto speri, e viene fatto per far funzionare l'ereditarietà multilivello. Quando viene creata una classe a 3 livelli, viene copiata la classe base, poi quella centrale e infine quella più derivata. Credo che le chiamate _init_ nidificate avvengano dopo.