LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Adding new LVOOP class methods without modifying the original class

Hi All,

 

I have a very simple DUT communication interface class, DUT_Interfaces. It has 3 methods:

  • Init
  • Query
  • Close

DUT_Interfaces has 3 children:

  • USB
  • Serial
  • SSH

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:

  1. Case statement, depending on interface do different things to get log and post process
  2. LVOOP With Factory: Create a new parent class called logging with just one method called get_log. Create 3 logging children with exactly the same name as DUT_Interfaces children, USB, Serial, SSH. Each logging child overrides get_log. Implement Factory Pattern to get class name of DUT_Interfaces loaded...do strip and build path to load the logging children.

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!

0 Kudos
Message 1 of 16
(1,222 Views)

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.


GCentral
0 Kudos
Message 2 of 16
(1,206 Views)

On LV 2018, sadly, no plans to upgrade to 2020+

0 Kudos
Message 3 of 16
(1,199 Views)

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).


GCentral
Message 4 of 16
(1,185 Views)

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.

G# - Award winning reference based OOP for LV, for free! - Qestit VIPM GitHub

Qestit Systems
Certified-LabVIEW-Developer
Message 5 of 16
(1,171 Views)

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.

Message 6 of 16
(1,159 Views)

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.

 

https://en.wikipedia.org/wiki/Decorator_pattern

Message 7 of 16
(1,148 Views)

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 

test.png

 

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.

0 Kudos
Message 8 of 16
(1,132 Views)

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

Message 9 of 16
(1,123 Views)

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.

Sam Taggart
CLA, CPI, CTD, LabVIEW Champion
DQMH Trusted Advisor
Read about my thoughts on Software Development at sasworkshops.com/blog
GCentral
Message 10 of 16
(1,104 Views)