LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Private base class method with child class overwrites?

I have a base class and two children classes. The base class defines a Create() method which is set to "Descendants must override". In my implementation it doesn't make any sense for the user to directly call the base class Create(), he always should call a child implementation.

 

So I thought I just set the base class method private, but this does not work for whatever labview implementation reasons. How can I enforce a broken VI if the user accidentally wants to use the base class method? (I was thinking about making the base class implementation broken on purpose, but I think this would be a really poor programming solution not to mention that in certain cases the child class may want to call the parent class method)

 

I used dynamic dispatch for the overriding VIs.

 

Thanks.

0 Kudos
Message 1 of 13
(1,299 Views)

Yes, this is a very poor solution. 

Consider also that you cannot have overwridden methods with different scopes (public, protected). Moreover, dynamic dispatch methods cannot be private. They cannot be private because there'd be no way to override them.

 

Anyway, I see 2 ways to address your issue:

  • Design your high-level code so that the base class method can never be called directly. This is the preferred approach. Remember that you control the architecture, not the user. In other words, you should structure your code so that only child classes can be passed to the parts of the application that invoke Create().
  • The Create() method of the base class should return an error (i.e. 'Not supported error'). In this way, you intercept and handle the error.

Bonus option (although I cannot know whether it fits your architecture):

You could remove the Create() method from the base class and instead define a static Create() method in each child class, making that method responsible for instantiating its corresponding class.

In this approach, there is no relationship between the different Create() methods, and, more importantly, your code will only instantiate a child class through the child class's own Create() method.
That said, I don't want to confuse the discussion. The two options above may already be sufficient for what you're trying to achieve, so this is just an alternative approach that may or may not fit your architecture.
Message 2 of 13
(1,278 Views)

Thanks. One drawback of not having a Create() method in the parent class (or outputing an error) is that child classes can't reuse it if its appropriate. This in theory could mean that many child classes need to implement the same Create() function. But as far as I see there is no way out of this in LabVIEW. (maybe an intermediate class, but eh... that also could have issues)

 

I control the architecture that's true. But when I said "user" I really meant other programmers who will work with these classes in the future. 

 

But all these are a rather low risk / low probability events in our environment, so even if I keep everything unchanged it will most likely work. Its a bit odd though to learn about these limitations of LV. I thought this stuff is pretty much there for every oop languages. But thats fine, LV must have its own legit reasons for implementing classes like this.

0 Kudos
Message 3 of 13
(1,250 Views)

You still have control even when you it comes to share code with other developer.

You should rely on method scope (private, protected, public) and on the distinction between static and dynamic dispatch. With these two mechanisms, you can expose only the specific functionality you want developers to access.

 

If you need to make the Create() method reusable, consider introducing a separate method (Init(), Initialize(), DoSomething(), or whatever()):

  • Create() => instantiate the object.
  • Init() => initialize the object. Child classes can either override it or reuse the parent's implementation.

Compared to other languages, LabVIEW does have several limitations, but I can assure you that the core OOP mechanisms are the same as in mainstream OOP languages such as C# and Java. You still have inheritance, encapsulation, method overriding, interfaces and access scopes.

0 Kudos
Message 4 of 13
(1,243 Views)

My class hierarchies tend to have different Create methods for the different child classes, as they often take different parameters. Of course you can make the Create function take a parameter object class that encapsulates such details but that can be often overkill. So my child classes usually implement their own specific "Create <class name>" methods that all have their unique name according to the class and don't overwrite any Create base class method. The base class may provide an Init method that all the Create methods usually call to do the common initialization for the base class private data and possibly can overwrite but then are required to call anyways..

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
Message 5 of 13
(1,216 Views)

I'd just set an error in the Base Create. And if it should be callable from children, check the incoming class ref so it isn't the base class. I don't understand why it should be inherited at all, usually i have unique Creates for a class.

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

Qestit Systems
Certified-LabVIEW-Developer
0 Kudos
Message 6 of 13
(1,199 Views)

I think that the name Create() is misleading.. I'm not sure that 1984 is using it as a real constructor...

0 Kudos
Message 7 of 13
(1,195 Views)

@Yamaeda wrote:

I'd just set an error in the Base Create. And if it should be callable from children, check the incoming class ref so it isn't the base class. I don't understand why it should be inherited at all, usually i have unique Creates for a class.


If you have 15 child classes need to be set up in the same way and 1 which requires a unique setup then would create 16 different setups of which 15 are the same? But as always: I have no idea, I just try asking questions to solve my problems, and there is always a chance that my question does not make sense.

0 Kudos
Message 8 of 13
(1,186 Views)

Basically the Create() usually has only a class output so needs to be specific to each class anyhow. If you have a lot of stuff that needs to be done for each of them that could be put into an Init() method in the base class.

 

Alternative is that you have to create a class constant of your specific child class and wire it to the class object input of your Create() dynamic dispatch method that you can overwrite in specific classes. But that is not really how a Create() object method should work. IMHO Create() really should be like a constructor for a class that creates the correct class object itself, not depend on a class constant that defines the desired class for a dynamic dispatch method. However I tend to have a high aversity to class constants on the user calling diagram. Your mileage may vary.

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
0 Kudos
Message 9 of 13
(1,168 Views)

@1984: are you familiar with other programming languages?

If so, what we are trying to convey is that Create() has the same purpose as the built-in constructor new (C# and Java), or __init__ (Python).
There are all constructors. Its purpose is to create and initialize an object using a specific set of input parameters.

 

Person person = new Person("John", 35);

 

The only difference is that LabVIEW does not enforce the use of a constructor pattern. Whether you use one or not is entirely a design choice made by the developer. Also, the name of the constructor is completely free.
 
Coming back to your last message...
 

@1984: If you have 15 child classes need to be set up in the same way and 1 which requires a unique setup then would create 16 different setups of which 15 are the same? 

The constructorS instantiate and 'configure' the child objects. The constructor should receive and store all the information required by the object.

You can then call a separate Init() method to perform the actual initialization operations using the data provided through the constructor. The base class implementation of Init() can contain the common initialization logic shared by the 15 child classes.

The 16th class, which requires a different initialization process, can override Init() and implement its specific logic.

This architecture works because Init() does not require class-specific input parameters. Everything it needs should already be encapsulated in the base or child class and populated before Init() is called.

 

For example:

  • Device is the base class and provides the common implementation of Init().

  •  The first child class is TcpInstrument that requires an IP address and a port: 
    TcpInstrument instrument = TcpInstrument.Create("192.168.1.10", 5025);

  • The second child class is SerialInstrument that requires a COM port and a baud rate:
    SerialInstrument instrument = SerialInstrument.Create("COM3", 115200);

  • Once the object has been created and the required data have been stored, Init() can be called without parameters.

  • Init() uses the information already stored in the object to perform the initialization. If both child classes share the same initialization process, they can use the implementation provided by Device. If one of them requires different logic, it can override Init().

 

Otherwise, to keep it as simple as possible, use a single method that accepts a string. Each child class can interpret this string differently. Fifteen child classes will use the inherited implementation, while the sixteenth will override it.

 

But again, I don’t know your architecture, so this approach may not fit it.
 
 
 
 
0 Kudos
Message 10 of 13
(1,130 Views)