09-17-2019 12:52 AM
@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.
09-17-2019 01:23 AM
@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):
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.
09-17-2019 10:18 AM
@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.
09-17-2019 11:16 AM
@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.
09-17-2019 11:39 AM
@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.
09-17-2019 11:42 AM
@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:
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.
09-17-2019 11:53 AM
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!
09-17-2019 04:55 PM - edited 09-17-2019 04:56 PM
@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.
09-18-2019 01:08 AM
#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.
09-18-2019 07:18 AM
For ease of clicking, the link above should probably be this one: thols' link (What are the advantages of Composition and Aggregation over Inheritance?)