LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Dynamically initialize private variables in child classes

Solved!
Go to solution

I have a minimal LVOOP code, which manages a number of qualitatively similar tasks, depending on user input. Each selectable task is a child class of one main parent class.

Overwriting the "do" method of the parent class with specific child classes works.

However, each child class requires potentially different input variables to properly work (and thus have different connector panes).

 

My current (non-working) approach was to use an enum with a case structure that calls the correct initialization function and input variables for each child class. However, there is an error on the wires connecting to the initialization subvis. It states:

 

"You have connected a refnum of one type to a refnum of another type and both types are members of some class hierarchy, but there is neither a simple up cast nor type cast between the two classes."

 

blockDiagram.png

 

 

 

 

 

 

 

 

 

So my questions are:

0) Is having child classes with differing private data incompatible with LVOOP? Is it better to get rid of the classes again?

If not:

1) How can I get rid of this error?

2) Should I use a different approach? (I would prefer to not bundle all possible private data clusters into a "supercluster", so that I have identical input to each child initialization method.)

3) Should I use a variant datatype to exploit polymorphic class methods?

4) Something else?

 

Attached is a minimal working example (LV 2018 64 Bit, Win10)

 

EDIT:

If casting to more specific type is necessary, should I wonder about any performance/memory issues?

Message 1 of 9
(4,883 Views)

Make your life easier.  Get rid of the dynamic dispatch here.  It doesn't make sense.

 

If you're initializing the object, you know what it is already so you can call a constructor with unique naming.  As it stands, you're going through the process of having multiple VIs to init the object.  Wouldn't you prefer to just have the one?  You're really looking to update the values that already exist (the default values).

 

Create Minus
Create Plus

etc

 

This lets you have the best of both worlds.  In the child classes, you can call the parent's Create method to update the shared value (in fact, you'll have to.  While the child inherits the property, it doesn't have direct access to it)

 

Also, get into the habit of keeping all of your classes contained within a folder (I prefer naming the folder the same as the class).  As it stands, you've got the parent class loose in the same folder as the project.  That will become miserable to maintain in larger projects (and is terrible for reuse).

0 Kudos
Message 2 of 9
(4,859 Views)

@ natasftw:

Your approach works, but it sidesteps the underlying problem a bit 😉

This is what I got for a create method:

create_method.png

 

However, the underlying problem seems to be that after a child class enters a polymorphic parent method (with dynamic dispatch), it returns the parent class. If this parent class hits a non-inherited child method (also with DD), the wire throws the above mentioned error.

 

Consider for example a subvi, where the parent method can not be absorbed into a child initialization method:

blockDiagram2.png

 

One solution to the underlying problem might be to cast the parent class (on which a child class travels) to the desired child class. However, it does not seem very elegant, or is it?

 

Updated archive is attached (with classes separate from project file 😉

0 Kudos
Message 3 of 9
(4,849 Views)

@labjan wrote:

However, the underlying problem seems to be that after a child class enters a polymorphic parent method (with dynamic dispatch), it returns the parent class. If this parent class hits a non-inherited child method (also with DD), the wire throws the above mentioned error.


No, it's the output of the case structure that does that. Each case has a different child, and their class in common is the parent. So the output is the parent class. If they don't have a parent in common, it will become the LabVIEW class (which is the parent all classes have in common).

 

Each child should have it's own init named after the child. Init A.vi, Init B.vi. These should be static. You can call the Init of the parent after that, and each child can overwrite that method so do specific actions on that initialization data.

 

Another option is to make all Inits the same. For instance, by giving the input a reference to an ini file. Each child can read from that ini file, and all CPs would be the same. Instead of an ini file, a configuration class, a file reference in general, even a DVR or cluster would work. This would force you to evolve the configuration to a higher level...

Message 4 of 9
(4,812 Views)

wiebe@CARYA wrote:


No, it's the output of the case structure that does that. Each case has a different child, and their class in common is the parent. So the output is the parent class. If they don't have a parent in common, it will become the LabVIEW class (which is the parent all classes have in common).


Thanks for the clarification. Your point can be directly seen in the block diagram. I got thrown off by an unchanged variable name, even though the variable datatype/class changed.

 


wiebe@CARYA wrote:

Each child should have it's own init named after the child. Init A.vi, Init B.vi. These should be static. You can call the Init of the parent after that, and each child can overwrite that method so do specific actions on that initialization data.


1) I think I see what you mean. Since the child init methods are not related to a parent init method (and there are no grandchildren inheriting them), they should be static and have distinct names. Right? The naming scheme was just based on confusion-inducing laziness on my part. Sorry 😉

 


