From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Which Classes Should Have Which Methods?

Solved!
Go to solution

Hi all,

I have been using LabVIEW for a while but never bothered much with classes. Now I'm writing instrument drivers for some of our devices. They share a lot of functionality, but at some point each device is different. I'm unsure at what level to create some methods (VIs). The cases I'm running up against are:

1. VI functionality is the exact same for all children. Should I create the VI in the parent and simply call with an instance of the child object as an input?

2. VI functionality is different for all children. Should I create a shell in the parent that gets overridden in each child? Or is that not necessary, and I should create each VI separately in each child?

3. VI functionality is same for some children but not all. Should I create a VI in the parent that covers the most children, and override in the children where it is different?

0 Kudos
Message 1 of 24
(2,798 Views)
Solution
Accepted by topic author Gregory

1. Yes.  (And any possible future child that needs to do things differently can override)

2. Yes, create the shell function at the parent level. (And you can check the box that requires children to override it.)

  At coding time, you want parent-looking functions and wires on your block diagram.  At run time, you'll have child-level objects traveling those wires and calling appropriate-level member functions via dynamic dispatch.

3. I've done it that way, but I'm not sure if it's a consensus good practice.  I was supporting a very limited set of instrument models and did the easiest thing.

 

 

- Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 2 of 24
(2,766 Views)

