Daklu Posted August 23, 2009 Report Share Posted August 23, 2009 Is there a "right way" (or any way for that matter) to call child class methods on a parent object that was stuffed into a DVR? Like everyone else I've been playing around with DVRs lately, focusing primarily on ways to use object references to simplify various design pattern implementations. Today I identified a behavior that has been giving me fits for weeks. If you create an object reference to a parent object, after dereferencing the parent object there does not appear to be any way to downcast it to a child object or use dynamic dispatching to call child class methods. I built a simple example project with a parent class and a child class to illustrate this. These show a simple implicit downcast and upcast to static dispatch methods. The downcast not working isn't surprising since wiring the parent object directly into a static dispatch child method breaks the wire also. Here I tried explicitly downcasting to the child class before calling the static method. There are no bad wires but the run arrow is broken with the error, "Although you can modify the class object inside a reference, you cannot substitute the class object for another object, even if the object belongs to the same class. This restriction prevents you from changing the data type of the object in the reference." This limitation did surprise me, although I suppose it is consistent--bundling the parent in a cluster and using the in-place structure's unbundle/bundle elements generates a runtime error. The difference is the in-place unbundle/bundle elements can easily be replaced with regular unbundle/bundle nodes. There's no equivalent for DVRs. This is as close as I could come to regular unbundle/bundle nodes when using DVRs The downcast gives me a runtime error. What's really odd is that the downcast works despite the error. If I clear the error the child class correctly updates its private data in the static method and a new object reference is created for the parent. I'm not sure if I'll be able to use this for my purposes but at least it's an avenue to explore. I'm also curious which is the bug: the runtime error or the downcast actually working. I tried several other techniques as well, but I always either got a broken run arrow or a runtime error. Are there other solutions I've missed? Parent-Child DVR.zip 1 Quote Link to comment
jgcode Posted August 23, 2009 Report Share Posted August 23, 2009 (edited) Is there a "right way" (or any way for that matter) to call child class methods on a parent object that was stuffed into a DVR? Like everyone else I've been playing around with DVRs lately, focusing primarily on ways to use object references to simplify various design pattern implementations. Today I identified a behavior that has been giving me fits for weeks. If you create an object reference to a parent object, after dereferencing the parent object there does not appear to be any way to downcast it to a child object or use dynamic dispatching to call child class methods. I built a simple example project with a parent class and a child class to illustrate this. These show a simple implicit downcast and upcast to static dispatch methods. The downcast not working isn't surprising since wiring the parent object directly into a static dispatch child method breaks the wire also. Here I tried explicitly downcasting to the child class before calling the static method. There are no bad wires but the run arrow is broken with the error, "Although you can modify the class object inside a reference, you cannot substitute the class object for another object, even if the object belongs to the same class. This restriction prevents you from changing the data type of the object in the reference." This limitation did surprise me, although I suppose it is consistent--bundling the parent in a cluster and using the in-place structure's unbundle/bundle elements generates a runtime error. The difference is the in-place unbundle/bundle elements can easily be replaced with regular unbundle/bundle nodes. There's no equivalent for DVRs. This is as close as I could come to regular unbundle/bundle nodes when using DVRs The downcast gives me a runtime error. What's really odd is that the downcast works despite the error. If I clear the error the child class correctly updates its private data in the static method and a new object reference is created for the parent. I'm not sure if I'll be able to use this for my purposes but at least it's an avenue to explore. I'm also curious which is the bug: the runtime error or the downcast actually working. I tried several other techniques as well, but I always either got a broken run arrow or a runtime error. Are there other solutions I've missed? Hi Daklu I have been playing with LVOOP and DVR also! It is very cool stuff. I had a look at your post as it is very interesting. I think removing the references to investigate the problem may help to solve the issue. Below is what I have come up with. Parent cannot downcast to Child as Parent cannot run extended Child methods (Child can always run all Parent Methods) so you get a broken run arrow and that makes sense, any other casting will generate a run time error (see attached png top). The downcast gives me a runtime error. What's really odd is that the downcast works despite the error. If I clear the error the child class correctly updates its private data in the static method and a new object reference is created for the parent. When you use the Preserve Run Time Class function I don't think you are getting a new parent, I think you are getting a new child: When the error occurs, all data on the wire is lost and a new child is created. You can then do what you like with the child (see attached png middle). I think the references may be hiding all this in the example you posted as you can have a parent reference pointing to a child object (see attached png bottom). Therefore I do not think it is a bug either way? Would you agree or am I totally off! Edited August 23, 2009 by jgcode 1 Quote Link to comment
MikaelH Posted August 23, 2009 Report Share Posted August 23, 2009 Hi I can’t help you much there. The design I’m looking into is to have the class-data (attributes) as a TypeDef belonging to the class and then put it into the Data Value Reference (DVR) wire. That then goes into the lvclass private data. A update method could then look like this. By using this method the dynamic dispatching will work as usually without having to put an In Place Element Structure around all abstract methods. Cheers, Mikael 1 Quote Link to comment
Popular Post Aristos Queue Posted August 23, 2009 Popular Post Report Share Posted August 23, 2009 Daklu, the style of my writing below is fairly terse and occassionally EMPHATIC. I've done this to emphasize key points that I think you've missed in How Things Work. Some customers in the past have felt I'm insulting them writing this way, but it is the only way I know through the limited text medium to highlight the key points. My only other option is to post just the key bits and leave out a lot of the exposition, but that doesn't seem to be as helpful when communicating. So, please, don't think I'm calling you dumb or being disdainful. I'm trying to teach. The problem is that you're almost right. Customers who are completely off-base are easier to teach because they need the whole lesson. Here, I'm just trying to call out key points, but presenting them in their full context to make sure it's clear what fits where. Throughout the post, refer to the graphic at the end of the post as it may clarify what I'm talking about. Dalku wrote: Is there a "right way" (or any way for that matter) to call child class methods on a parent object that was stuffed into a DVR? No. If there is, that's a bug that needs to be reported to NI ASAP. Think about what you just asked for... ignore the DVR part for a moment. You just asked for a PARENT object to invoke a CHILD class method. That cannot ever happen. You cannot pass a parent object directly to a function that takes a child object. The parent object in question IS NOT a child -- the parent object does not have the child's private data, nor does it have all the methods that may have been defined on the child class. For this reason, LV will break the wire if you try to wire a parent wire to a child terminal -- the wire is broken because there are zero situations in which this can successfully execute. You CAN pass a child object to a parent terminal. That is because a child IS an instance of the parent -- it has all the necessary data and methods defined to act as a parent object. What you can do is take a child wire, up cast it to a parent wire and make a Parent DVR out of that. Alternatively, you could take a child wire, make a Child DVR wire, and then upcast that to a Parent DVR wire... these two processes produce the idenitical result: a parent DVR that contains child data. If you create an object reference to a parent object, afterdereferencing the parent object there does not appear to be any way todowncast it to a child object or use dynamic dispatching to call childclass methods. I built a simple example project with a parent class anda child class to illustrate this. Upcast and downcast DO NOT create new objects EXCEPT when they return an error. The point of a cast is to say, "I have an existing object. Please check that it is this type and approve it to go downstream if it passes this test." You use this only when you need to do something for a specific type of object and you do not have the ability to edit the parent and child classes in order to add the appropriate dynamic dispatch VIs to both. Preserve Run-time Class (PRTC) is the same thing. "Allow this object to pass downstream if it passes this test, otherwise create a new object that does pass the test." The test in this case is "Does the object in have the same TYPE AT RUN TIME as the OBJECT (not the wire) on the target object input?" If the left object is the same or a child class of the center object then there is no error. You will ALMOST NEVER WIRE PRTC WITH A CONSTANT FOR THE CENTER TERMINAL. I would say "never" because I can't think of any useful cases, but maybe someone has something out there. If you are wiring the center terminal of PRTC with a constant, something is wrong in your code. You use the PRTC to assert that the left object, which comes from some mystical source, is the right type to fulfill run-time type requirements of dynamic dispatch VIs, automatic downcast static VIs, and the Lock/Unlock of Data Value References. In all three of these cases, there is some input (either the input FPTerm or the left side of the Inplace Elt Struct) that must be passed across to the output (either the output FPTerm or the right side of the Inplace Elt Struct) WITHOUT PASSING THROUGH ANY FUNCTION THAT CHANGES THE OBJECT'S TYPE. You're free to change the object's value, but not its type. Sometimes you pass the object to functions where LV cannot prove that the type is maintained. Easy example -- pass the object into a Global VI and then read the Global VI. You'd never do this, of course, but it demonstrates the problem. LV cannot know that the object you read from the global is the same object you wrote in -- some other VI elsewhere might have written to the global in parallel. But you, as the programmer, know that there are no other writes to the global VI, so you use the PRTC to assert "this is going to be the right object type." You wire the original input (as described above) to the center terminal, and the output of the global to the left terminal, and pass the result to the original output terminal (as described above). Does that make sense? 9 Quote Link to comment
Daklu Posted August 23, 2009 Author Report Share Posted August 23, 2009 The design I’m looking into is to have the class-data (attributes) as a TypeDef belonging to the class and then put it into the Data Value Reference (DVR) wire. I saw that over on the LVOOP thread and it does provide a lot of benefits, but I not sure it quite solves the larger problem I'm trying to address. (If I could describe the problem without causing you a severe case of MEGO ("my eyes glaze over") I would, but I still don't understand the problem enough to do it concisely.) When the error occurs, all data on the wire is lost and a new child is created. Ahh, this is key information I missed. I thought the parent class data was preserved in the child class. So, please, don't think I'm calling you dumb... Meh... you wouldn't be far off. Customers who are completely off-base are easier to teach because they need the whole lesson. I'm the backyard mechanic who knows just enough to really screw things up. Think about what you just asked for... ignore the DVR part for a moment. You just asked for a PARENT object to invoke a CHILD class method. Yeah, when you put it that way it's apparent why the example doesn't work. Too much time in the trenches makes it easy to miss the obvious. The example I posted is a simplified representation of what I'm trying to do; I'll have to see if I can create a more accurate model to experiment with. Upcast and downcast DO NOT create new objects EXCEPT when they return an error. Ahh... I didn't know this either. It says so right in the help file but every single one of the dozen times I looked at it I read it as "the function returns an error and the data value of the object out is of the same wire type as the object in" instead of what it really says, "...same wire type as the target object." (In fact, it wasn't until after I pasted that quote here to show you that the documentation was wrong that I discovered my mistake.) Does that make sense? Not yet, but there's lots of stuff here for me to digest. I really appreciate the detailed response. It can be hard getting a good overall view of things by picking up tidbits of information here and there. I'm sure I'll be back with more questions later. 1 Quote Link to comment
Daklu Posted August 23, 2009 Author Report Share Posted August 23, 2009 The candles are starting to flicker to life... You will ALMOST NEVER WIRE PRTC WITH A CONSTANT FOR THE CENTER TERMINAL. I read that and immediately wondered why the first case causes a broken run arrow and the second doesn't. (Hey, it was in CAPS... it was easy to skip the preceeding sentences.) Then the word "object" in the PRTC context help hit me over the head. From my end user perspective, previous versions of Labview don't make a distinction between classes and objects. (For example, the To More Specific Class prim has 'Target Class' as an input that in reality accepts an object.) Despite my attempts to differentiate between the two when posting, in my head I often use the terms interchangably. I'll have to work hard to change my way of thinking. I'd give you more kudos if I could... 1 Quote Link to comment
Daklu Posted August 24, 2009 Author Report Share Posted August 24, 2009 I'll have to see if I can create a more accurate model to experiment with. Thanks to AQ's explanation above, some solitude courtesy Nickelback and loud speakers, and fixing a problem with circular dependencies, I was able to develop a better model of what I was trying to do and get it to work! This model isn't exactly what I'm doing in my real code, but it's closer. I have a reusable code package with two classes developers can use in their applications either directly or as parent classes for their own application-specific classes. In this example parent 1 is a simple counter with Increment Counter and Get Counter Value methods while parent 2 is a helper class that operates on parent 1 and makes a "Multiply by 2" method available. (They are shown with abstract methods but they could be implemented.) While writing an application a programmer realizes he needs a counter object that can also count down and handle negative numbers, but he can't change the reuse code package. He creates Child 1 to extend Parent 1 and adds the decrement method. Now his counter can count up and down. To correctly multiply negative numbers by 2 he creates Child 2 and overrides the Mult by 2 method. Inside that method he uses Parent1:GetCtr to check if the counter is positive or negative. If it is positive he uses Child1:IncCtr to provide the x2 functionality; if it is negative he uses Child1:DecCtr. The problem was that I couldn't figure out how to use Child1 methods inside the InpEl:DVR structure even though the value on the wire at runtime is a Child1 object. That's when I started floundering and ended up with the example posted at the start of this thread. But now, thanks to the fine folks on LAVA, I see where my assumptions were wrong and things are much more clear. *sniff* I love you guys... *sniff* It might take me a bit longer to make it around the block, but I'll get there eventually. Here's the test vi I wrote and the code for Child2:MultBy2. DVR Composition.zip Quote Link to comment
Aristos Queue Posted August 24, 2009 Report Share Posted August 24, 2009 Glad to hear it worked out. 1 Quote Link to comment
K-node Posted September 4, 2009 Report Share Posted September 4, 2009 This model isn't exactly what I'm doing in my real code, but it's closer. I have a reusable code package with two classes developers can use in their applications either directly or as parent classes for their own application-specific classes. In this example parent 1 is a simple counter with Increment Counter and Get Counter Value methods while parent 2 is a helper class that operates on parent 1 and makes a "Multiply by 2" method available. (They are shown with abstract methods but they could be implemented.) While writing an application a programmer realizes he needs a counter object that can also count down and handle negative numbers, but he can't change the reuse code package. He creates Child 1 to extend Parent 1 and adds the decrement method. Now his counter can count up and down. To correctly multiply negative numbers by 2 he creates Child 2 and overrides the Mult by 2 method. Inside that method he uses Parent1:GetCtr to check if the counter is positive or negative. If it is positive he uses Child1:IncCtr to provide the x2 functionality; if it is negative he uses Child1:DecCtr. Daklu, I was wondering if you tried to implement this using SciWare's approach of having the class's data members as a single DVR of a TypeDef and not creating a DVR of the object itself (see http://lavag.org/topic/10666-labview-oop-by-ref/). I played around with it a bit on your example and it does clean up the 'Application' part of the VI. All of the IPEs can be inside Get/Set methods. Makes the application programmer's job a bit easier in my opinion. There was an ensuing debate on whether this approach (DVR of data member cluster) was both 'in the spirit' of NI's implementation and safe from a race or deadlock conditions. SciWare any further thoughts on the latter issue? I do not see much risk beyond what is 'expected' when you share references to the same memory across threads. The project I am working on has a fairly deep class hierarchy. One branch of the hierarchy represents hardware components - motors, actuators, sensors, analog outputs. Another branch represents assemblies of these components (or other assemblies) - arms, grippers, thermal baths. My original thought was to have the assemblies hold a DVR to the hardware components. But without the ability of Dynamic Dispatch on a DVR the IPEs were simply too cumbersome. So, in comes SciWare's idea and the IPEs are now buried in the classes Get/Set (sorry NI, Read/Write) methods. Yes, I am worried about deadlock conditions. And a bit worried about performance, but since I am controlling slow mechanical devices (e.g. benchtop robots), speed is not really a concern. I am hoping that others out there are also trying this. I too will give some feedback as my design get implemented a bit further. Quote Link to comment
Daklu Posted September 6, 2009 Author Report Share Posted September 6, 2009 Daklu, I was wondering if you tried to implement this using SciWare's approach of having the class's data members as a single DVR of a TypeDef and not creating a DVR of the object itself (see http://lavag.org/top...iew-oop-by-ref/). I played around with it a bit on your example and it does clean up the 'Application' part of the VI. All of the IPEs can be inside Get/Set methods. Makes the application programmer's job a bit easier in my opinion. The short answer is no. I don't quite understand exactly what changes you made. If you can post images of your code it would help me understand your implementation. (My LV'09 trial license expired and I haven't got a home license from work yet.) The project I am working on has a fairly deep class hierarchy. Danger, Will Robinson! Deep inheritance hierarchies aren't very malleable and you may find yourself unable to accomodate future changes. Or worse, you could end up hacking in modifications and ending up with very brittle code. Can you post a diagram of your object model? Quote Link to comment
Aristos Queue Posted September 6, 2009 Report Share Posted September 6, 2009 Danger, Will Robinson! Deep inheritance hierarchies aren't very malleable and you may find yourself unable to accomodate future changes. Or worse, you could end up hacking in modifications and ending up with very brittle code. Can you post a diagram of your object model? Really? My opinion has generally been that deep hierarchies mean that you've adequately decomposed objects and you can inherit in the future off of any of the intermediate levels instead of being stuck with one concrete class that has everything. I've always thought that the deep hierarchies are highly extensible. Quote Link to comment
Daklu Posted September 7, 2009 Author Report Share Posted September 7, 2009 Really? My opinion has generally been that deep hierarchies mean that you've adequately decomposed objects and you can inherit in the future off of any of the intermediate levels instead of being stuck with one concrete class that has everything. I've always thought that the deep hierarchies are highly extensible. This is exactly why I love LAVA. I can voice an opinion and have more experienced developers (or the lead R&D developer in charge of LVOOP!) challenge my ideas. I'm writing this response with the intent of explaining how I came to the conclusion that deep trees are bad, not as an attempt to teach you (as if!) the finer points of OO programming. I'm more than happy to be shown the error of my ways. I'll also note that a "class heirarchy" implies, in my mind, inheritance relationships. First, I think there are subtle differences between "malleable" and "extensible." Both refer to ways of altering functionality; however, extensible code adds functionality through 'bolt-on' additions while malleable code adds functionality by changing the way the objects are organized. As a metaphor, image the code is represented by marbles in the shape of a triangle on a chinese checkers board. Extensible code allows changing the triangle into a circle by adding marbles to it. Malleable code allows changing the triangle into a circle by moving the marbles that are already there. In my opinion, deep class hierarchies may be highly extensible, but they are rarely malleable. My opinion is based partly on my experiences and partly what I've learned from OOP literature. One of the first lessons I learned when I started "seriously" studying OO design was to favor composition over inheritance. Various books and websites have pointed out that inheritance relationships are fairly tightly coupled. Creating a long inheritance chain causes dependencies that are not easily changed later, especially when there is existing code that depends on that chain. Learning that, I realized why the class hierarchies I've worked with have ultimately fallen short of what we needed them to do. In the past several years I've worked with two fairly deep framework class hierarchies and neither ended up being able to properly support the functionality we needed without completely redesigning the tree. Undoubtedly some of the problem was due to our inexperience with OO design. However, looking back on it now I still do not see inheritance providing a good solution to the problems we faced. Our hierarchies, like kugr's, were based on hardware devices, so when he says one branch of his "deep class hierarchy" is devoted to "motors, actuators, sensors, [and] analog outputs," the warning bells start ringing. If this branch is based primarily on inheritance he may end up in exactly the same situation I did. Having been burned twice by deep inheritance trees, my design philosophy now leans heavily the other way. Instead of trying to fit hardware devices with different capabilities onto the same inheritance tree, now I look to have several short and small inheritance trees. Many small trees gives me malleability. Extensibility tends to be achieved by creating an abstract facade and creating child classes that are composed of the parts of the smaller trees that I need. Quote Link to comment
Aristos Queue Posted September 7, 2009 Report Share Posted September 7, 2009 It is a philosophy question, and I doubt there is a right answer. "Favor composition over inheritance" is a battle cry I have heard a lot over the last few years. Composition means I can "plug in" functionality from lots of sources. The trouble, as I see it, is that you end up writing a lot of wrapper functions in order than the outer object can expose the inner objects' APIs as its own. Now, the theory is that the inner object's API can change completely and as long as the new API can still support the old functionality, the outer API doesn't have to change -- you change the implementation of those wrappers, not the outer API. But what I observe is that it is rare to want to change the inner API and not change the outer one. Perhaps you're adding a new option, or you're breaking one atomic function into several individual parts. There's usually a reason you're doing that, and it is rare -- in my experience -- that the reason you're doing it is just so you can have a better internal API. Changes to deeper layer APIs are almost always driven by top-level demands for features. Or, to put it another way, it is rare that the core decides, "I'm going to change how I do things because it will be better." It is much more common for the outermost layer to say, "I need to serve the user differently... but I can't do that unless the core exposes some new functionality." Given that the desire for a new API is almost always driven by the layer closest to the user, when the core changes, the outer layer will want to change anyway, so if there's a tight binding (inheritance) between core and outer layer, so what? Not having that binding doesn't generally seem to save you. Sure you *could* avoid rewriting the outer layer when the core changes, but if the whole point of changing the core is so you *can* rewrite the outer, then you're going to rewrite the outer. Yes, I spend a lot of time changing inheritance trees around -- introducing new middle layer classes, wiping them out again a couple revisions later. I've got one tree of classes I'm working on at the moment where each layer of inheritance is adding one function, and one "concrete" class forking off of the trunk at each level. As I revise each of the concrete classes, they're sliding down the tree, gaining functionality, and I'll probably collapse a lot of the middle layers eventually. I find that the chain of inheritance is useful for navigating my code and I don't see a major drawback to refactoring the tree. Very few classes are persisted to disk, so that's rarely an issue, and in LV, the mutation logic built into the language generally handles everything I need it to. 1 Quote Link to comment
K-node Posted September 8, 2009 Report Share Posted September 8, 2009 The short answer is no. I don't quite understand exactly what changes you made. If you can post images of your code it would help me understand your implementation. (My LV'09 trial license expired and I haven't got a home license from work yet.) I will work on that. I kind of destroyed it when I was playing with it further. I was trying different approaches to the structure. Should have left it alone while it was working. Danger, Will Robinson! Deep inheritance hierarchies aren't very malleable and you may find yourself unable to accomodate future changes. Or worse, you could end up hacking in modifications and ending up with very brittle code. Can you post a diagram of your object model? Perhaps deep is a relative term (there are only 5 levels). I will post a diagram soon. I need to grab Visio or something. I work for a huge company and it takes a bit longer to get through the red tape of allocating a license. Anyway, so far it has worked for me. I am using SciWare's approach regarding making the class's private data cluster contain a single DVR to a Strict TypeDef (I can also see advantages to multiple DVRs). I have hit some deadlock situations where IPEs have competed, but that was rather poor implementation on my part. I know I will hit more as I progress, but it seems better appraoch from a class usability standpoint. All of the IPEs are held inside the class's methods (typicaly data access methods). The calling VIs do not know anything about DVRs and DynamicDispatch works great. What is a real pain is debugging. Putting a probe on a wire that represents an object AND that object uses DVRs as member variables - all you see is the refnums! I am not surprised at this. I would like a way to dereference these values in the debugger. Am I missing something? Quote Link to comment
Daklu Posted September 10, 2009 Author Report Share Posted September 10, 2009 Given that the desire for a new API is almost always driven by the layer closest to the user, when the core changes, the outer layer will want to change anyway, so if there's a tight binding (inheritance) between core and outer layer, so what? Not having that binding doesn't generally seem to save you. Sure you *could* avoid rewriting the outer layer when the core changes, but if the whole point of changing the core is so you *can* rewrite the outer, then you're going to rewrite the outer. I agree with everything you said, but this last paragraph highlights why I now avoid deep inheritance trees. We have an internal hierarchy that includes classes for various instruments, products that need to be tested, protocols, etc. The intent is to use this api as the foundation for all our test tools. If we come across a new requirement the api doesn't support, we *can't* change the outer layer without breaking other applications. So we either have to fork the api for that application (which we have done) or implement hacks (which we have also done.) As you say, there is no "right" answer. It's just a matter of picking your poison. I need to grab Visio or something. I actually like Star UML better than Visio. Plus it's free. I am using SciWare's approach regarding making the class's private data cluster contain a single DVR to a Strict TypeDef. I finally had a chance to look at the Bike/Racer implementation this morning. That looks like a very good way to create by ref class hierarchies. I'll have to play with it more to see how it fits into my use case. (Which means I'll have to remember what my use case was... ) Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.