LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Which Classes Should Have Which Methods?

Solved!
Go to solution

@Gregory wrote:

@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?


One suggestion is to make the Status output type a class itself - then the different classes can output a status object of the relevant class and these would be coerced to the parent "Status" class.

Message 11 of 24
(1,064 Views)

@pauldavey wrote:

@Gregory wrote:

@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?


One suggestion is to make the Status output type a class itself - then the different classes can output a status object of the relevant class and these would be coerced to the parent "Status" class.


Paul's suggestion here is good. In order to display the contents though, you still need a common way to view the class data. Two options might be (and I'm happy to learn of others, because I don't desperately like either of these):

  • Subpanels - pass reference for subpanel to the "Status" class object with a dynamic dispatch "Display Status in Subpanel.vi", and have it embed it's own chosen subVI in the panel, displaying the status
  • Tables or Listboxes (or similar) - have each child Status output a common string representation suitable for some indicator

If you choose the latter, you could presumably also have the output of "Get Status" be the string representation, but it might be easier to parse a Status object if you wanted further operations that were common to all status forms.


GCentral
0 Kudos
Message 12 of 24
(1,057 Views)

@cbutcher wrote:

@pauldavey wrote:

@Gregory wrote:

@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?


One suggestion is to make the Status output type a class itself - then the different classes can output a status object of the relevant class and these would be coerced to the parent "Status" class.


Paul's suggestion here is good. In order to display the contents though, you still need a common way to view the class data. Two options might be (and I'm happy to learn of others, because I don't desperately like either of these):

  • Subpanels - pass reference for subpanel to the "Status" class object with a dynamic dispatch "Display Status in Subpanel.vi", and have it embed it's own chosen subVI in the panel, displaying the status
  • Tables or Listboxes (or similar) - have each child Status output a common string representation suitable for some indicator

If you choose the latter, you could presumably also have the output of "Get Status" be the string representation, but it might be easier to parse a Status object if you wanted further operations that were common to all status forms.


We often use JSON for these types of things. We will have a generate configure which can accept JSON. Internally it will convert that to the class specific cluster. For a read of the parameters/data the read will return a JSON formatted string. One benefit of this is that we can interact with other languages or pass data via web services. In a LabVIEW only environment the a 2D array of key-values pairs also works fairly easily.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
Message 13 of 24
(1,037 Views)

@cbutcher wrote:

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.

Thanks, the whole post helped a lot. One more question that has come up now: if one device has a feature or setting that no other device has, what is the correct way to handle that? I assume it will depend on the specific example. Say I have some oscilloscopes and 1 of them allows me to set the input impedance but others do not. Should I...

1. Create this VI in the parent. Default behavior is to throw an error at runtime. Override with the child that supports it to implement the functionality.

2. Same as #1, but default behavior is to ignore it and not throw an error.

3. Only create the VI in the child that supports it, so the programmer knows at edit time that it is not supported by other scopes.

0 Kudos
Message 14 of 24
(1,015 Views)

@Gregory wrote:

@cbutcher wrote:

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.

Thanks, the whole post helped a lot. One more question that has come up now: if one device has a feature or setting that no other device has, what is the correct way to handle that? I assume it will depend on the specific example. Say I have some oscilloscopes and 1 of them allows me to set the input impedance but others do not. Should I...

1. Create this VI in the parent. Default behavior is to throw an error at runtime. Override with the child that supports it to implement the functionality.

2. Same as #1, but default behavior is to ignore it and not throw an error.

3. Only create the VI in the child that supports it, so the programmer knows at edit time that it is not supported by other scopes.


Hate to say it but the answer to this one is "it depends". How likely is it that you will add other devices which will have the same functionality? Is it a problem if the parent class has a no-op for this method? Must your application rely on this particular operation or can it allow the no-op operation.

 

If this functionality would be limited to very few devices, I would probably create a child class for these specific devices and then inherit from that. If the presence of the no-op is not a problem, you may want to have it in the parent class since it will allow for extension. I have a case where I need a task to use a parent level class wire and there is some functionality that is only supported in some instances, but not all. However, the no-op operation is not a problem so the method is included in the parent. An example of this is a device level communication where some devices require a heartbeat message and some don't. The task simply calls the heartbeat method on the specified interval but if the device does not require there is no harm to have the no-op occur.

 

If the task requires this specific operation to function, you may want to define it in the child class. Though I would create a virtual child and derive from that. The task would use the virtual child. This allow you to define concrete instances of the virtual child allowing to ad more devices which support this functionality and also allow your task to operate with more than a single device.



Mark Yedinak
Certified LabVIEW Architect
LabVIEW Champion

"Does anyone know where the love of God goes when the waves turn the minutes to hours?"
Wreck of the Edmund Fitzgerald - Gordon Lightfoot
Message 15 of 24
(1,003 Views)

