LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Silly & basic question - where did my child data go?

I'm having a brain fart.  I thought it'd neat to make an abstract class called "IO Data", with 2 child classes - analog IO and digital IO data.  The parent class has no data of its own.  So I followed the well-worn Factory Pattern path and loaded objects of each child into parent objects.  Ok.  So now - how do I access the children's data after they've been rolled into the parent objects?  I show a DD method on the bottom part, just to make sure it actually compiled.  Still confused about OOP, paul

0 Kudos
Message 1 of 27
(2,385 Views)

Given your question, I'm not sure you've understood why someone implements the Factory Pattern.

 

The most important takeaways regarding OOP are:

  • Easier re-use by inheriting existing functionality. Note there are other ways to enable re-use (eg. SubVIs for logic) that don't require OOP.
  • Enabling run-time selection of which method (ie. Dynamic Dispatch VI) to call based on the provided instance. In LabVIEW, this is possible without OOP but requires a lot more complexity and effort. This is the biggest gain of using OOP / LVOOP.
  • Easier (enforced by compiler) encapsulation of data. Again, there are many other ways to do this (SubVIs, FGVs / AEs are an example of this and they aren't LVOOP) and OOP doesn't bring that much extra to the table. This is simply a design protection mechanism that makes it easier, as a developer, to reason about the code-base.

I think your problem is that you are too focused on the data . The advantage of OOP is not the data - the data is simply a means to an end. The advantage of OOP is being able to change or enhance behaviour (ie logic), to create a seam in your code where you can either change behavior at runtime or enable a caller to leverage different functionality without changing the caller code. This is why the connector panes of DD VIs must match - otherwise the calling code would need to change. Class data is an implementation detail of the class to enable it to function but that's a "how" of OOP, not the "why".

 

Using your example - you have classes based around data (analog, digital). You need to think about the user of this class hierarchy. The person who calls methods on these objects - from their perspective, what does the method do? What data does it need from the caller and what will it return? DD methods (and thus class hierarchy) are defined based on the use cases of the end user.

 

I think you might gain some more insight looking at some generic reference material regarding the why of OOP. It will make your designs in LVOOP more robust.

Message 2 of 27
(2,368 Views)

RE not understanding the rationale for Factory Pattern - Absolutely! I'm about 1.5 weeks into PVOOP-land!  I reckon I should have clarified - my little doodle isn't "shipping code" - in my app I'll use the "Get LV Default Class Data", using paths stored in an ini file to load desired child classes.   So yep, I'm going to follow the well-worn path, all that stuff.  They had an example in the LVOOP traing class I just did...

 

BUT - anyway so here's where I was headed:  I want to use Variant Attributes to configure my IO Sets/Gets/Configuring.  I saw Elijah K's little treatise on how you could use them to implement a lookup table to quickly find, then select appropriate measurement classes to implement.  So I'm planning to have a config ini file that's used to 1) Populate an "IO Mapping" table with the desired key-value pairs, the "name" being an "IO Point" name, the "value" being a cluster with the desired "IO Data" child class, and some specific channel numbers, bitmask stuff, etc. - the specific "IO Data" child class "value" would then determine (via Dyno-Dispatch methods what *types* of Sets & Gets to actually do.  Am I totally out to lunch with this? Thanks, paul

0 Kudos
Message 3 of 27
(2,350 Views)

Oh, I didn't finish my thought.  So I thought I would test this scheme by just creating a couple of "IO Data" parent class objects, one with an "Analog Data" child loaded and the other with a "Digital Data" child.  But then I couldn't figure out how to *get at* the data that I'd written to the child classes.  Since the parent class has no data, can't use an accessor vi with it...  make any sense?

0 Kudos
Message 4 of 27
(2,346 Views)

Accessor VIs are just VIs. They can do anything you want as long as you have the class on the connector pane. Yes the IDE can auto-generate some for you that access class private data but that's just a convenient shortcut. So your parent class could have a DD VI that returns the same data type as your child classes (its default logic could be to return the default value for that data type).

0 Kudos
Message 5 of 27
(2,331 Views)

Thanks tykoo7 - I must digest this.  I see all the business about the Factory Pattern but the examples don't go far beyond the loading of the child onto the parent... I appreciate your help! 

0 Kudos
Message 6 of 27
(2,329 Views)

I don't understand what you mean with 'rolled into', i do hope that doesn't mean you're trying to access child data from the parent somehow. 

A class always works with it's own and it's parents data, a parent never uses/handles a childs personal belongings (data, think you wouldn't want your parents to handle your bank account and surf history, would you?).

 

Maybe it's easier to think of it in an evolutionary sense, a parent is 'mammal' and children 'cats', 'dogs' and 'cows'. To have the code general we place and use the Mammal-methods everywhere, but the implementation is up to the individual childclass. The method 'create mammal glands' will look quite different between cats and cows, for instance.

G# - Award winning reference based OOP for LV, for free! - Qestit VIPM GitHub

Qestit Systems
Certified-LabVIEW-Developer
Message 7 of 27
(2,308 Views)

@tyk007 wrote:

Accessor VIs are just VIs. They can do anything you want as long as you have the class on the connector pane. Yes the IDE can auto-generate some for you that access class private data but that's just a convenient shortcut. So your parent class could have a DD VI that returns the same data type as your child classes (its default logic could be to return the default value for that data type).


Actually in this specific case you probably should mark the accessor VI in the parent (abstract) class as "must overwrite". As it doesn't do anything useful for a parent class instantiation (it's an abstract method just there to define the access interface for the object hierarchy) it should really force any inheriting class to implement an overwrite method.

Rolf Kalbermatter
My Blog
Message 8 of 27
(2,298 Views)

@Yamaeda wrote:

I don't understand what you mean with 'rolled into', i do hope that doesn't mean you're trying to access child data from the parent somehow. 

A class always works with it's own and it's parents data, a parent never uses/handles a childs personal belongings (data, think you wouldn't want your parents to handle your bank account and surf history, would you?).


I think you took the generational analogy a little to far here. In the case of bank accounts and surf history the desire to not access each others data usually goes in both directions. 😀

 

A parent has indeed no business accessing a childs data directly. If you ever feel the need to do that in an implementation, step back, take a deep breath and throw everything away and start from scratch again!  😁

 

But a parent can be (exclusively or partially) an interface definition and in doing so, absolutely can define an interface that a child should/must implement, to access something that the parent basically implies a child should have. Otherwise that child should not need to inherit from that (interface) class.

 

The complication in the OPs problem is that he wants to have analog and digital children. So if the parent (interface) defines a Get Data.vi accessor method it needs to be implemented in a way that it can return both types of data. Or you implement a Get Analog Data.vi and Get Digital Data.vi method in the parent and overwrite the according method in the child, but that is not very scalable if you ever intend to extend the hierarchy to support other data types and should be avoided whenever possible. It also burdens the user of Analog child to worry about the Get Digital Data.vi method which has no functionality. That is really muddying the object interface definition and generally an ugly solution, but often chosen as quick and dirty implementation when extending existing class hierarchies, as a proper redesign would require changing the parent and all its already existing offspring. 😂

Rolf Kalbermatter
My Blog
Message 9 of 27
(2,296 Views)

You can give the analog child a Get Data.vi that returns double(s), and give the digital class a Get Data.vi that returns Boolean(s).

 

If the parent doesn't have a Get Data.vi method, the connector panes don't have to match. The returned data can be a double(s) and Boolean(s) indicator. But you won't have run time polymorphism. You will get compile time polymorphism, for instance if you use Get Data.vi in a .vim. So you can get doubles from a analog class, but you can't call Get Data.vi on an array of analog and Boolean objects. Well, you can, but you have to cast each individual object to it's more specific class. This is a code smell, not something you want...

 

Another option is to make the returned data the same type. Variants, flattened data, strings, or a data class will do the trick.  The parent can now define the interface, as all 3 connector panes can be the same. This will allow run time polymorphism. But it just 'delays' the problem. You get the exact same issue with the data class, but later on. Still useful, if the data class has (or supports) functions to visualize, write to file, store to DB, etc. These functions are better of delegated to the data class, or even better: interfaces using it.

 

Of course you can give the parent Get Data.vi both double and Boolean outputs. Then make the analog class use the doubles output, and the Boolean class the Booleans output. Another code smell, this will soon confuse everyone, including yourself.

 

This problem is typical when you're using inheritance to extend a parent. Polymorphism is easier to comprehend (and like) when children implement their parent. Your example does both: it implements it's parent (e.g. acquire.vi) and extends it (e.g. get child specific data.vi). This is useful, but not a typical use case for polymorphism. You still get to enjoy encapsulation, inheritance and compile time polymorphism.

 

This might not be a problem you want to use to learn (or like) OO. It will give the impression that OO is really hard. But in fact, the problem is hard. For instance, think about solving the exact same problem without OO... My guess is it will be equally hard or even harder.

Message 10 of 27
(2,275 Views)