Hi Gregory,

  1. Yes - this will work with Static or Dynamic dispatch. Note that if you make it static dispatch, you can't override it in any children (but that to change is only a few clicks on the ConPane).
  2. If you want to be able to use your classes interchangeably (sounds like the general purpose, and is typically what you want) then you must use Dynamic Dispatch and create it in the parent. This VI can be an empty shell, as you described. If you want, you can have it set to require overriding in the class properties (then there will be a compile-time error if child classes don't override it - although they can transfer that responsibility to their children if they want)
  3. Exactly. If you have partial functionality you want in all children, then some have variations on part of the method/VI, you might want to look into the Template pattern. An example can be found with Actor.vi and Actor Core.vi - basically you have a parent, non-dynamic (static) VI with the common functionality, which calls a dynamic-dispatch VI (blah blah core.vi) (might be empty in the parent) which can be overridden to provide additional functionality without making it "all or nothing" regarding the Call Parent Method.

GCentral
0 Kudos
Message 3 of 24
(2,759 Views)

For point 3, you might consider whether those children with common functionality should have their own parent class (which itself is a child of your existing parent class).  It depends if this make sense or not.

0 Kudos
Message 4 of 24
(2,730 Views)

@Kevin_Price wrote:

 

  At coding time, you want parent-looking functions and wires on your block diagram.  At run time, you'll have child-level objects traveling those wires and calling appropriate-level member functions via dynamic dispatch.

 


Hi Kevin, can you explain why I want parent-looking functions on the block diagram? If one child has some functionality that no other class has, would I want to skip the parent shell in that case?

0 Kudos
Message 5 of 24
(2,708 Views)
Solution
Accepted by topic author Gregory

@Gregory wrote:

@Kevin_Price wrote:

 

  At coding time, you want parent-looking functions and wires on your block diagram.  At run time, you'll have child-level objects traveling those wires and calling appropriate-level member functions via dynamic dispatch.

 


Hi Kevin, can you explain why I want parent-looking functions on the block diagram? If one child has some functionality that no other class has, would I want to skip the parent shell in that case?


I know I'm not the desired respondent, but the idea here is that the parent wire type determines the icon that will appear on your calling block diagram.

 

If you wire a child block diagram constant, they will change to the specific child, but if you for example load the class by path and specify that it is a parent.lvclass object (because children are allowed in this case - child is a parent) then the wire type remains parent.lvclass even though the object (class instantiation) is child.lvclass. The behaviour is then as for the child class.

 

If you use a VI that exists only in a child, you are unable to use the parent wire type - the wire must be a child. This prevents you changing to a different child, either at run time or at edit-time (you'd have to at least remove the child1.lvclass:uniquesubvi.vi)


GCentral
Message 6 of 24
(2,704 Views)

@cbutcher wrote:

Hi Gregory,

  1. Yes - this will work with Static or Dynamic dispatch. Note that if you make it static dispatch, you can't override it in any children (but that to change is only a few clicks on the ConPane).
  2. If you want to be able to use your classes interchangeably (sounds like the general purpose, and is typically what you want) then you must use Dynamic Dispatch and create it in the parent. This VI can be an empty shell, as you described. If you want, you can have it set to require overriding in the class properties (then there will be a compile-time error if child classes don't override it - although they can transfer that responsibility to their children if they want)
  3. Exactly. If you have partial functionality you want in all children, then some have variations on part of the method/VI, you might want to look into the Template pattern. An example can be found with Actor.vi and Actor Core.vi - basically you have a parent, non-dynamic (static) VI with the common functionality, which calls a dynamic-dispatch VI (blah blah core.vi) (might be empty in the parent) which can be overridden to provide additional functionality without making it "all or nothing" regarding the Call Parent Method.

Regarding #2, these classes don't have to be interchangeable. The test stations are very device-specific. But I would like to follow general best practices.

Regarding #3, thanks, extending the "Call Parent Method" is exactly what I wanted to do for some methods!

0 Kudos
Message 7 of 24
(2,703 Views)

@pauldavey wrote:

For point 3, you might consider whether those children with common functionality should have their own parent class (which itself is a child of your existing parent class).  It depends if this make sense or not.


Actually, I do have some intermediate classes to further group the devices and reduce the amount of code duplication. I did not mention this because I did not want to muddle up the question, but thank you for bringing it up.

0 Kudos
Message 8 of 24
(2,696 Views)

@cbutcher wrote:

If you use a VI that exists only in a child, you are unable to use the parent wire type - the wire must be a child. This prevents you changing to a different child, either at run time or at edit-time (you'd have to at least remove the child1.lvclass:uniquesubvi.vi)


I see, then I will definitely keep this in mind if I have to load the class by path. What if each child has a method to get the device status, but each status is a slightly different typedef'd cluster? In that case I have to create the method only in the children because overrides cannot change anything on the connector pane, correct?

0 Kudos
Message 9 of 24
(2,693 Views)

I almost never load classes by path (just to get the versions of my PPLs, which are all scripted to add a VI with a fixed connector pane and name when compiled, and which has nothing to do with OOP) but I still suggest that you'd want to try and avoid directly calling child-only methods.

 

If you do this, you might as well not bother with inheritance (moderate exaggeration, but not excessive...) because you must make significant edit-time changes to change your child class.

 

Suppose you create a parent "DMM.lvclass" and then a bunch of child classes, one per manufacturer. The communication methods may vary wildly, but you conveniently manage to get them all to share an API (Read Voltage, Read Current, Set Range, whatever).

 

If you now create two separate test stations using your "DMM.lvclass" VIs (the parent level code) then you can just drop "ManufacturerA_DMM.lvclass" on one block diagram and use exactly the same code in a second setup, changing only a single block diagram constant.

 

If you start to use child class VIs, then you can't do this - you'll get broken wires when you change the BD const, because the wire type is constrained to be something that can be passed to "ManufacturerA_DMM.lvclass:MySpecificGetStatus.vi", presumably only objects of type "ManufacturerA_DMM.lvclass".

 

The specific example you mention is a pain...


@Gregory wrote:

What if each child has a method to get the device status, but each status is a slightly different typedef'd cluster? In that case I have to create the method only in the children because overrides cannot change anything on the connector pane, correct?


Yes - you can't change the connector pane and use dynamic dispatch. At this point you have to consider how generic you want your code to be. If you're using LabVIEW 2017 SP1 or later, you can consider malleable VIs (VIMs), which will adapt to classes if they have the same name and suitably similar connector panes. I've found them tricky to work with (and you can't make them part of a public interface to a PPL, so I've more or less avoided using them).

 

An (undesirable) alternative is to use a variant as the output type. Now you can have a shared connector pane, but your child code has to use Variant to Data to be able to get a real output, at which point the benefit of dynamic dispatch is reduced (you still need to edit the code to have the appropriate indicator type and so on).

 

If you're going to end up editing the code anyway (change indicators etc), having child specific objects is a possibility.

 

It may be possible to use an object typically with dynamic dispatch, and then have a "Get Status" VIM that checks for Get Status.vi in each child and provides the appropriate output. In this case you cannot have the method exist in the parent (you'll get an error about trying to override a non-dynamic dispatch VI, if non-DD, or about non-identical CPanes if DD). I haven't tried this.

 

My suggestion would be see if you can find things that can unify your devices.

Taking a series of power supplies as an example, perhaps ChildA has a status that includes ChA_ConstCurrent, ChA_ConstVolt, ChB..., and ChildB has only ConstantC, ConstantV values.

In that case, you could have an array of clusters of CC,CV values, one element per channel in the power supply.

 

It can make any individual child a little more tricky to work with, but improve the reusability of your framework/infrastructure-level code when changing DMM/Power Supply/whatever if you do this. I suppose you have to evaluate based on your situation (and try multiple options if time allows, see which you like best).


GCentral
Message 10 of 24
(2,679 Views)