NI Home
Cart Cart | Help
Company Events Academic NI Developer Zone Support Solutions Products & Services Contact NI MyNI

Currently Being Moderated

Moving Common OO Design Patterns From Other Languages Into LabVIEW

VERSION 13

Created on: Dec 7, 2008 8:30 PM by Elijah Kerry - Last Modified:  Apr 13, 2009 10:08 AM by AristosQueue

 

Introduction

When talking about computer programming, a design pattern is a standard correct way to organize your code. When trying to achieve some particular result, you look to the standard design patterns first to see if a solution already exists. This sounds a lot like an algorithm. An algorithm is a specific sequence of steps to take to calculate some result from a set of data. Generally algorithms can be written once in any given programming language and then reused over and over again. Design patterns are rewritten over and over again. For example, in house cleaning, consider this algorithm for vacuuming a carpet: “Start in one corner, walk horizontally across the floor, then move to the side and continue making adjacent parallel stripes until the whole floor is vacuumed.” Compare this with the design pattern for cleaning a room: “Start cleaning at the ceiling and generally work your way down so that dirt and dust from above settle on the still unclean areas below.” Design patterns are less specific than algorithms. You use the patterns as a starting point when writing the specific algorithm.

 

Every language develops its own design patterns. LabVIEW has patterns such as “Consumer/Producer Loops” or “Queued State Machine.” These are not particular VIs. Many VIs are implementations of queued state machines. When a LabVIEW programmer describes a problem, another programmer may answer back, “Oh. You need a queued state machine to solve that problem.” Starting in LabVIEW 7.0, LabVIEW has had VI templates for many LabVIEW design patterns available through the File>>New… dialog. With the release of LabVIEW Object-Oriented Programming, we need to find the new patterns that arise when objects mix with dataflow.

 

The seminal text on design patterns appeared in 1995: Design Patterns by Gamma, Helm, Johnson and Vlissides. This text is colloquially known as “the Gang of Four book.” This text focused on object-oriented design patterns, specifically those that could be implemented using C++. Many of those patterns are predicated on a by-reference model for objects. As such they apply very well to classes made with the GOOP Toolkit (or any of the several other GOOP implementations for LabVIEW). LabVIEW classes, added in LV8.2, use by-value syntax in order to be dataflow safe. This means that the known patterns of object-oriented programming must be adapted, and new patterns must be identified. This document describes the LabVOOP-specific patterns I have identified thus far.

 

All VIs attached to this document are saved in LabVIEW 8.5.

– Stephen Mercer,

LabVIEW R&D

Reference:

Design Patterns. Gamma, Erich, et al. 1995.

Addison Wesley Longman, Inc.


Specification Pattern

Intent

To have different functionality for a class depending upon some value of the class without updating case structures throughout your VI hierarchy when you add another value.

Motivation

Your data has some field – perhaps an enum, perhaps a string, perhaps something more complex – and you have a case structure that cases out on that field to decide which of several actions to perform. The data in this field is “constant,” meaning that once you have initialized the class this field never changes. When you find yourself creating a lot of these case structures, or, worse, updating a lot of these structures to add a new case, you should remember this pattern.

Implementation

Examples of this pattern occur in just about every shipping example. Any time you have a child class that overrides a dynamic dispatch VI defined by the parent class, you’re seeing an application of this pattern.

 

If some data element is constant from the time the cluster (or existing class) is initialized onward, then you should strongly consider creating a set of child classes. The data in that field is unnecessary – stop hauling it around the diagram. A class knows its data type. For any case structure that used to select based on the type, create a new dynamic VI on the parent class and put the code that used to be in each frame of the case structure into the children’s override VIs. Instead of one monolith cluster with an enum type, you now have a parent class and many more specific classes – thus the name of this pattern.

 

The parent class is frequently one that you never expect to actually see traveling on a wire in your application. It exists merely to define the API that all of your child classes will implement. Such parents are frequently referred to as “abstract classes” because you never expect them to be physically instantiated on the wire.

