Me encuentro con un problema mientras escribo una serie de clases que se heredan entre sí.
Eliminé el código al ejemplo básico siguiente para simplificar.
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).
Veo que durante la creación de instancias, F_Type se establece correctamente en 3, así como cuando intento acceder a la propiedad desde el objeto mismo.
Sin embargo, cuando estoy dentro de un método, el valor de this:F_Type o F_Type es por alguna razón 2 (pero si accedo desde la matriz asociativa que almacena el objeto, el valor es correctamente 3). Por extraño que parezca, el método está definido en la clase Base (que tiene F_Type = 1, por lo que no puedo entender por qué el valor es 2. ¿Podría ser esto un error de algún tipo? (Veo esto tanto en JMP 17.1.0 como en JMP 18 Early lanzamiento, no tengo una versión anterior disponible para probar)
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();
Verifiqué dos veces y el mismo comportamiento se aplica también a los métodos.
Si llama a un método específico desde otro método y el método "destino" difiere en el diferente nivel de herencia, se utiliza el método de la clase "un nivel superior" en la pila (no estoy seguro de que la redacción tenga sentido).
De manera similar, si defino el método de la clase derivada desde el método "_init_", todo funciona como se esperaba.
Mientras que para las variables puedo entender la lógica de hacerlo en el método "_init_", para los métodos parece contrario a la intuición.
¿Hay alguna documentación al respecto?
aquí el ejemplo modificado:
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">Re: Herencia de clases, ¿qué me falta?</LINGO-SUB><LINGO-BODY id="lingo-body-725797" slang="en-US" mode="CREATE"><P>Gracias por la respuesta.</P><P>Si bien readaptar el código es lo suficientemente fácil como para poder seguir trabajando, no entiendo realmente lo que hay detrás de las cortinas.</P><P>cuando llamo a las propiedades directamente obtiene el valor correcto (3), es sólo desde dentro del método que el valor es 2 (y no 1) también intenté agregar otro nivel (y siempre es el nivel n-1 que se considera).</P><P> </P><P>Además, esto plantea otra pregunta: ¿qué pasa con la herencia de métodos que también se almacenan en variables? ¿Debería definir también los métodos específicos desde "_init_"?</P><P> </P><P> </P></LINGO-BODY><LINGO-SUB id="lingo-sub-725714" slang="en-US" mode="CREATE">Re: Herencia de clases, ¿qué me falta?</LINGO-SUB><LINGO-BODY id="lingo-body-725714" slang="en-US" mode="CREATE"><P>Probablemente esto esté más cerca de lo que quieres.</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;
las clases derivadas usan super:_init_ para asegurarse de que su clase base esté completamente construida, luego continúan para inicializar la clase derivada. Redefinir F_Type en cada capa oculta la clase base F_Type, no la reemplaza. No entiendo por qué su ejemplo muestra F_Type=2 en lugar de F_Type=1 dentro de la llamada al método, pero el problema desaparece con el patrón de uso anterior.
Grandes preguntas. El documento, tal como está, no lo cubre con los detalles que busca... al menos ninguno que yo haya encontrado.
Creo que tienes la comprensión intuitiva correcta sobre cómo anular un método en la clase base; asignar un nuevo método en el init derivado funcionará, por la misma razón que F_Type funciona cuando F_Type no se "redeclara" en la clase derivada.
También creo que está observando un error, descrito anteriormente por @hardner https://community.jmp.com/t5/Uncharted/How-to-use-Define-Class/bc-p/694620/highlight/true#M384 que Lo he olvidado en parte, pero en ese momento estaba convencido de que a veces un puntero de objeto podría ser el objeto completamente derivado y otras veces podría ser el super: objeto.
@julian : este tipo de documento debe ser escrito por un desarrollador que comprenda por qué un método virtual es útil.
Craig,
Gracias de nuevo por la respuesta tan rápida.
Cuanto más juego con esto, más convencido estoy de que mi "anulación" original de variable y método debería ser como debería ser y el comportamiento actual debería modificarse.
Desafortunadamente, me di cuenta de que agregar el método en _init_ no es realmente una solución útil para la herencia "multinivel".
Si un método debe describirse naturalmente en la clase "intermedia", no será accesible para los niveles inferiores, ya que agregarlo al _init_ solo lo configurará para el objeto activo que creo, pero no actuará como prototipo para la clase que se deriva de él.
Lo mismo debería ser el comportamiento también para las propiedades. Creo que debería poder declararlos sin la necesidad de _Init_. _Init_ debe usarse solo cuando el valor se establece en tiempo de ejecución y no está predefinido (en mi opinión), definirlo en _Init_ requeriría que lo especifique para todas las clases derivadas, no solo en el nivel que necesito con más esfuerzo de mantenimiento.
Hagamos el ejemplo de las formas. Tengo un polígono general al que debes especificar "lados" como entrada en tiempo de ejecución para que lo tenga en la entrada y lo defina cuando se inicie el objeto. Espero que para el cuadrilátero codifique los "lados" para que sean 4. Sin embargo, para el cuadrado me gustaría heredar del cuadrilátero sin definirlo nuevamente en 4.
para lados está bien... pero si necesito inicializar una lista larga o una matriz asociativa, es molesto repetirlo para cada clase.
¿Cuál es tu opinión?
Intenté usar las clases varias veces y encontré algunos buenos casos de uso (paso por referencia), algunos errores y algunas características faltantes (esto).
Todavía estoy perfeccionando mi modelo sobre cómo funciona, gracias por las preguntas y la discusión.
- Creo que será útil pensar que cada subclase tiene un conjunto de variables miembro propio similar a un espacio de nombres y un enlace oculto al siguiente conjunto de variables miembro de la clase base. Y piense en esto como ese espacio de nombres. Le gustaría que esta fuera la clase más derivada, porque desde allí puede seguir los enlaces a la clase más básica y aprender todo. Lo que puede salir mal, o al menos en contra de las expectativas, es que a veces, cuando se llama a un método, éste se convierte en el espacio de nombres donde reside el método. No hay ningún vínculo con la subclase. No importará, a menos que el método espere llamar a funciones virtuales en la subclase. Por lo tanto, habrá una diferencia si la subclase usó _init_ para reemplazar la función virtual en la clase base (OK) o la asignó al espacio de nombres de la función virtual (Fail).
- Estoy bastante seguro de que se están realizando más copias de las que esperaba, y se hace para que la herencia multinivel funcione. Cuando se crea una clase de 3 niveles, se copia la clase base, luego la del medio y luego la más derivada. Creo que las llamadas _init_ anidadas ocurren después de eso.