08-15-2013 05:35 PM - edited 08-15-2013 05:37 PM
I have a base class, call it AnalogAcquisition. I then have a child class StrainAcquisition. I have a second class AnalogChannel, which has a child class, StrainChannel.
My base class AnalogAcquisition has an array of AnalogChannels in it. My strain class has a method called "SetChannels", which accepts an array of StrainChannels as an input, then uses an accessor to put these strain channels into the parent classes AnalogChannel array. I want this implementation because I want to ensure only StrainChannels are placed in my StrainAcquisition Class, but then I am reusing the parent classes existing array of AnalogChannels to hold the child class's channels.
So far this seems to be working out fine. I just want to check if this is a valid implementation. Something feels a bit strange about requiring a specific channel type, then shoving it onto a parent class's wire, where from that point forward I will have to use DD VIs for anything specific to that class, even though I have already ensured the specific type ahead of time.
Maybe this is correct and I am just not seeing the big picture. That's why I'm looking for either a "good job, you're on the right track" or a "what are you doing you fool", preferably with an explanation as to why included .
Solved! Go to Solution.
08-15-2013 09:33 PM - edited 08-15-2013 09:44 PM
After re-reading your question I think I understand what you're asking a little better. The stuff below is somewhat relevant but this is more focused.
Require a SetChannels vi to be overridden by child classes of AnalogAcquisition. Do the same for any other common functionality. This is basically an interface like in C# or Java sense. Dynamic Dispatch will ensure that the proper method gets called at run time.
What you're missing here is the concept of a constructor, or constructor signature on the base class that would ensure all child classes properly initalize the base class array with valid types but in LabVIEW "constructors" are just another VI (i.e. "Initialize.vi") and you have to decide when to run it.
I think you're on the right path.
--------------------------Original Reply--------------------------
If it works it works, but you're creating a dependency that will make it a little harder to reuse the class for another similar but different application.
I've used a similar pattern because it's easier and simpler to make it work even though it's not a pure (completely modular) implementation. I hate when trying to follow best practices gets in the way of making progress.
Remember that the object oriented paradigm was not created for the benefit of the program, it was created for the human - to make smaller, more manageable chunks of code that can be independently debugged and understood without having knowledge of how all of the other program components work or interface with it. By requiring a parent class to have knowledge of a child class you basically lock yourself into using those two classes together every time you want to that functionality. It's not a bad thing but kind of works against the primary goal of OO - code reuse.
08-16-2013 01:08 PM - edited 08-16-2013 01:10 PM
That's effectively what I'm doing: a create method.
Here is a screenshot of the VI. Notice, because I am coercing the channels to their base type to put the data in the parent class, now anywhere downstream, even within the strain class, I have to use dynamic dispatch on any methods that act on the channels after I get them out of the parent class (unless I downcast to the specific StrainChannel class first, since I know what it is).
08-16-2013 01:46 PM - edited 08-16-2013 01:46 PM
That's not necessarily a bad thing - it's what dynamic dispatch was designed for. The overhead for determining which instance to call at run time is small and remains constant no matter how many child classes you develop (http://www.ni.com/white-paper/3573/en/). You would really only need to cast to more specific class in order to call a method that is unique to that child class. Any common functionality in your parent class (init, start measurement, clean up...) should be defined (and if necessary overridden) from the parent class and called using DD.
08-16-2013 01:47 PM
for(imstuck) wrote:Something feels a bit strange about requiring a specific channel type, then shoving it onto a parent class's wire, where from that point forward I will have to use DD VIs for anything specific to that class, even though I have already ensured the specific type ahead of time.
What you have independently discovered is called the Abstract Factory pattern in the Gang of Four book (Google if you need more info). Perfectly acceptable. This is different from the Factory Pattern. In the factory, some bit of data is used to pick which child class to instantiate. In the Abstract Factory, you have two (or more) paired hierarchies of classes and you use a method of the primary class to supply the specific related classes to a framework defined by the parent class.
08-16-2013 02:05 PM
Ugh, I need to read that book. It's on my list.
08-16-2013 02:39 PM
I'll give you a secret: nearly no one *reads* it. You skim the headings and the intros, and then keep your eyes open for something that sounds like it in your code and only when you have a real world example that you think it applies to then you read that one pattern. Some people go the extra mile of contriving reasons to need the patterns as a way of learning them (generally not in their shipping product but in some build tool or something that they are writing). Most people don't get very far when trying to study it as a text book.
08-16-2013 02:49 PM - edited 08-16-2013 02:57 PM
However, now I'm running into the following, which I thought I may. Because I hold base class channel in the private data, I end up doing the following cast. It just seems wrong, but maybe it's not...
I need to cast to an analog input before I can check limits. I don't want the check limits in my base channel class, which I would need if I want to use DD. Or is this an acceptable use for to more specific class, since I have provided an interface ensuring these channels are of type AI.
Edit: or maybe it's a sign I shouldn't have channel -> AnalogInputChannel ->StrainChannel as my heirarchy. Maybe just AiChannel->strain channel is sufficient. For AO I can have AoChannel ->ChildAoChannel. The AI and AO channels will NOT inherit from a base channel class. It seemed to be right to have a base class channel, from which all channel types can inherit. But, then I start running into these issues where in order to use DD you need specific functionality at such a high level, where sometimes it doesn't belong.
08-16-2013 04:01 PM
Generally, that's ok. Some points...
If you did this in C# or JAVA, you would probably store the array of the specific class objects in the child class in addition to the parent class. Since those languages handle objects by reference, there's no duplication. The reason you would store locally is to avoid the downcasting that you are doing in your picture.
When you call To More Specific, LabVIEW takes time to verify that the objects you're downcasting really are the type you're requesting. Such checking takes time, and sometimes that time is noticeable depending upon how often you have to hit it. In your code, there's no way that the downcast can fail, but there's no way for LabVIEW to know that. I have contemplated an "Unconditional To More Specific" node that would just pretend that the cast was successful even if it weren't. Such a node would crash LabVIEW if you were wrong (or have other unintended side effects like overwriting random parts of memory). Obviously it would only be usable in situations where you, the programmer, were certain that you had the casting right. It would be the equivalent of the C-style unconditional cast in C++. But that sort of "caveat emptor" programming has never been something LabVIEW offers, and I concluded that such a node has no place in LabVIEW, even if it would buy performance.
I don't know of any way in LabVIEW to avoid the cast if you want to make the channels available to the parent class and still use their specific functionality in the child classes.
08-19-2013 02:33 AM
Are you sure you need Strain channel to inherit from AI Channel at all?
Can the same not be achieved by compositing the objects instead of inheriting? I would consider having the Strainchannel linked to the AI channels in the parent object but not inherited from them. Is this something which would be feasible? It depends a lot on what type of operations you're doing on your channels whether such an approach could ever be feasible. It would simply declare a strainchannel to be a seperate entity which USES an AI channel. Operators on the AI channel which need some intervention for proper Strain measurements would need to be overridden.
It's probably bad design but I personally like quoting Star Wars when I have to do upcasts like you have pointed out : "I have a bad feeling about this."
Shane.