Consequences

This refactoring typically results in many more VIs in your project. This can have a load time impact on your application as a whole. But your run time benefits may make this worth it since there is less data being copied around for each object than for each pre-refactoring cluster.

 

This refactoring assumes that you’re going to change all existing case structures that select on type into calls to dynamic dispatch VIs. However, you may not have time to change all the structures, or the structures may be on VIs owned by other developers. If you encounter a situation in which you cannot create a specific dynamic dispatch VI for each case structure, you can still create a single dynamic dispatch VI called Get Type.vi and have each child override it to return the original data. Just call that method and pass the results into the existing case structures. You won’t be taking full advantage of dynamic dispatching, so you’ll still have to update those case structures when adding new child classes, but at least you won’t be copying the data of that field every time you copy your object.

 

If you cannot add Get Type.vi to your hierarchy (perhaps because the class is password protected), there are other ways to do type testing. The most obvious is the To More Specific function, which returns an error if the given data is not of the target class. So you can test “Is it this class? No. Is it this class? No. Is it this class? Yes.” Other ways of doing type-testing exist; you may find them documented elsewhere.

Collaborations

This pattern is frequently used in conjunction with the Factory pattern. When refactoring existing code, you may find the Delegation pattern helpful to create a good class hierarchy.

Editorial Comments

[Stephen Mercer] This is an obvious pattern to anyone who has grasped the basics of object-oriented design. This is an easy pattern to understand, and it is easy to think of the implementation on your own. Not all the patterns are complex systems. Sometimes pointing out the obvious is useful. When planning a new application, you may look at a list of patterns to consider what the best design would be. Having these so-called “obvious” patterns in the list helps remind you that you ought to use them. It may seem silly to mention Specification as a pattern because this pattern is nothing more than the central use case for class inheritance and dynamic dispatching. I highlight it as a pattern because even experienced designers sometimes overlook the obvious. They may miss the fact that a piece of data in their cluster never changes. If data never changes, that data is effectively part of the data type, and rather than carry that information around as data, which has to be copied at wire forks and other junctures, it can be carried as part of the data type, with its behavior encoded in various VIs which do not require copying all the time. Clusters that carry around an enum of how to interpret its cluster elements or a string representing the source of the cluster data cry out for application of this pattern. This pattern simplifies complex VIs and large clusters into manageable chunks by giving an easy way to break up the functionality of the case structure across multiple VIs. This pattern is the first one that a new user of object-orientation should learn and is the first one to consider when refactoring existing applications.

 


Aggregation Pattern

Intent

To treat an array of objects as a single object and define special behavior for that particular type of array.

Motivation

An array nicely collects data together into an indexable list. But there are many primitives that operate on an array: Build Array, Remove From Array, etc. There are times when you want an array to guarantee certain properties, such as an array that:

 

  • guarantees that no duplicates are ever inserted

  • is always sorted

  • doesn’t allow removing elements

 

This pattern creates a class that lets you treat an array of another class as a single object.

Implementation

See “AggregationPattern.zip”

 

To begin, identify the invariant that you want the array to maintain. Create a class with a name that reflects that invariant. Is this an array that should always be sorted? Then you might create a class named Ordered Array.lvclass. Perhaps it is an array that has no duplicate elements. Then you might create a class named Unique Elements Array.lvclass. Regardless of the name you choose, you will give the class an array of some data type as its private data. You define methods on the array class that maintain the invariant. So instead of a raw LabVIEW array that allows arbitrary modification using the array primitives, you now have an encapsulated array that only allows the specific actions that you define. For example, if the array is to be always ordered, you would create Insert Element.vi such that it inserts the new element in the correct sorted position in the array. Whatever functionality you supply – or leave out, as in the case of an array that does not allow removing elements – defines what can happen to the array.

 

