I have written a bit about the GOF design patterns previously. Recently Tom McQuillan and I were talking about how it would be nice to have examples on how to implement the patterns in LabVIEW. To be fair, Elijah Kerry put together some great examples several years ago, but he didn’t cover all the patterns. Tom and I also thought that since we now have interfaces in LabVIEW that it might be worth revisiting all the patterns and seeing how interfaces might make some of the patterns easier to implement.
We decided to put all the code on Gitlab. I am going to do a writeup on each of them here for my blog and Tom will do some videos on his YouTube Channel as well.
The factory pattern is probably one of the most common patterns used in LabVIEW. It’s one that most OOP programmers are at least familiar with. The pattern creates a bunch of objects that can all be treated the same in that they all share some common ancestor. A common use case would be a Hardware Abstraction Layer (HAL).
You can get the example code here. You will notice there are 2 projects. Open the Factory.lvproj. It should look like the image below. The project contains an Source interface which has a Factory method (static dispatch) and a Get Data method (dynamic dispatch. It also a contains a VI Get Sources List that simply returns any children of Source that it finds in the project folder. The Test VI is what we are going to run for our demo.
If you explore the folder in Windows you will notice 3 other classes that don’t show up in the project: DMM, Scope, and DAQ. These all inherit from Source. The only class or interface that the Test VI knows about is Source. If you look in the dependencies of the factory project, you’ll notice they don’t show up there.
Now let’s open the Test VI and look at it’s Block Diagram. Note the no class shows up here, just the Source Interface.
If you run the Test VI, you’ll see the graph change for each Source. If you go to the block diagram and click on the Get Data method you’ll see that it is dynamic dispatch, you’ll be able to see all the available classes (provided they are in memory). You can also look at the project after cycling through all the choices and see that the classes (along with a few other libraries) have been loaded into memory.
What makes all this work is the factory VI. You pass in a path to an lvclass file. At edit time, the project has no idea what class you are going to load. At runtime when you pass a path to a class in, then that class is loaded into memory. The factory VI is simple. You just get the default value of the class from the path. That returns a LV Object. What the rest of our code expects though is a Source, so we use the To-More-Specific primitive to cast the object to a Source. You need to make sure you handle your errors here in case you get a bad path or if the path points to a class that does not inherit from Source.
This example (loading the child classes by path) works very well if you want to have a plugin type system where users can supply their own classes to represent their own unique data sources. If you want the list of Sources to be closed you can use an enum. The disadvantage is that all of the classes are always loaded into memory even if they are never selected. If you build it into an executable all of the available classes are defined at build time and can’t be changed. The advantage is that you don’t have to worry about bad paths or paths to classes that don’t inherit from source which would cause runtime errors. You also don’t have to worry about distributing all the child classes if you build an executable.
Note that the factory method doesn’t have to be a member of a class at all. In many cases, it makes sense to make it a method of the parent class or interface, particularly if you are using an interface or an abstract parent class, but it is not required. Also note that in this case we used an interface as the output of the factory, but it could also be a regular class.
The hardest part about Object-Oriented Design is setting up the correct class hierarchy and relationships. When you get it right, it makes adding new features a breeze. It’s often as simple as adding a new child class to contain the new functionality. Without the correct hierarchy, you can quickly create a mess. The GOF patterns help, but it can still be tricky to get it right. If you would like some help making sure you are on the right track, give us a call. We’d be glad to talk to you and schedule a code/design review.