서로 상속되는 일련의 클래스를 작성하는 동안 문제가 발생합니다.
단순화를 위해 아래의 기본 예제로 코드를 제거했습니다.
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).
인스턴스화 중에 F_Type이 3으로 올바르게 설정되는 것을 볼 수 있을 뿐만 아니라 개체 자체에서 속성에 접근하려고 할 때도 마찬가지입니다.
그러나 메서드 내부에 있을 때 this:F_Type 또는 F_Type의 값은 어떤 이유로든 2입니다(그러나 개체를 저장하는 연관 배열에서 액세스하는 경우 값은 올바르게 3입니다). 이상하게 이 메소드는 F_Type= 1인 Base 클래스에 정의되어 있는데 왜 값이 2인지 이해할 수 없습니다. 이것이 어떤 유형의 버그일 수도 있나요? (JMP 17.1.0과 JMP 18 Early에서 모두 볼 수 있습니다. 테스트할 수 있는 이전 버전이 없습니다)
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();
다시 확인했는데 메소드에서도 동일한 동작이 적용됩니다.
다른 메서드에서 특정 메서드를 호출하고 "대상" 메서드가 상속 수준에 따라 다른 경우 스택에 있는 "한 수준 위" 클래스의 메서드가 사용됩니다(표현이 의미가 있는지 확실하지 않음).
마찬가지로 "_init_" 메서드 내에서 파생 클래스의 메서드를 정의하면 모두 예상대로 작동합니다.
변수의 경우 "_init_" 메서드에서 수행하는 논리를 이해할 수 있지만 메서드의 경우 직관적이지 않은 것 같습니다.
그것에 관한 문서가 있습니까?
수정된 예는 다음과 같습니다.
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: 클래스 상속, 제가 무엇을 놓치고 있나요?</LINGO-SUB><LINGO-BODY id="lingo-body-725797" slang="en-US" mode="CREATE"><P>답장을 보내 주셔서 감사합니다.</P><P>코드를 다시 적용하는 것은 작업을 계속할 수 있을 만큼 쉽습니다. 하지만 커튼 뒤에 무엇이 있는지 실제로 이해하지는 못합니다.</P><P>속성을 직접 호출하면 올바른 값(3)을 얻습니다. 메서드 내에서만 값이 2(1이 아님)이고 다른 수준을 추가하려고 시도했습니다(항상 n-1 수준입니다). 고려).</P><P> </P><P>또한 이는 변수에도 저장되는 메소드의 상속에 대해 또 다른 질문을 제기합니다... "_init_" 내에서 특정 메소드도 정의해야 합니까?.</P><P> </P><P> </P></LINGO-BODY><LINGO-SUB id="lingo-sub-725714" slang="en-US" mode="CREATE">Re: 클래스 상속, 제가 무엇을 놓치고 있나요?</LINGO-SUB><LINGO-BODY id="lingo-body-725714" slang="en-US" mode="CREATE"><P>이것은 아마도 당신이 원하는 것에 더 가깝습니다</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;
파생 클래스는 super:_init_를 사용하여 기본 클래스가 완전히 구성되었는지 확인한 다음 계속해서 파생 클래스를 초기화합니다. 각 레이어에서 F_Type을 재정의하는 것은 기본 클래스 F_Type을 대체하는 것이 아니라 숨기는 것입니다. 귀하의 예제에서 메서드 호출 내에서 F_Type=1 대신 F_Type=2를 표시하는 이유를 이해할 수 없지만 위의 사용 패턴에서는 문제가 사라집니다.
좋은 질문입니다. 문서는 여러분이 찾고 있는 세부 사항을 다루지 않습니다. 적어도 제가 찾은 내용은 없습니다.
나는 당신이 기본 클래스에서 메소드를 재정의하는 방법에 대해 올바른 직관적 이해를 가지고 있다고 믿습니다. 파생 클래스에서 F_Type이 "재선언"되지 않은 경우 F_Type이 작동하는 것과 같은 이유로 파생된 init에 새 메서드를 할당하면 작동합니다.
또한 이전에 @hardner https://community.jmp.com/t5/Uncharted/How-to-use-Define-Class/bc-p/694620/highlight/true#M384 에서 설명한 버그를 관찰하고 있다고 생각합니다. 부분적으로 잊어버렸지만 당시에는 객체 포인터가 완전히 파생된 객체를 가리키는 경우도 있고 super: 객체를 가리키는 경우도 있다는 것을 확신했습니다.
@julian - 이런 종류의 문서는 가상 메서드가 왜 유용한지 이해하는 개발자가 작성해야 합니다.
크레이그,
매우 빠른 답변에 다시 한번 감사드립니다.
이 작업을 많이 할수록 변수와 메서드에 대한 원래의 "재정의"가 올바른 방식이어야 하며 현재 동작이 수정되어야 한다는 확신이 더 커집니다.
불행하게도 나는 _init_에 메소드를 추가하는 것이 실제로 "다단계" 상속에 유용한 솔루션이 아니라는 것을 깨달았습니다.
메서드가 자연스럽게 "중간" 클래스에 설명되어야 하는 경우 _init_에 추가하면 내가 만든 활성 개체에 대해서만 메서드가 설정되고 파생 클래스에 대한 프로토타입으로 작동하지 않으므로 하위 수준에서 액세스할 수 없습니다.
마찬가지로 속성에 대해서도 동작해야 합니다. 내 생각엔 _Init_ 없이도 선언할 수 있어야 한다고 생각합니다. _Init_는 값이 런타임에 설정되고 미리 정의되지 않은 경우에만 사용해야 합니다(내 의견). _Init_에서 정의하면 유지 관리에 더 많은 노력을 들여 필요한 수준뿐만 아니라 모든 파생 클래스에 대해 값을 지정해야 합니다.
도형의 예를 만들어 보겠습니다. 런타임에 "측면"을 입력으로 지정해야 하는 일반 다각형이 있으므로 입력에 포함하고 개체가 시작될 때 정의됩니다. 나는 사변형의 경우 "측면"을 4로 하드 코딩할 것으로 예상합니다. 그러나 정사각형의 경우 다시 4로 정의하지 않고 사변형에서 상속하고 싶습니다.
면에 대해서는 괜찮습니다... 하지만 긴 목록이나 연관 배열을 초기화해야 하는 경우 각 클래스에 대해 이를 반복하는 것은 짜증나는 일입니다.
당신의 의견 것입니다?
나는 클래스를 여러 번 사용하려고 시도했고 몇 가지 좋은 사용 사례(참조별 전달), 일부 버그 및 일부 누락된 기능(this)을 발견했습니다.
질문과 토론에 감사드립니다. 모델의 작동 방식을 계속 개선하고 있습니다.
- 각 하위 클래스를 네임스페이스와 같은 자체 멤버 변수 집합과 다음 기본 클래스 멤버 변수 집합에 대한 숨겨진 링크로 생각하면 도움이 될 것이라고 생각합니다. 그리고 이것을 네임스페이스라고 생각하세요. 가장 기본 클래스에 대한 링크를 따라가서 모든 것을 배울 수 있기 때문에 이 클래스를 가장 많이 파생된 클래스로 만들고 싶습니다. 잘못되거나 적어도 기대에 어긋나는 경우는 때때로 메서드가 호출될 때 이것이 메서드가 존재하는 네임스페이스가 되는 것입니다. 하위 클래스로 돌아가는 링크가 없습니다. 메소드가 서브클래스에서 가상 함수를 호출할 것으로 예상하지 않는 한 이는 중요하지 않습니다. 따라서 하위 클래스가 _init_를 사용하여 기본 클래스의 가상 함수를 대체하거나(OK) 이를 가상 함수의 네임스페이스에 할당하면(실패) 차이가 발생합니다.
- 나는 당신이 기대하는 것보다 더 많은 복사가 진행되고 있으며 다중 레벨 상속이 작동하도록 수행되었다고 확신합니다. 3레벨 클래스가 생성되면 기본 클래스, 중간 클래스, 가장 많이 파생된 클래스가 복사됩니다. 나는 중첩된 _init_ 호출이 그 후에 발생한다고 믿습니다.