The name of this pattern comes from the fact that you are creating a data aggregator – that is, this new class is a data type that represents some aggregation of many instances of another data type. The concept may expand far beyond just a simple array to become any number of collections of data – lists, sets, maps, etc.

 

The sign that you might want to apply this pattern is when you start referring to an array of the type by some specific name. As an example, you might have a class Athlete. And you have an array of Athletes that you’re passing around and calling “the team”, and you keep checking to make sure that no Athlete is on the same team twice. This is a good sign that you should create a Team class instead of using the array directly.

Consequences

The major drawback to this pattern is the amount of effort required. It can be a lot of work to re-implement all of the common array operations for your special array type. Fortunately, most of the time when these invariants are required, not all of the array operations are commonly needed.

 

On the plus side, if all the functions on the class that can insert elements guarantee that the array stays sorted then you can use faster algorithms when searching the array. The Search 1D Array primitive built into LabVIEW does a linear search down the array because LabVIEW has no way of knowing whether the given array is sorted or not. A sorted array can use a binary search (which, if you're not familiar with it, just accept is a much much faster way of searching an array). By limiting the variations of your collection, you may be able to optimize some cases.

 

You may be tempted to give the new class a wire appearance that looks like an array of your element data type. That’s generally just confusing since the class wire will break if it is wired directly to any of the array primitives.

Editorial Comments

[Stephen Mercer] Like the Specification pattern, this is an “obvious” pattern. It is included because, as easy as this pattern is, it is even easier just to have a raw array running around on your diagram (you don’t have to write a bunch of accessor VIs to duplicate the functionality already found on certain primitives). There are times when you might choose to do that. But as your code gets more complicated over time, you may find yourself wishing that you had wrapped the array up so that you could guarantee certain characteristics.

 

There are ideas floating around R&D to allow a class to provide a VI that would define the class’ behavior when it connects to a loop indexing tunnel. That would improve the ability of a class to mimic an array with restrictions on its behavior. It is one of many long-term syntax upgrades we are contemplating.


Factory Pattern

(adapted from Gang of Four’s Factory pattern)

Intent

Provide a way to initialize the value on a parent wire with data from many different child classes based on some input value, such as a selector ring value, enum, or string input. This may include dynamically loading new child classes into memory. Ideally new child classes can be created and used without editing the framework.

Motivation

Data comes from many sources – user interface, network connection, hardware – in a raw form. This data frequently comes in types, and the data is to be handled generically but each type has a specific behavior at the core of the handling. Such a problem cries out for the use of classes and dynamic dispatching. We want to write an algorithm around some common parent type and then let dynamic dispatching choose the correct subVI implementation for the parts of the algorithm that are type specific. The hard part is in initializing the right class from the input data.

Implementation

See “FactoryPattern.zip”

 

Any application using LabVIEW classes generally has a framework that is written using the parent class, and then at run time, child classes travel on the wires through the framework. This pattern provides a mechanism for dynamically selecting the child classes, including dynamically loading them into memory after the program starts running, if necessary.

 

The basic idea is this: Use some bit of data to identify the path to the class you are interested in loading. Use the Get LV Class Default Instance.vi function to load the class into memory and get an instance of that class.

 

Once you have used the factory to get a particular class of data, you are free to use it as you would any other class. Commonly, you would wire the chosen class instance to an Init method of the class which would take your data as an input. The advantage of this system is that the child class is only loaded into memory if someone actually wants to instantiate that child. If you have a great many possible types of data, this can save on memory and load time for your application. The name of this pattern comes from the fact that a single VI serves as a factory to produce all the myriad child types.

 

Once loaded into memory, a class will stay in memory as long as its parent class is running or there are any objects of that class type still in memory. Since objects can lurk all over the place – as the operate value of controls and indicators, as the last value in an uninitialized shift register, etc – it is basically impossible to dynamically unload a class once it is loaded. You should not attempt any architecture that is predicated on being able to unload and reload a class.

Editorial Comments

[Stephen Mercer] An example of this pattern was made available by Christina Rogers in her refactoring of the Getting Started Window in the Init From XML methods. Information about this example is available here:

http://eyesonvis.blogspot.com/2006/08/object-oriented-getting-started-window.htm l

Note that her example predates the introduction of Get LV Class Default Instance.vi. She used a method for loading classes into memory that works fine, but only works in the development environment, not in the runtime engine.


Hierarchy Composition Pattern

(adapted from Gang of Four’s Composite pattern)

Intent

To represent a single object as a tree of smaller instances of that same object type. Useful for images (an image is made of a set of sub images), file systems (directory contains both files and other directories) and other systems where there is a common aspect to both the whole object and its parts.

Motivation

“Composition” refers simply to using one class as member data of another class. One class is thus “composed” of another class. The Hierarchy Composition pattern is specifically for times when you are making a class that is going to be composed of smaller instances of itself.

Implementation

An example of this pattern shipped with LV8.2. It was significantly revised in LV8.6. See

<labview>\examples\lvoop\Graphics\Graphics.lvproj

 

This example includes a class hierarchy for Graphic.lvclass, as shown in the image.

 

A graphic is simply something that can be drawn. Graphic defines a method Draw.vi, which can be overridden by children. There are three children of Graphic. The first three are straightforward implementations: Point.lvclass, Line.lvclass, and Circle.lvclass. Each of these has coordinate data as its member data, and implements Render Picture.vi accordingly. The third class, however, is Collection.lvclass. Collection has as its private data an array of Graphics. That is, Collection, which is itself a Graphic, contains other Graphics. Its implementation of Render Picture.vi is to loop over that array and call Render Picture.vi for each contained element. The demo shows how a Collection graphic can be built up from individual points, lines, and circles.

 

You could continue this, adding a Collection inside a Collection, nesting them to create more complex Graphics.

 

The difficulty arises when you try to call Render Picture.vi for a Collection that contains another Collection. This is a recursive call to Collection.lvclass:Render Picture.vi. As of LabVIEW 8.5, LV does support recursion as long as you make the dynamic dispatch VI reentrant using the “Share clones” option.

Editorial Comments

[Stephen Mercer] The ability of a class control/indicator to contain itself or any of its descendant types makes a LabVIEW class very different from a class in C++ or JAVA or most other object-oriented languages. LabVIEW has a mechanism that allows us to have this hybrid of one class directly containing another class with one class containing a reference to another class. We have a by-value syntax, but the containment rules behave like references in most other OO languages.


Delegation Pattern

Intent

To have two independent classes share common functionality without putting that functionality into a common parent class.

Motivation

Good object-oriented design requires each class to focus on its assigned task. A class shouldn’t have member VIs unrelated to its task. If you have two classes that have some shared functionality, the usual solution is to create a parent class that both of them inherit from. Sometimes, however, you already have a class hierarchy created and a new feature comes along. The new functionality needs to be added to two existing classes that either do not have a common ancestor or do have a common ancestor but that ancestor is several generations up and not every descendent of that ancestor should have the new functionality. In some languages you might try multiple inheritance. But even in languages that support multiple inheritance, delegation is generally a better solution.

 

This pattern applies best when you have a dynamic VI inherited from some ancestor and two descendent classes want to override that dynamic VI with exactly the same implementation. Delegation helps you avoid writing the implementation twice, once for each of the two classes.

Implementation

An example of this pattern was made available by Christina Rogers in her refactoring of the Getting Started Window in creation of her Project Wizard class. Information about this example is available here:

http://eyesonvis.blogspot.com/2006/08/object-oriented-getting-started-window.htm l

 

The general idea is this: Create a third class that has the new functionality. The new class is frequently something like “Function Helper” or “Function Assistant,” where “Function” is whatever functionality is to be delegated. Give that class all the data fields necessary to carry out the new functionality. Then add that third class as a data member to the two classes that need to share the functionality.

 

The two descendent classes override the ancestor’s dynamic VI. But they only put in a bit of code necessary to call the exact same method on their data member of the helper class. The actual work is entirely in that method of the helper class. Now there is only a single instance of the code, which makes bug fixing easier. The two descendent classes have delegated the work to a third class – thus the name of this pattern.

 

You might think, “Why not just have a common subVI that isn’t a member of either class?” The answer is that there may well be some state data associated with the helper object. It may have fields of its own which you would have to add to both of the caller classes and then keep in sync. By making it an object, you fully encapsulate this bit of functionality.

Editorial Comments

[Stephen Mercer] This pattern is not listed explicitly in the Gang of Four text. The idea occurs as an aspect of several other patterns. I think it is useful enough to warrant specific attention.


Channeling Pattern

Intent

To provide a guaranteed pre-processing/post-processing around some dynamic central functionality.

Motivation

You have a class hierarchy. And you have some algorithm that you want to implement on the parent class. There is a core step of the algorithm that needs to be dynamic dispatch so that each child class can provide its own behavior. But it is unsafe to call that step directly – there are pre-conditions that must be checked or post-conditions you want to enforce. So you need an interface that guarantees the core is only called from a particular entry point.

Implementation

See “ChannelingPattern.zip”

 

To make this pattern work, give the parent class a static dispatch VI that implements the algorithm. That way the child classes cannot override the algorithm itself and thereby avoid calling the pre-processing/post-processing. Make the core step of the algorithm a dynamic dispatch VI that the child classes can override. Let the dynamic dispatch VI be protected scope in the parent class. This forces all the children to use protected scope for their override VIs. No VI outside the class hierarchy will be able to call the core VIs directly.

 

This pattern does not prevent child classes from calling the step on themselves or other child classes without going through the algorithm. It does make sure that all the VIs outside the class hierarchy go through the algorithm VI.

 

The name of this pattern refers to the control of the flow of data – the data is channeled through the static VI to get to the dynamic VIs.

Editorial Comments

[Stephen Mercer] A customer asked me why all member VIs in classes aren’t dynamic dispatch. After all, the performance overhead of dynamic dispatching is pretty small compared to a regular subVI call. And the amount of overhead is a fixed constant amount. So it made sense to the customer to make all the VIs dynamic dispatch and only make static dispatch VIs if a performance problem actually arose. That would give maximum flexibility to the class. This pattern is one of several reasons not to make every VI dynamic dispatch. If the algorithm itself were dynamic dispatch, child classes might decide to override the algorithm and skip the pre/post processing.

 

This pattern is particularly useful for large programming teams where you might not know all the developers, or where child classes will be implemented by third parties. It lets the original author put compiler-enforceable guidelines into the code so that future programmers do not unwittingly write VIs that will create issues for the architecture.


Visitor Pattern

(adapted from Gang of Four’s Visitor pattern)

Intent

To write a traversal algorithm of a set of data such that the traversal can be reused for many different operations.

Motivation

Suppose I have an array of integers. I need a function that will calculate the sum of those integers (ignore, for the sake of argument, the fact that there’s a LV built-in function that does this). I drop a For Loop and iterate over every value, adding each element to a running total in an uninitialized shift register. The value in the shift register when the loop finishes is my sum.

 

Now suppose I want to find the product of those integers. I write the same For Loop and shift register, only instead of the Add primitive, I use Multiply. Everything about these two VIs is exactly the same except for the core action. How can I avoid duplicating so much code?

 

Dynamic dispatching does not immediately help in this case – I do not have any child types on any of the wires that I can dispatch on. I could make my traversal VI have a VI Reference input. Then, instead of using a primitive, I could use a Call By Reference node. The VI that I pass in would have either the Add or Multiply primitive.

 

This is helpful, but only works if my two VIs have exactly the same connector pane. What if I need other information, other parameters, to do the job in that Call By Reference node? What if there is more than one running total that I’m keeping track of? This pattern arises from the need to answer these questions.

Implementation

See “VisitorPattern.zip”

 

Instead of taking in a VI Reference, the traverse VI takes in an object of class Action. This is a class that you define. Create one child class of Action for each specific action you want to do during the traversal. Then instead of a Call By Reference node, call the Do Action.vi method. This will dynamically dispatch to the correct implementation. The Action object can have all sorts of data inside it that can augment the operation at hand. You can implement new Action children without changing the original traversal framework.

 

The traversal VI can get very complicated, far beyond the simple For Loop over an array that I mention here. As the traversal walks over a data set, the Action object “visits” each piece of data and updates itself, which explains the name of this pattern. Your visitor can collect a summary of information about the pieces of data (such as the sum and product calculations), or it might search for a particular value, or it might even modify the data as it visits (divide each value in the array by 2). The traversal works just as well in all of these cases.

Editorial Comments

[Stephen Mercer] I am tired of writing LV2-style globals. These globals are great – very dataflow efficient, very easy to understand. But you have to duplicate the VI every time you want to support a different data type. I figured there had to be a better way. I’ve been thinking about this implementation for a while. It may not be optimal, but I think it is an excellent starting point. Imagine… the possibility of never writing another LV2-style global VI ever again, because you could just reuse one and give it the particular action that you want. A whole new flavor of über-geek nirvana!


Singleton Pattern

(adapted from Gang of Four’s Singleton pattern)

Intent

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.

Motivation

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 means all VIs can access the data. But it does not mean that all VIs will access the global. This pattern describes a framework of classes that guarantee that single instance of data.

Implementation

An example of this pattern shipped with LV8.2. That version turned out to be a bad idea. A better implementation has since been found. Please see the revised version that shipped in LV8.6:

<labview>\examples\lvoop\SingletonPattern\Singleton.lvproj

 

In this implementation, we seek to guarantee 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 are the same functions that would normally be on the class itself. They are moved to the library so that the functionality is exposed without allowing the class itself to be dropped. Checkin.vi and Checkout.vi use a single-element queue 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.

 

Editorial Comments

[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.

 

I have further comments about data copies on the block diagrams of the Get Max Field VIs.


Conclusion

Those are the design patterns thus far. I hope to update the list as time goes by, both with more patterns and with better examples for the existing patterns. Pay attention to the code you write – when you find yourself following the same routine over and over, try giving a name to the pattern. See if you can clearly identify when it is useful and how best to do it.

 

Exploration of these patterns helps us design better code. Using the patterns is using the learning of previous developers who have already found good solutions. In studying the patterns, we can learn what good code “feels” like, and we are then more aware when we create an unmaintainable hack in our own programs.

 

When we follow the standard designs it helps others looking at our code understand what they’re looking at. If a programmer knows the general pattern being used, he or she can ignore the framework and focus on the non-standard pieces. Those points are where the program is doing its real work. It is akin to looking at a painting in a museum – the painting is the interesting part. The picture frame and the wall are just there to hold up the art. If either the frame or wall is too decorative, it pulls the viewer’s focus.

 

I do not talk about any anti-patterns in this document. Anti-patterns are common attempts that look like good ideas but have some generally subtle problem that will arise late in development. Some anti-patterns in other languages are sufficiently detectable that a compiler could even be taught to recognize them and report an error. Perhaps these exist in LabVIEW. We should be cataloguing these as well.

 

 

Let wire and node
gently flow into a world
of class and method.

LabVOOP


Downloads:
Average User Rating
(2 ratings)




Daklu Daklu  says:

After reviewing the Rabbit Squad class, I now understand why NI password protects many of their block diagrams--self documenting code at its finest! 

More Like This

  • Retrieving data ...