03-16-2022 08:51 PM
Hi All,
I have a very simple DUT communication interface class, DUT_Interfaces. It has 3 methods:
DUT_Interfaces has 3 children:
Each children has it own override method of the 3 parent methods.
This whole thing has been working fine for the past year or so...but today I got a new project that requires some ability to grab logs from the DUT. This requires me to use the query method along with some extra processing logic that will be different for each interface. However, since I don't believe this new ability will be widely use beyond this project, I don't want to go back to DUT_Interfaces and add a new method. Is there a way to add new functions without changing my original DUT_Interfaces parent or child?
Here are couple tests I tried that worked:
I want a OOP solution, so 1 is no go. 2 is working based on some testing I did, but was wondering if there is a more straight forward solution then the way I am doing it by playing around with a Factory Pattern (this is on top of the factory pattern I already have to load DUT_Interfaces).
Thanks!
03-16-2022 10:21 PM
Which version (year) of LabVIEW are you using?
If 2020+, you could create an interface for "log querying", and then create 3 new children (one for each type) inheriting from the existing children, and adding the interface.
I'll have to check the usability in terms of a storable wire type though... Might end up needing to cast occasionally, which could be annoying.
03-16-2022 10:29 PM
On LV 2018, sadly, no plans to upgrade to 2020+
03-17-2022 12:10 AM
The (perhaps) 'cleaner' solution is similar to your 2., but is a bit of a pain to implement in LabVIEW.
You create your "DUT_WithLogging" class, and give it 4 methods (Init, Query, Close, Get Log).
If the 'Get Log' behaviour is different in each child (USB, Serial, SSH) then you create 3 children, as you've done.
In each child class, store a copy of the original child in private data (so USB_WithLogging contains DUT_USB in private data).
For the other 3 methods, use Unbundle/Bundle (or In-Place Element Structure) to get the private implementing class, and then call that class' method.
Alternatively, store a "DUT_Interface" in the "DUT_WithLogging" and add an additional (protected) method like "Get Implementor", then have each of your <type>_WithLogging children override the "Get Log" and "Get Implementor" methods, with the latter returning the DUT_<type> class.
Again, store this in private data (in this case, in the DUT_WithLogging private data) and then instead of overriding the Init/Query/Close, implement those in DUT_WithLogging to call DUT_Interface, using the stored class (again, {Un,}Bundle or In-Place Element Structure).
If the number of methods is large, the second is more tempting. You can also make the (3) methods Dynamic Dispatch and just not override in the logging children, unless their behaviour requires customization (in the context of logging - presumably in the original context the nested private object has the appropriate implementation).
03-17-2022 02:55 AM
Hmm, i like the idea of creating subclasses of your 'Interfaces', USBwLog and so on. This class would have new protected/private functionality for the logs. For simplicity you can add a qualifier in the Query itself, like start with "Log_" and in such case perform these functions.
Otherwise you can add a new public function GetLogs (or what you're after), but then you're in the classic problems of:
"Should i add this as a base object function just to be able to use base class code in the project, or should a cast the classes dynamically each time i need to call it?"
To that question there no clear cut answer.
03-17-2022 06:37 AM
You can add a new child and add methods to it without changing the parent.
You just won't be able to use the methods threw polymorphism.
You can use the methods, but you'd have to cast to the child. This is very not OO-ish, but a result of not wanting to add to the parent.
An interface would have the same result: to use the new methods that implement the new interface, you'd have to cast to the interface to be able to use the new methods.
03-17-2022 07:20 AM - edited 03-17-2022 07:37 AM
Do you need any protected methods from the parent class?
If not, don't solve this with inheritance, use composition.
It's the "decorator pattern". You add your parent class as data in your new class, and do whichever processing you require through the decorator class. Solved. You can have as many decorators as you like. If you make an intermediate "decorator" class from which all of your "base decorator" classes inherit from, you can even put a decorator insiude a decorator. It then bridges the gap between "decorator pattern" and "composite pattern".
To change from one decorator to another, no casting is required, just pass the base class from one decorator to another and you're done.
03-17-2022 09:15 AM
Hi Everyone,
Thanks for all the suggestions. My question at this point is how do I still make dynamic dispatch at runtime work with these solutions. For example, if I pick USB for DUT_Interface, my DUT_Interface factory will load the USB class into memory...is there anyway to then take this USB DUT_Interface class and have it dynamic dispatch something in my logging class (whether as an intermediate class or an interface class) to find the right concrete logging class (USB_logging, Serial_logging, etc).
This is what I was trying to do with my solution 2, I basically
With the solutions y'all have given, it seems like I will still have to play setting up the factory a second time to get dispatching to work.
03-17-2022 10:15 AM
@Mikejj wrote:Thanks for all the suggestions. My question at this point is how do I still make dynamic dispatch at runtime work with these solutions. For example, if I pick USB for DUT_Interface, my DUT_Interface factory will load the USB class into memory...is there anyway to then take this USB DUT_Interface class and have it dynamic dispatch something in my logging class
I do not completely understand this.
Is the logging class a child of DUT Interface? That doesn't seem right.
@Mikejj wrote:
is there anyway to then take this USB DUT_Interface class and have it dynamic dispatch something in my logging class (whether as an intermediate class or an interface class) to find the right concrete logging class (USB_logging, Serial_logging, etc).
In general, if you want to call a child's methods that the parent doesn't have:
1) Add the method to the parent, use 'normal' Dynamic Dispatching.
2) Cast to the child, use it's methods as usual.
3) Use VI Server (DIY Dynamic Dispatching).
1) is by far the most maintainable.
Is there any reason you want to avoid changing the parent at all costs? Esp. since the parent is very simple, adding to it doesn't seem a high risk. I don't see how adding a method to the parent can change anything to the existing behavior.
03-17-2022 06:36 PM - edited 03-17-2022 06:37 PM
Lookup the GOF Decorator pattern. It will do exactly what you want.
Create a new child class that is logging enabled. Have it own an object of the parent type.
For each method call have it delegate to the owned object and before (or after) calling the parent node, have it log whatever you need to log.
Edited to add:
I didn't read down far enough to see that Shane had already suggested the decorator pattern. Great minds think alike.