Example Code

Applying Common Object-Oriented (OO) Design Patterns to LabVIEW

Products and Environment

This section reflects the products and operating system used to create the example.

To download NI software, including the products shown below, visit ni.com/downloads.

    Software

  • LabVIEW

    Other

  • OOP

Code and Documents

Third-Party Code Repository

Some users manage their code in repositories outside of ni.com. Use your best judgment when following links to third-party sites. https://forums.ni.com/docs.html

Description


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.

 

Stephen Mercer,

LabVIEW R&D

 

Design Patterns

  • Template Method (aka Channeling) Pattern - To provide a guaranteed pre-processing/post-processing around some dynamic central functionality. Allows child classes ability to override some steps of an algorithm without giving them the ability to change out the entire algorithm.
  • Factory Pattern - 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.
  • Hierarchy Composition Pattern - To represent a single object as a tree of smaller instances of that same object type.
  • Delegation Pattern - To have two independent classes share common functionality without putting that functionality into a common parent class.
  • Visitor Pattern - To write a traversal algorithm of a set of data such that the traversal can be reused for many different operations.
  • Aggregation Pattern - To treat an array of objects as a single object and define special behavior for that particular type of array.
  • Specification Pattern - 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.
  • Singleton Pattern - Guarantee that a given class has only a single instance in memory
  • Decorator (Wrapper) Pattern - Extend a concrete object’s responsibilities dynamically. Extend the features of an existing class without refactoring tested code.
  • Strategy (Policy) Pattern - Change the behavior of a class's method without having to edit the method's implementation.

 

Application Examples

 

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

 

 

Reference:

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

Addison Wesley Longman, Inc.

Description-Separate-2
Elijah Kerry
NI Director, Software Community

Example code from the Example Code Exchange in the NI Community is licensed with the MIT license.

Comments
george_ford
Member
Member
on

I appreciate all of this effort. I have been personally been working on doing what you have done on my own, and have struggled. I have read this LVOOP overview article  and this discussion concerning the reason for the OOP design choices that were made, and I still find myself in the category of in this quote from that last article of, "<Constructors, being> so fundamental to the language cannot be so completely hidden, and the person feels either ashamed for not being able to find it or angry at us for making it so hard to find!"

 

From the way I am looking at this code, this does not reflect the Strategy Pattern correctly. In the Strategy Pattern, you have a Behavioral interface with specific implementations of said Behavior. So, "HeatBehavior" could be a interface and the implementations would be, "Bake", "Boil", "Broil", "Microwave". The HeatBehavior would provide a "virtual" function, "cook", and each implementation would then implement that function accordingly.

Then, you have a Client interface that is composed of a Behavior object. The Client interface would provide a public function, "doCook", which would call the Behavior object's "cook" function. So, in text language, it would look like (ignoring memory leaks and whatnot):

 

 

 

class KitchenDevice {
private:
    HeatBehavior* m_heatBehavior;
public:
    void setHeatBehavior(HeatBehavior* hb) {
        m_heatBehavior = hb;
    }
    void doCook() { m_heatBehavior->cook(); }
};

 

 

 

You then create implementations of "clients" of the "KitchenDevice"

 

 

 

class Oven : public KitchenDevice {
public:
    Oven() { setHeatBehavior(new Bake{}); }
    // NOTE: the "Bake" class implements HeatBehavior with it's own behavior
};

 

 

 

 If you are in a rush, you implement the "Microwave", and cook. If you have more time, create the Oven and cook:

 

 

 

void performCook(KitchenDevice* kd) {
    kd->doCook();
}

int main() {
    Oven*      oven = new Oven{};
    Microwave* mw   = new Microwave{};
    Broiler*   b    = new Broiler{};
    performCook(oven); // performs baking
    performCook(m);    // performs microwaving
    performCook(b);    // performs broiling
    
    return 0;
}

 

 

 

The potential power of this pattern in LabVIEW becomes clear with, let's say, a power supply.

1. Create behaviors common in interacting with a power supply: SetVoltageBehavior, SetCurrentBehavior, etc

2. Then provide implementations for those: some power supplies communicate via SCSI, some via direct voltage communication. You could even have a 5V supply via a digital switch. All of those will implement the SetVoltageBehavior, SetCurrentBehavior, etc in their own way.

3. Then, create a base client: PowerSupply with implementations of these.

4. You essential create a library of "PowerSupply" implementatins. Whenever you get a new one, you just 1. create a new implementation of the Behaviors and then an implementation of the PowerSupply client.

 

Ok, (writing much more than I intended)...

Having said that, I have been unable to create a LabVIEW class hierarchy that can do this. I won't get into the issues (I've typed enough), but if anyone can provide the above format in LabVIEW code, I'd appreciate it.

Also, if it is already included within the code and I have missed it, apologies.

Thanks

george_ford
Member
Member
on

I discovered where I was going wrong.

  1. I was creating dynamic dispatch vi's in the base client class that set behavior classes. In those setter vi's, naturally, I used the base class of the behaviors. Upon trying to override those setters in the child classes, LabVIEW prevented me because I was trying to write implementations of the base behavior classes into a node that takes a base behavior class in the client's base setter classes (I apologize if hard to follow).
    • SOLUTION: LabVIEW prevents ANY mismatch in types when providing dynamic dispatch vi's. This actually forced me to structure the class more correctly by making me create static dispatch vi's in the client base class to set the behaviors.
    • With static dispatch vi's, the vi nodes can take any child class of the parent class. Again, this is not so with dynamic dispatch vi's (someone please correct if I am wrong)
  2. I then created my own constructor vi's in the child classes. These "constructors" call the static setter vi's from the base class and assign implementations of the base behavior class. Again, since a static vi node that is connected to a base class object can later be used to attach a child object of that the parent class, this is exactly what I do in the constructor vi's.
  3. This means that I never directly use the child classes. Instead, whenever I need an implementation of a particular child class, I place the constructor vi of that class in there.

I'm sure this isn't very easy to follow, but I did want to follow up and mention that I found a solution to my problem.

If anyone knows of a better way than this, particularly that of having to create "constructor" vi's, I'd like to know.

kmurphs
Member
Member
on

Hi Elijah,

 

The last link in the "Application Example" is a broken link (Hardware Abstraction Layer). I'm not able to find it on NI's website.

Would you please update it?

 

Regards 

Ryan_Wright_
Member
Member
on

I believe that most of the links above moved to the LabVIEW Development Best Practices Documents page here:

 

https://forums.ni.com/t5/LabVIEW-Development-Best/tkb-p/7305

Johnzahey
Member
Member
on

the link for pattern gives me error. Are there any new links?