When I first got my hands on LabVIEW 8.2, I was really curious to see some of the shipping examples that show how to use the native object-oriented features in 8.2. I was extremely happy to see an example on objects by reference (ReferenceObject.lvproj).
Now, I’m not going to get into a debate on which is better, by value or by reference. These two features are not mutually exclusive. In fact, references are values, so it is very easy to develop by reference objects using the OOP in 8.2 – and eventually NI will implement a native by reference object management scheme (I hope). There I said it, let’s move on.
So, back to the story… I took a look at the ReferenceObject.lvproj example, and was really happy to see a pattern with a locking mechanism for shared data. While the support VIs (that implemented the data access and locking) were named different, they were basically the familiar GetData (read), GetDataToModify (read+lock), and SetModifiedData (write+unlock). I thought, “hey, I know what do with those!”
But, I had some initial thoughts…
1) The example uses a single element queue as the object reference. The object instance data is the single element that’s in the queue, and the queue reference points to the object instance. There will be a unique queue for each object instance. OK, this is basically just dqGOOP. I’ve used that pattern before, so I’m comfortable with that.
2) CheckOut (read+lock) and CheckIn (write+unlock) are public VIs of the ReferenceClass. IMO, they should definitely be private. Only the class member VIs should be accessing/locking/unlocking the object data directly.
3) I prefer the data access VIs to be named GetData, GetDataToModify, and SetModifiedData (instead of CheckOut and CheckIn) and I like them have the familiar lock and unlock icons, just like old-fashioned GOOP.
4) There are two classes used to implement the by reference object. The purpose of SimpleObject is to insulate ReferenceClass’s Queue Refnum (reference object data type) from type changes of the data cluster. That’s pretty cool.
5) There are accessor methods in SimpleClass. It would be a whole lot easier to have just two methods, “object to data” and “data to object”, in SimpleClass and then put all the accessor methods in ReferenceClass. Again, I think that the only purpose of the SimpleClass should be to insulate ReferenceClass’s Queue Refnum from type changes to the data.
6) The SimpleClass data should be one cluster (a typedef, of course), so that ObjectToData and DataToObject can get all the data in a single element, instead of unbundling and bundling multiple elements.
I decided that it would be a good idea to refactor the ReferenceObject example, to see if I could improve upone the pattern’s design. I wanted to improve upon the original example and optimize it for two main considerations:
- Developer effort to maintain the class
- Run-time Performance
While there was no one perfect solution, I did come up with three new examples. Each of these balancing the considerations in different ways.
ReferenceObject_JK01 (ReferenceObject_JK01.zip 133KB)
This example uses a value object (SimpleClass) as the queue element of the reference object (ReferenceClass).
- Cluster data type can be changed without changing the type of the Queue Refnum (which requires the user to manually redefine the queue refnum type).
- Fast – Since the by reference object’s instance data is never flattened/unflattened, there is no performance penalty.
- There are two classes required for each class. This requires a lot of extra work for developers, to maintain all these extra classes and the linkages between support VIs.
ReferenceObject_JK02 (ReferenceObject_JK02.zip 119KB)
This example uses a flattened string as the queue element. The object data cluster is flattened and unflatted when enqueuing and dequeuing into the ReferenceClass queue.
- Cluster data type can be changed without changing the type of the queue refnum, since the queue data is the flattened string of the cluster.
- There’s only one class
- Slower – Since the by reference object’s instance data is flattened/unflattened, there is a performance penalty.
ReferenceObject_JK03-1 (ReferenceObject_JK03-1 115KB)
This example uses a cluster as the ReferenceClass queue element.
- Fast – since the by reference object’s instance data is never flattened/unflattened, there is no performance penalty.
- There’s only one class
Another implementation of the ReferenceObject would be to use a variant (rather than a flattened string, as in the second example). NI has done some optimization of the variant (in LabVIEW 8.0), which might make this a feasible choice.
One new LabVIEW feature (which is utilized by the 3rd example) is that a queue refnum created by selecting Create Indicator from an Obtain Queue will be dynamically linked to the type definition wired to the Obtain Queue’s element type input. This means that you will not have to update the queue reference when you change the data cluster type definition. This ia really cool! I’m not sure yet when this feature first appeared, but it’s absolutely great! Thank you, Philippe Guerit, for telling me about it (I hadn’t realized this when I first wrote this article). So, with the a dynamic linkage between the data cluster and the queue refnum, the third example, ReferenceObject_JK03-1, is the hands-down winner for both efficiency and ease of development and maintenance!