11-09-2015 05:53 PM
Hi y'all!
So today I would like you to evaluate my take on producer consumer design pattern template (achieved after combining different designs saw on youtube/NI presentation/forum.
Doing so I stumbled a lot on many video recommending OOP. The application I will build using this design pattern is a touchscreen interface (lot of controls) for a measurement system [NI usb acquisition, generate signal, pool multiple analogue sensors, control sensors (sometimes not the same depending on the customer),read/write tdms file (can end up being quite big), interact with PLC etc.]
The current version of the application I'll later inherit from (puns intended ) have what you can call a super-mega-gigantique cluster (cluster of clusters containing pretty much every type of data ) that is passed around quite a bit ( producer loop, and 4 consumer loops, and some other places too).
As an initiation I used a mini cluster on this template and tried to make a comparison between classes and clusters:
So question:
1- Am I doing it right?
2- Why is super cluster bad? (it's so much easier to bundle-unbundle...)
3- What is the difference between dynamic dispatch and accessors? (On most of the tutorials and demos I saw they where using dynamic dispatch just to read and write on a class method I found about accessors just recently)
4- Must overwrite - must call parent
5 - Having so much vi won't be a big impact on system performance?
6- Dynamic load and To more specific? (I think I understand this one a little bit, but any input is welcomed.
That's it for now!
Thanks !
Solved! Go to Solution.
11-10-2015 05:21 PM
Hi Fabrice,
Taking a look at this here are some answers to your questions.
1. You seem to be doing this mostly right based on what you have told me you are looking for. It seems like you have a decent grasp on the architechture.
2. A cluster with a lot of elements is bad because it takes a lot of memory every time that you use it. You also have to transfer around the giant amount of data which takes longer.
3. Dynamic dispatch is where a child gets selected automatically when its made based on criteria. Accessors are similar to get and set methods. So these two things are completely different.
4. I don't understand what your question "must overwrite- must call parent" means.
5. Having a big VI does not necessarily impact system performance. What you are doing in that VI does affect it. Though keep in mind that you want to make your code readable so a smaller VI is better.
6. Dynamic load just loads a class, and To more specific takes a parent and makes it into a child.
Let me know if you have more questions about this.
11-10-2015 07:07 PM
@Skirpt wrote:
2. A cluster with a lot of elements is bad because it takes a lot of memory every time that you use it. You also have to transfer around the giant amount of data which takes longer.
This is entirely wrong. If you're careful about how you pass the super-cluster around and how you use the data in it, you can use one efficiently. Similarly, creating a class that holds a lot of data isn't automatically more efficient than a super-cluster; you still need to use the class properly.
The advantage to a classes over a super-cluster is flexibility. Putting all your data in one cluster means linking together items that probably aren't related to each other. If your subVIs all accept that supercluster as an input, it will be difficult to reuse those VIs on another project that has a different super-cluster. However, as a class, you could create a child class with the new data needed for the new project, without disturbing the existing logic (as one example). There may be cases where you want to combine classes and a supercluster - it wouldn't be unreasonable to have a cluster that contains several classes as cluster elements.
The reason to use dynamic dispatch accessors is in case you want to override the accessor later; which you cannot do with a bundle/unbundle. An accessor also lets you provide logic around setting or reading a value; for example, it lets you generate an error if you try to set an invalid value. When you're the only coder on a single project this may not seem so useful, but it can be important if you're writing classes for other people to use and you need to enforce certain conditions. If you know you'll never need that extra logic, then a cluster with a bundle/unbundle is definitely simpler.
11-10-2015 07:15 PM
FabriceToe wrote:4- Must overwrite - must call parent
I assume here you're asking what these mean? Must Override means that any child class must provide its own implementation of the method. This is used where the parent method should never be called, or the parent method is incomplete. A common OOP example is a collection of shapes, where each shape has its own Draw method. You would want to set Must Override on the Draw method, because each shape needs to draw itself; the parent can't do it based on the available data.
Must Call Parent is for the case where the parent method does something critical and therefore must always be called, in addition to whatever the child implementation does. If the Draw method above does some setup to prepare for drawing (setting the color, getting access to the canvas), then you might set Must Call Parent for it to do that initial setup before drawing the specific shape in the child method.
11-12-2015 10:09 AM
Thanks Nathand and Skirpt for your responses.
So if I get it right, creating accessor to access for example "temperature" in a class is the same thing as creating a dynamic dispatch VI to access the property except that with dynamic dispatch a child can override the logic in accessing "temperature" (for example let say to add a gain for a specific situation ) ?
Nathand :
Must Call Parent is for the case where the parent method does something critical and therefore must always be called, in addition to whatever the child implementation does. If the Draw method above does some setup to prepare for drawing (setting the color, getting access to the canvas), then you might set Must Call Parent for it to do that initial setup before drawing the specific shape in the child method.
In this example say your method is "Draw" and in the parent class "Draw" contain 'Color' 'line width'; and the child class "Draw" method contain 'coordinates'. How is that enforced by LV when you check "Must Call Parent"? How will I know if I take someone else code that I must call the parent class method first? Also is it like dynamic dispatch where both methods must have the same name and inputs/outputs?
Also I am not sure I still get the To More Specific/To More Generic. I Thought it will behave like: I pass the parent class wire everywhere a class is needed and then on specific places i use To More specific to go to a child class and perform actions there but when i tried it I got errors
( Error 1448 occurred at To More Specific Class in To More Specific.vi - Possible reason(s) : LabVIEW: Bad type cast. LabVIEW cannot treat the run-time value of this LabVIEW class as an instance of the given LabVIEW class.)
11-12-2015 01:35 PM
FabriceToe wrote:
So if I get it right, creating accessor to access for example "temperature" in a class is the same thing as creating a dynamic dispatch VI to access the property except that with dynamic dispatch a child can override the logic in accessing "temperature" (for example let say to add a gain for a specific situation ) ?
That's right. An accessor is just a VI, there's nothing particularly special about it (other than the ability to use property nodes instead of a VI call, but from an execution point of view they're identical). As with any VI in a class, an accessor can be either static or dynamic dispatch.
FabriceToe wrote:
Nathand :
Must Call Parent is for the case where the parent method does something critical and therefore must always be called, in addition to whatever the child implementation does. If the Draw method above does some setup to prepare for drawing (setting the color, getting access to the canvas), then you might set Must Call Parent for it to do that initial setup before drawing the specific shape in the child method.
In this example say your method is "Draw" and in the parent class "Draw" contain 'Color' 'line width'; and the child class "Draw" method contain 'coordinates'. How is that enforced by LV when you check "Must Call Parent"? How will I know if I take someone else code that I must call the parent class method first? Also is it like dynamic dispatch where both methods must have the same name and inputs/outputs?
It is exactly dynamic dispatch. Within a class hierarchy, any override VIs must all share the same connector pane (inputs and outputs) as the parent method. Dynamic dispatch selects the correct method for that class at runtime. Without the explicit option to call the parent, there would be no way to call the parent method within a child, because dynamic dispatch on that class would always call the child method. When "Must Call Parent" is set, you'll get a broken run arrow (the code won't compile) and an error if a child does not call the parent method. That does not mean the parent method will be called - the call to the parent could be in a case structure that doesn't execute - but it does force you to place a call to the parent in the child VI. You don't need to call the parent method first, you just need to do it somewhere.
FabriceToe wrote:
Also I am not sure I still get the To More Specific/To More Generic. I Thought it will behave like: I pass the parent class wire everywhere a class is needed and then on specific places i use To More specific to go to a child class and perform actions there but when i tried it I got errors
( Error 1448 occurred at To More Specific Class in To More Specific.vi - Possible reason(s) : LabVIEW: Bad type cast. LabVIEW cannot treat the run-time value of this LabVIEW class as an instance of the given LabVIEW class.)
You cannot use To More Specific to force a class to be a different class; you can only use it when a wire shows the parent class but you know it's actually an instance of a child (and likewise for To More Generic, if you need to force a child to be treated as a parent). For example, you might have some code that operates on generic LabVIEW objects, so you pass in your class; then later, you need to use that same object as the specific object that you know it actually is. Then you would use To More Specific.
A concrete example of this: perhaps you create several instances of different classes, with no common parent, and then you put them all in an array. That array will contain LabVIEW Objects, since that's the common parent of all LabVIEW classes. Later, you index one element out. You know what class you put in at that index, so you can use To More Specific to treat that wire as the correct type. The important thing here is that when you first put that element into the array, it was NOT a LabVIEW object, it was a more specific class, so it's possible to go back to using it as that class. If instead you created an array of LabVIEW Objects, you could not do this.
11-12-2015 03:27 PM
Thanks again for your response!
Nathand:
You cannot use To More Specific to force a class to be a different class
So is there another way to do something similar?
I have another question. Why is case 1 and 2 below produce different result? (i.e in case 2 I write nothing to the indicators)
Aren't class object suppose to act like a typedef. cluster? (i.e. Update the typedef. cluster/object and it's updated everywhere?). If not how to keep track of changes and use the same class object in different subvis where in some you set new value and in other you read them ?
11-12-2015 04:02 PM
So is there another way to do something similar?
No. It's analagous to this: You have a Tiger. You use this Tiger as an Animal, then you want to use it as a Big Cat. This works because a Tiger is an Animal and a Big Cat. But if you try to use it as a Lion you get an error. You aren't changing the class of the object, just the class of the wire.
Aren't class object suppose to act like a typedef. cluster? (i.e. Update the typedef. cluster/object and it's updated everywhere?). If not how to keep track of changes and use the same class object in different subvis where in some you set new value and in other you read them ?
They do indeed act like typedef clusters; but that is not how typedef clusters work. These are not by-ref data structures.
11-12-2015 04:07 PM
FabriceToe wrote:
Nathand:
You cannot use To More Specific to force a class to be a different class
So is there another way to do something similar?
No. If you need the functionality of a child class, then you should use the child class in the first place, rather than an instance of the parent.
FabriceToe wrote:
Aren't class object suppose to act like a typedef. cluster? (i.e. Update the typedef. cluster/object and it's updated everywhere?). If not how to keep track of changes and use the same class object in different subvis where in some you set new value and in other you read them ?
I'm not sure if you've misunderstood type defintions, or how they relate to classes. Let's look at this example:
Both instances of "Typedef 1" are linked to the same Type Definition, but it should be obvious that the Number indicator will contain 0, not 10, when you run it. Classes work exactly the same way. When you edit a type definition, or a class, that change (for example, changing a numeric representation) is reflected in all instances of that type definition or class. At runtime, each instance is separate; changing the value of one instance does not affect the others. Unlike in many languages, classes in LabVIEW are by-value, not by-reference, making them consistent with other LabVIEW data types. See http://www.ni.com/white-paper/3574/en/ for more details, particularly the section titled "How does OO fit with dataflow?"
11-13-2015 05:52 AM
To add to the previous good comments:
A CLASS is a definition of a data structure with associated methods (visible in the Project).
An OBJECT is an actual created instance of that definition.
Please try to understand that definition. Each wire is an OBJECT of type CLASS. By changing one OBJECT you do not affect other OBJECTS of that type.
Imagine the horrors if by changing the value of a single Boolean in LabVIEW, ALL booleans would react accordingly. Try programming with all that craziness going on. OF course each Boolean is of the same TYPE but each instance is individual.
Your lower example creates two OBJECTS of type VISA and PATH (No wire connecting them). Of course they do not have the same internal data. They are the same TYPE (CLASS) but each copy is treated individually. You CAN make them react as you think they should, but you need to program that on purpose.