This is from a presentation I once gave at a NI User Group in Vancouver, Canada, about Object Oriented Programming in LabVIEW. It's really basic and aimed at beginners. Let's start with the UML Class Diagram right away so you can refer to it as we go. Even if you're not familiar with UML yet, don't worry for today this is self-explanatory enough.
Disclaimer: I'm using a fantasy world with knights, wizards, and trolls but I'm in no way an expert in those domains so please overlook all the wrong statements I might make about those 🙏
We are going to create different types of characters: knights, wizards, and trolls. Each one of them becomes a class (literally a *.lvclass file). As you can already see, we'll also have a class called Character, from which all other classes will inherit. We also say that the other classes extend Character.
In our case, it won't be possible to create a character who is just a character. We'll have to choose between the 3 concrete classes, as opposed to Character which is therefore an abstract class. That's why in the UML Class Diagram above, the class name - Character - is written in italic.
The 3 concrete classes have some attributes in common:
they all have a name (one knight could be called Aragorn and another knight could be called Lancelot)
they all have a health level that can decrease as they get hit
In LVOOP, attributes are stored inside the class private data (*.ctl). You will only see it inside the LabVIEW project, you won't see a *.ctl file on disk. It's the *.lvclass file that contains its own private data.
The 3 concrete classes also have some methods in common:
they all can have their special ability queried
they all can be hit
they all can be named...
In LVOOP, each method is simply a *.vi file.
The concrete classes do have some differences of course. For example, the wizard has a magic ability which other characters don't have. It stores its value inside its own private data (Wizard.ctl).
As for methods, every child class (the concrete classes that inherit from their parent class) has the choice to override its parent's methods or not. (there is actually a way in LVOOP to force children to override certain methods but let's keep that for another time).
Let's take a step back: the whole point of OOP is abstraction. Once we have defined all of this, we want Main.vi to be able to manipulate Character objects without having to know from which concrete class they were instantiated. In the video below, when I hit a character, the code inside Main.vi doesn't behave differently at all based on the type of character. It simply calls the Hit.vi method on the character object. Through what is called dynamic dispatch, LabVIEW then knows to call the child method that overrides the parent method if there is one, otherwise it calls the parent method.
Some examples:
Get Name and Health.vi is not overriden by any child class, since this operation is unrelated to the type of character
Get Special Ability.vi is overriden by Knight and Wizard but not by Troll, because they don't have any special ability. So the parent class will be called, which will just return an empty string in this case.
A place where LVOOP differs from other OOP laguages is that it forces all class attributes to be private. So when I hit a character, the Hit.vi method of the child class cannot directly modify the Health attribute since it belongs to the parent class.
When inside a Knight method, I can only unbundle the private attribute of the Knight class: its armor value.
I could try to cast the object to a character class, which is valid since a Knight IS a Character (that's the definition of inheritance), but then LabVIEW still doesn't grant me access to the private attributes of the Character class.
Finally, I could cast my Knight to the Character class, call some methods of the parent class that handles the health reduction, and then cast my object back to the Knight, since it's what it really is. But this really is bad practice.
Instead, by far the best practice is for the child method to delegate the job to its parent method. It is overriding it, but it is also calling it, so both the child and the parent method will execute. Here, the method Hit.vi of the Knight class computes how much health should be subtracted based on the armor level of the knight, and then it passes this value to the Hit.vi method of Character, which handles the health reduction.
That last bit might be confusing: do we have a Knight object AND a Character object? No, we have a single Knight object, created by instantiating the Knight class, but at the same time it also IS a Character, and therefore contains Character's attributes.
Another popular example is shapes:
A Shape can have a color, but we can't really say much more. We want to make it abstract, we can't programmatically create a Shape that is "only a Shape".
If we create a Rectangle class which extends Shape, we need its width and length and we are able to draw it, so it's a concrete class. One of its methods might be to compute its diagonal. This wouldn't be a method of its parent, since not all Shapes have diagonals, think of Circles for examples.
Now we can also make a Square class which is a special kind of Rectangle. We make it inherit from Rectangle and now it can override the method that computes the diagonal.
A quick video showing Main.vi in action.
Download ZIP file (LV2023Q3)