07-19-2010 02:21 PM
Today's nugget has to do with the organization of data in a LabVIEW Class. I have really only embraced LabVIEW Class programming within the past year, which probably makes me a late adopter compared to other LabVIEW architects. But when I first started using classes, I took what I assume is a pretty standard approach to creating data member access VIs...every time I added a new data item to my private data control, I created read/write accessor VIs for that item. One thing I noticed fairly quickly was that this really bloated the codebase of my LabVIEW Class-based apps. It also became a maintenance headache when I needed to add/remove/modify items in my private data control, because I had to go add/remove/modify the data member access VIs as well. So in my current LabVIEW Class-based project, I have taken a different approach:
In this example, my private data control now only contains a single element...and that is a typedef cluster (Harness Data.ctl in the screenshot above) that itself contains all the data items for my class. With this approach, I now have a single read and a single write VI that gains me access to all the data in my class. If I need to add/remove/modify items, I only do it in the typedef cluster, and the read/write VIs don't need to change. The only disadvantage I've encountered to this approach is that each read/write in my code also requires an Unbundle By Name/Bundle By Name, respectively. But I find this requirement to be much less of a burden than having to maintain a library of accessor VIs, especially since the adding/removing/renaming of VIs on disk is an extra headache when your project is in SCC, as mine always are.
I realize that this approach "publicizes" all the data in my class. For my apps, whose interfaces have all been internal to this point, this is not a concern. But I imagine for a LabVIEW Class-based app where there is a public interface, you would be going through extra work anyway to create public accessors for data, so as long as the Read/Write VIs that access the entire data cluster are private, it shouldn't be a concern.
One final note: Since I am relatively new to LabVIEW Class development, I'm guessing there are theoretical reasons why this approach isn't the best. But so far, I've found data member access to be a much easier endeavor with my current organization than with my previous. So if you have any ideas about how my approach may break down in the future, speak up!
07-19-2010 02:49 PM
Does this make searching for where a class data item gets accessed more tedious? That is, with your previous method you could search on a discrete (cohesive) accessor VI. Is there a suitable workaround for searching (e.g. during troubleshooting)?
07-19-2010 02:51 PM
@LabBEAN wrote:
Does this make searching for where a class data item gets accessed more tedious? That is, with your previous method you could search on a discrete (cohesive) accessor VI. Is there a suitable workaround for searching (e.g. during troubleshooting)?
So far, I have found block diagram text searches for the data member name to be only slightly less easy than searching for instances of a single data member access VI.
07-19-2010 03:05 PM - edited 07-19-2010 03:05 PM
One slight catch that I can think of straight away with this method + source code control is this:
If you change the contents of your class's data cluster (ie add a new data member or something), every single VI that uses your Get/Set Everything VIs will end up getting recompiled etc whereas if you had separate data accessors, you would only have to re-save the VIs that actually changed.
This makes it much harder to track down the actual VIs that were changed as part of your "change".
It also makes it a whole lot easier to add bugs into your app - I'm sure I'm not the only one who has changed a type def cluster to later find at least one or two bundle/unbundle by name nodes got confused with the change.
Shaun
07-19-2010 03:10 PM
If you add an element to your type-def then trying to read old versions of your class data from file will break.
I like the accessors since they let me selectively over-ride how where a value or stored and retrieved.
Example:
I need to be able to control the number of bits on a SPI bus. This is an option that my classes read from config file. When running, I need to enforce the proper number of bytes to carry those bits. My "Write NumBits" does the math and updates "NumBytes" within the accessor "Write NumBits".
Ben
07-19-2010 03:16 PM
Ben, that example is one of my favorite features of proper data abstraction! 🙂
One thing though... I swear that I have saved classes to disk (using flatten to XML) and successfully re-read the data back into newer versions of the class with changed cluster type defs. In fact on of my templates for managing application preferences uses classes for this very reason (among others, obviously 😉 )
07-19-2010 08:56 PM
07-19-2010 09:41 PM
I'm carefully watching this conversation because it addresses one of my primary hindrances for not choosing mainstream LVOOP in my development: the relatively low node-to-file ratio.
This results in high-overhead situations that hinder the development process: more files means more time to complete SCC operations, more time to complete FTP operations when deploying to a remote target (it's much faster to deploy ten 100kB files than one-hundred 10kB files), more window-acrobatics juggling with fewer nodes-per-window, and according to a project with a lot of classes just more time to do everything.
Darren's idea for mitigating this problem is ill-received (after 7 hours, his Nuggets usually have 10 Kudos - this one currently sits at 0). No one yet has said anything positive about increasing the node-to-VI ratio, which I find confusing... possibly the conversation should lead toward a new post on the Idea Exchange that attempts to reconcile both sides?
07-20-2010 03:18 AM
Sounds like a job for scripting for me.....
I don't like the idea of accessing a cluster. For me the data in and out of accessor VIs should be as "user-friendly" and fool-proof as possible. One example is a set of LVOOP command parsers for instrument communication I have implemented.
If an instrument has a command of S012.4 made up of "S" for "Set", "01" for channel number and "2.4" for a voltage value then I don't want the user to have to enter the string "S012.4" directly because users are stupid and make lots of mistakes Instead I have the "S" as constant, have an enum for 01 to 04 (Because that's all my instrument supports) and a SGL with proper limits set for the final value. Within my accessor VI I then convert to and fro between the aforementioned string and the allwoed values. This way the user (programmer) is simply NOT ABLE to enter wrong values.
Of course I could do it like Darren says and simply have a cluster of "S", enum and SGL as a cluster but then I lose a decisive piece of my functionality. My parent class contains a string as a data member and this class takes case or the messaging and instrument communication. When accessing the data in my child class it is this string I am operating on, not a string in my child class. My child classes are essentially command parsers. I will still have to convert back and forth in order to have any benefit from this kind of inheritance in this situation.
This level of abstraction is extremely valuable for me and is a main reason why I like using LVOOP for implementing this kind of thing.
Shane.
07-20-2010 07:44 AM - edited 07-20-2010 07:46 AM
OK more thoughts on this topic.
I have run into the issue with having to remove accessors first before removing elements from the private data definition.
[Set blatent honesty mode]
But I only had to remove them becuase I screwed up and should not have had them to begin with! So I took those experiences as a learning opertunity and learned something from my mistake. What were the mistakes?
1) I did not fully understand how to implement the right methods at the right level of the inheritance haierachy. Looking back at it, I was over-loading one of the levels and a result, was pinting myself into a corner.
2) I Cloned off the wrong level of the hierachy - Similar to above but I selected a prototype from which to create a new Class that had more than what I needed so I had to toss a lot of stuff.
So... For
#1 I learned to think more and try to keep my layers .... thin? For
#2 I either have to clean it up before introduction into SCC or if my Class hierachy is good, just crate a whole new class and implement over-rides for what is different.
Now back to the Accessors!
The Over-ride and inheritance power starts to kick-in when the class hierachy starts branching.
NOTE Still in honsety mode I got a preview of this when I was implementing a "Controls on the Fly" app in LVOOP. I modeled my classes similar to what NI did with controls and indicators. This was a good start for me since NI alredy provided a smaple outline. What I found was "The more classes I added to the hierachy, the less I had to code". I have deployed a few LVOOP apps since then and I strugled with getting good re-usable Classes... becuase I did not know what I was doing. Yes the apps worked fine, were easy to follow and modify but when I was done I did not find a lot of code that I could re-use Except... I found that the work I did with my Files to wrapp-up my TDMS functions let create a sibling class to handle the Config file stuff.
But now you may be asking "Why wrapp up NI functions?"
Taking to long to post, will contiue in next posting...
Ben