Need a LabVIEW consultant?
Making Object-Oriented Code Easier to Modify
Let’s explore Liskov Substitution Principle and LabVIEW! Developing any robust software application requires deliberate planning and design and careful observance of established software engineering principles. Software engineers have created helpful mnemonics for remembering best practice design principles for designing object-oriented applications.
One such tool is the acronym SOLID, each letter representing a principle of object-oriented design (OOD). While adherence to any of these principles implies the need for the others, perhaps no other tenet is as central to object-oriented design and programming as the Liskov Substitution principle. Despite being at the conceptual core of OOD, this principle is often misunderstood and misapplied.
All five principles combined help make object-oriented code more readable, maintainable, and easier to upgrade and modify.
Definition of the Liskov Substitution Principle (LSP)
The Liskov Substitution principle (articulated first in a presentation by Barbara Liskov in 1988) states that objects of a parent class should be replaceable with objects of a child class without altering the properties of the program.
Another way of saying this is that a child class should be able to replace a parent class without requiring the redesign of the parent class or breaking the application. From yet another angle, a child class should be able to do everything a parent can (and maybe more).
Preconditions and Postconditions
To get more specific, it can be helpful to look at particular functions in terms of preconditions and postconditions. In computer science terminology, a precondition is something that must be true before a function can execute.
Preconditions can take various forms, but often they have to do with inputs: data types, data ranges, forbidden values, and format of the input. It is the responsibility of the caller to make sure that the preconditions are met.
Postconditions are things that must be true when the function has been completed. These often include output values, data validity, and data ranges. They also may include a guarantee of the state of different code modules or system resources.
For example, a postcondition for a hardware abstraction layer function may be that the instrument is inactive when the function returns. Postconditions are the responsibility of the function.
SOLID Design Principles
Single Responsibility Principle
Open/ Close Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Diversion Principle
Liskov Substitution Principle Applied in LabVIEW
LabVIEW is well suited for applying the Liskov Substitution Principle. More than anything, having the right mindset and disposition toward creating code that follows programming rules and principles will lead to good code.
Things to keep in mind:
- How can I divide the code up so that I never have to write the same code in two different places?
- What data types should I use so that child classes will always be able to use the parent interface?
- Which methods are universal and will be shared by all levels of the hierarchy?
LabVIEW Example 1
While the Liskov Substitution Principle applies to any object-oriented design, one classic application is designing Hardware Abstraction Layers (HALs). The goal of creating a HAL is to abstract the main application logic from the physical hardware. This way, the application is not dependent on a particular version or model of hardware. Also, with good hardware abstraction, certain design patterns can improve the efficiency of the application. For example, a VI could initialize all its instruments collectively without knowing precisely which instruments it is communicating with.
To see the LSP in action, let’s first look at an example of violating the principle. Suppose we are developing a HAL to abstract two instruments: a DMM and Power Supply.
The image below shows a class hierarchy of a class for each instrument and a parent class that provides the abstraction.
The Instrument class defines four methods: Initialize, Close, Read, and Write. At first glance, this looks fine. These are all typical functions that instruments perform. However, closer inspection reveals an issue with this structure. According to the LSP, every child class of Instrument must be able to fulfill all of the functionality of its parent.
Instrument implements Read and Write, but DMMs can only read and power supplies typically only write. With this implementation, DMM does not fulfill the Write function of the Instrument, and Power Supply does not fulfill the Read function of the Instrument. Users of the Instrument class hierarchy are led to believe that any instrument can read and write, and in this example, that idea is violated.
How might we redesign this class hierarchy to not violate the LSP? There are a couple of different ways we could do this.
First, we might add an intermediate layer to the hierarchy – in this case, two intermediate classes – such that each class defines a type of instrument: input and output.
LabVIEW Example 2
The previous example examined the behavior of classes expressed in methods, but what about the data? Suppose the Instrument class was written to connect with GPIB instruments. The class has many other useful functions that we want to use when deploying a DMM class so we have the DMM class inherited from the Instrument class. However, the DMM we are using communicates via Ethernet, not GPIB.
The class data for the parent and child classes are shown above. The instrument has a data element called GPIB Interface, and DMM has a data element called IP Address. The idea is that DMM ignores the GPIB interface and functions provided by Instrument and implements IP Address functionality instead. Users of the class hierarchy who want to use instruments do not realize that they need to supply IP address information for the DMM to work.
It also requires extra accessor functions to allow the user to set the IP address in the DMM class. This means that the specific instance of Instrument (DMM) must be known at edit time. Extra programming is required to get it all to work, and the interface (expressed as class data) defined by Instrument is made moot.
To fix this issue, we need to provide a way to initialize any type of instrument through our Instrument class. There might be many ways of doing this, but one simple method is to use a string data type to represent the instrument address. All instruments have addresses, and these addresses can be expressed as strings, whether they are VISA device names, IP Addresses, Serial Ports, or others. Then, each instrument can validate and convert the string into an appropriate type.
Key Takeaways: Liskov Substitution Principle and LabVIEW
Applying and adhering to the Liskov Substitution Principle can mean taking a more rigorous approach to development, planning, and designing code. However, it is a robust principle that leads to strong code that behaves as designed and expected and is much less likely to require redesign and refactoring in the future (see Open/Closed principle). Be kind to future you and follow this principle in your projects!
Examples of our LabVIEW Development
Cohu: Software Refresh
The goal of this software refresh project was to extend the life of Cohu’s product beyond what customers thought was possible.
FormFactor: Motion Control and Automation
Solubit’s development provides seamless communication with the motion control software to reduce exchange times by 60%.
SRAM: Controlled-Load Simulation System
A system that drives a bicycle drivetrain forward while applying a controlled load. Used for evaluating the capability of a front derailleur.