|This article is part of a series. To see more design patterns, please visit Applying Common Object-Oriented (OO) Design Patterns to LabVIEW|
This is part of a series of recommendations on how to implement some of the famous Gang of Four OOP design patterns using LVOOP. It represents an open discussion with the NI Community, so please feel free to provide comments and suggestions if you have any!
Guarantee that a given class has only a single instance in memory, that no second instance can ever be created, and that all method calls refer to this single instance.
When you create a class, sometimes it is advantageous to guarantee that a program always refers to the same global instance of the class. Perhaps the class represents a database. It would be unfortunate if some section of code accidentally instantiated its own database and thus failed to update the global database. Creating a global variable means all VIs can access the data, but it does not mean that all VIs will access the global variable. This pattern describes a class that is designed to ensure all its clients access the same data.
To implement a Singleton, two general requirements must be met:
In LabVIEW, there are multiple technologies available for achieving these goals. Two implementations are provided here as examples. The first employs a single-element queue (SEQ) to manage the instance and a functional global variable (FGV) to provide universal access. The second replaces the SEQ with a Data Value Reference (DVR) and provides access with a Notifier.
An example of this implementation shipped with LV8.2. That version turned out to be a bad idea. A better design has since been found, so please see the revised version that shipped in LabVIEW 8.6: <labview>\examples\lvoop\SingletonPattern\Singleton.lvproj
Checkin.vi and Checkout.vi use an SEQ to guarantee only one copy in memory at a time. While the element is checked out to be modified by some routine, no other operation can proceed, thus guaranteeing serial access to the data. Dequeuing the object provides the client exclusive access to its data (via public methods), until it chooses to re-enqueue the object by checking it in. In this implementation, the queue acts as a reference to the object, which can be accessed from anywhere in the application through "Checkout" and "Checkin". The reference is persisted in an FGV, which provides a copy to any client that calls the "Get Queue" method. If the object has not yet been created (on the first call), it creates an instance and adds it to the SEQ; every call thereafter, a copy of the reference to that first instance is used.
This implementation also provides a design for guaranteeing a single instance of Data.lvclass. We achieve this by putting Data.lvclass into an owning library, Singleton.lvlib, and making the class private. Now no VI outside the class can ever drop the class as a control, constant or indicator, so we guarantee that all operations are limited to this library. Callers can use Singleton.lvlib:Checkout.vi to get the current value of the data, modify it using any of the operations defined by Singleton.lvlib and then set the new value with Singleton.lvlib:Checkin.vi. The public functions, which would normally be on the class itself, have been moved to the library so their functionality is exposed without allowing the class itself to be dropped.
The attached example provides this implementation. It is derived from the Extensible Session Framework (ESF).
In this design, the client requests access to the singleton by providing a default instance -- a newly dropped object -- of the singleton to its "Get Instance" method. The method then outputs a reference to the persistent instance, which is used by the client in lieu of its dropped instance. Inside the method, it can be seen that the singleton object is held in a notifier instead of an FGV. This provides persistent global access to the object using a native LabVIEW feature. A reference is still needed to give access to methods from every client in the application, so a DVR to the object is held in the class private data. (This is normally forbidden, but placing the DVR in a variant first lets you circumvent the compiler.) When the object is popped from its notifier, the DVR is taken out of the object and passed out to the client.
A DVR is used instead of an SEQ here. The two options are largely interchangeable, but the DVR allows use of the In Place Element Structure when referencing and dereferencing the object. In LabVIEW 2010, DVRs also propagate type to object property nodes, so properties can be accessed directly via the DVR. DVRs are not available prior to LabVIEW 2009.
Note that while the "Get Instance" method is required to gain access to the share instance of Singleton.lvclass, it is not required that the client use it to instantiate the class. The client could just drop an object of the class type and use that object. This lack of protection against multiple instances violates one of the requirements for a Singleton design, but it can be solved in the same way as the previous example: place the class inside a library and mark it private, then place public methods outside the class (but still in the library). That protection mechanism is not shown here, because it may be deemed unnecessary for many applications and libraries.
[David Staab] The Singleton is meant to act as a global state/data repository, so here's an important note: in both implementations, the reference -- and the object it points to -- is maintained by the client that first created it. It is not truly self-persistent. This means that when the client process leaves memory, the reference, SEQ or DVR, will become invalid. So when working with a Singleton class in LabVIEW, it is very important to maintain awareness of client lifetimes.
Although the DVR+Notifier design takes a default instance of the class as an argument to the "Get Instance" method, it only uses this object to provide a unique string name to the Notifier so the same Notifier can be accessed every time. It is interesting to note here that the Notifier engine built into LabVIEW is actually acting as a registry of Singletons. The design can be altered to take a string name as an argument to "Get Instance", in which case any of a hierarchy of Singleton classes could be returned by the function. (This is what ESF does.)
There are lots of situations that call for multiple instances of a class (optionally with a fixed limit to their quantity), but the needs still exist to initialize each of them uniquely and to provide references to each of them globally. Consider using ESF in these situations, instead of developing your own customized Singleton.
[Stephen Mercer] Please do not use the version of this pattern that shipped with LV8.2. Although the example that shipped does show the basics of this pattern, many of its details turned out to be bad ideas in practice. The example was revised in LV8.6, drawing on the experiences of multiple users.
Personally, I have no use for this pattern. In LabVIEW, I find it kind of silly, as it is the sort of pattern that develops when you’re already breaking dataflow. But I include it because it seems many people desire to see how it could be done in our language.
The only hole in the above solution that I can find is VI Server. VI Server could get a reference to the Front Panel controls of Singleton’s member VIs and use the Value property to effectively manipulate a different instance of the class than the one in the queue. Would using Subroutine Priority prevent VI Server access? I think so, but that seems like a pretty odd way to fix these VIs.