wiebe@CARYA wrote:

Another option is to make all Inits the same. For instance, by giving the input a reference to an ini file. Each child can read from that ini file, and all CPs would be the same. Instead of an ini file, a configuration class, a file reference in general, even a DVR or cluster would work. This would force you to evolve the configuration to a higher level...

2) The configuration class sounds interesting. Do you have more info on that?

 

3) The main question I am left with now is this: Say I have a parent class with some child classes. After the initialization phase is over, they all travel through the block diagram as a parent class, right? If I want to modify the child classes individually later on, how would I do that?

 

Is the "To more specific class" function the way to go? (See the while loop in the subvi in my 2nd post above).

0 Kudos
Message 5 of 9
(4,793 Views)
Solution
Accepted by topic author labjan

If you're putting all of the objects into an array to pass them as a single wire, yes they'll be an array of parents.  Though, the children objects will still know what they are.

 

If you're feeding this into VIs with dynamic dispatch, that shouldn't be a problem.  If you're trying to wire into class specific VIs, then yes you'll want to use the "To More Specific Class" on the objects you're wanting to pass into that VI.

0 Kudos
Message 6 of 9
(4,779 Views)

@labjan wrote:

wiebe@CARYA wrote:

Another option is to make all Inits the same. For instance, by giving the input a reference to an ini file. Each child can read from that ini file, and all CPs would be the same. Instead of an ini file, a configuration class, a file reference in general, even a DVR or cluster would work. This would force you to evolve the configuration to a higher level...

2) The configuration class sounds interesting. Do you have more info on that?


This could be anything really, as long as it's one type.

 

A class makes most sense, but a reference of whatever kind would work too. Even a type def'd cluster does the trick.

 

All the inits can accept this type, and use it to get their information from it. This means the inits can all have the same interface, and so can inherit from the parent.

 

The init can for instance be a configuration class (undocumented), and each child can simply read their keys\values from it. But a LV configuration file reference would work just as well. 

 

Resist the urge to embed the class\reference in the private data of the parent or child classes. Using a resource is much lighter coupling then containing the resource! Lighter coupling is better...

0 Kudos
Message 7 of 9
(4,769 Views)

@labjan wrote:

3) The main question I am left with now is this: Say I have a parent class with some child classes. After the initialization phase is over, they all travel through the block diagram as a parent class, right? If I want to modify the child classes individually later on, how would I do that?

 

Is the "To more specific class" function the way to go? (See the while loop in the subvi in my 2nd post above).


I concur with @natasftw's answer.

 

Just want to mention that if you need To More Specific a lot, you might not be doing it right.

 

The hole point of polymorphism is that you don't care which child it is.

 

If you're building an array of different child objects, TMS will be terrible in most cases.

 

If you need a TMS, it might be something the class should be doing. A method to tell the parent and child to do it will avoid the TMS in the unrelated part of the code. This will 'pollute' the parent, so it's not 'a one size fits all' solution. Sometimes TMS it is appropriate...

 

0 Kudos
Message 8 of 9
(4,765 Views)

Thanks to everyone who helped me understand this better. Even though I picked one reply as a solution all of them were helpful.

0 Kudos
Message 9 of 9
(4,721 Views)