@Gregory wrote:

Thanks, the whole post helped a lot. One more question that has come up now: if one device has a feature or setting that no other device has, what is the correct way to handle that? I assume it will depend on the specific example. Say I have some oscilloscopes and 1 of them allows me to set the input impedance but others do not. Should I...

1. Create this VI in the parent. Default behavior is to throw an error at runtime. Override with the child that supports it to implement the functionality.

2. Same as #1, but default behavior is to ignore it and not throw an error.

3. Only create the VI in the child that supports it, so the programmer knows at edit time that it is not supported by other scopes.


Another tricky point. Option 4 is to invert the last few posts and consider having some sort of "Configure Scope" VI which accepts Key/Value pairs in string format or a Configuration object...

Regarding the 3 suggestions you had, I can't give a good answer (maybe someone else will) but I can give my thoughts:

  1. The benefit is that you can write your code in terms of the parent class, and change child at will at edit or run time. The obvious downside is that this isn't really true - in most cases you'll throw an error, at which point you need edit-time code changes anyway.
  2. Significant improvement in reconfigurability compared to 1, but now the problem is that if the impedance is a crucial setting to change, you'll proceed believing everything is perfect when in fact the instruction was ignored for scope B.
    1. 2b) Throw a warning. I mention it only to suggest against it, but it's a possibility. Here the problem is that if you know to check the warning, you probably also knew to not call the VI on some scope types. If you didn't know to check for a warning, you might go happily on your way for some time without seeing it, and it could be overwritten by a real error and hidden
  3. This is a bit more tempting here, but has all the same problems already discussed in previous posts with limiting your ability to change the child class. If that's acceptable (because in your example it's crucial that you set the impedance correctly) then you can use To More Specific Class and a BD constant for the child class in the parent wire generally to get a child wire in a specific location for this operation. If you change the child class (from ScopeA to ScopeB), you'll get a run-time error that you can't cast a ScopeB object on a ScopeParent wire to a ScopeA object in that location. Then you have to go change the code that calls the "Set Impedance.vi" or whatever. If you use a ScopeA wire with a ScopeA constant, then you get an edit-time error when you change the wire type to ScopeB, which is perhaps preferable (but if you're storing your Scope in private data of some other class, it can be annoying to have to change it around from A to B to C to... compared with just having a ScopeParent in the data and bundling a ScopeA at initialization or whatever).

As you can already see, there are ups and downs to each of these options. You're trying to build a Hardware Abstraction Layer but your hardware items don't all neatly fit the same abstraction (they never do... 😞 ). If you minimize the sharp edges you at least get something that helps you reuse your code without having to rewrite the same thing over and over.


GCentral
Message 16 of 24
(1,001 Views)

Alright, thanks for taking the time to break it down. I will try some things and see what works best. At least I have a better idea of what I'm after now!

0 Kudos
Message 17 of 24
(992 Views)

@Mark_Yedinak wrote:

...

Is it a problem if the parent class has a no-op for this method? Must your application rely on this particular operation or can it allow the no-op operation.
..,
If the presence of the no-op is not a problem, you may want to have it in the parent class since it will allow for extension. I have a case where I need a task to use a parent level class wire and there is some functionality that is only supported in some instances, but not all. However, the no-op operation is not a problem so the method is included in the parent. An example of this is a device level communication where some devices require a heartbeat message and some don't. The task simply calls the heartbeat method on the specified interval but if the device does not require there is no harm to have the no-op occur.

Just make sure you wire across the class data wire in your no-op. Otherwise it’s not a no-op!

 

I think based on your example it makes sense to at least have a virtual class for oscilloscopes and then just decide where device specific functions should go. I think defining it at the class level only is good since the main occasion for not having the child class wire is when you are iterating through a bunch of devices without regard for what they are. If you’re wanting to do a device-specific operation then chances are you have initiated a specific class and have that wire type. It also keeps your parent class tidy.


 

0 Kudos
Message 18 of 24
(967 Views)

#3. I have done it like that (and tried some other ways) and it is usually simple and works, but can be rigid and can bloat the class. Did anyone mention "Favor Aggregation over Inheritance"? When I hear "some classes are the same for some methods", then I'm thinking it's a common function that could be described as a class. Consider that inheritance is a very strong relationship and that an OO-designer should favor loose coupling. If you google that phrase, you will find much to read where this issue is addressed. Or read e.g. here.

Certified LabVIEW Architect
Message 19 of 24
(943 Views)

For ease of clicking, the link above should probably be this one: thols' link (What are the advantages of Composition and Aggregation over Inheritance?)


GCentral
0 Kudos
Message 20 of 24
(903 Views)