Jump to content

Fundamental Issues in understanding casting/inheritance?


Recommended Posts

Lets say I have a class hierarchy like this:
Class Heirarchy.PNG

NOTE:  Stole this hierarchy from this thread because it was helpful in understanding my problem.

The classes have functions that look like the below.  The intent is that any animal can have a name but only dogs can bark and only cats can meow. There is also a "Find Animal" VI that takes an array of animals and finds the first object with the provided name.
post-54134-0-18311600-1446068626.png
 
And I use it to write code that looks like this:

post-54134-0-04039600-1446068809.png

 

Obviously after the find animal I have a broken wire because any animal can come out of the find animal and the compiler cant guarantee that the "Meow.vi" will work on every animal since it is a method of the felidae class.

 

It is my understanding I have one of two choices at this point:

A:  I can add "Meow" and "Bark" as methods to the parent class, and make them dynamic dispatch.  I've done this with projects in the past and everything works fine.  The only problem is you end up with enormous parent classes that have tons of methods that don't do anything.  It also "feels wrong" because I don't like the idea of having a ton of placeholder code.

 

B:  You do something like this:

post-54134-0-39656200-1446069339.png

 

In this case you are guaranteeing the compiler that at run time there will be a specific class on that wire and that it can take it to the bank.  If it turns out that the class on the wire is the wrong type, we give it an out with a error handling case.  I'm a little uncomfortable with this type of code because I haven't done a lot of it, but I don't know how well it scales.  My question to you all is, "Am I correct in my understanding or is there a better way to deal with this case?"   Thanks, and I'll attach the proj to this thread.

post-54134-0-16008600-1446068421.png

Animal House Example.zip

Link to post
Share on other sites

In this stance your Cat and Dog hierarchies are actually different functionally - one Meows and the other barks. Your find Animal and logic after that presumes to work on any Animal - but Animal doesn't have a bark or meow. This is an LSP violation for the caller that expects an animal to do something specific.

 

Your use case of the API (your example) is that the animal needs to make a sound - bark or meow is irrelevant. In this instance I think it is cleaner to:

 

- Have a Make Sound method in Animal that Cat and Dog base classes over-ride to Meow or Bark.

- if it is important to be able to make any Cat meow specifically then you are better off adding a "Make Meow" and "Make bark" to your Cat and dog base classes that is only available to them and their children. This way your clarify to your callers that any animal can make a sound but only cats meow and only dogs bark.

 

The best approach here is probably to favor composition and move sound making into it's own hierarchy of classes that are composed into your animals but that's a whole different story.

Edited by ak_nz
  • Like 1
Link to post
Share on other sites

Thanks for the commend, I have seen composition used before and I should have included it.

 

So is that (Composition) the standard way of dealing with objects that have some things in common but something that are not?  I'm just really curious because this seems to come up a lot in the couple years we've been using LabVIEW OOP, especially when you are developing HALs.  For example lets say you have a power supply class.  Most power supplies have a init, close, set voltage, set current, etc but occasionally you'll come across one that has additional (useful) functionality that the others don't have.  If I want to make use of that additional functionality, do I start a whole new class hierarchy?  If I do that then I've broken my power supply HAL.  It seems like this should be solvable with OOP since its designed to EXTEND functionality of a class. 

 

BTW, bark and meow were bad examples.  I should have used something like cats can claw and dogs can track.  Two totally unrelated functions that you can't easily attribute to just any animal.

Edited by mgould2001
Link to post
Share on other sites

There is normally an idea of "interface" or "trait" that allows a class to say to callers that it implements certain behaviour. In standard out of the box LabVIEW, these ideas are only possible via composition rather than by inheritance. The GDS toolkit will allow you to create interfaces in a way but natively LabVIEW only supports single inheritance and has no notion of abstract classes or interfaces that exist only to enforce a contract for behaviour.

 

Don't forget that OOP hierarchies are not about "things" they are about common behaviour in the context of your callers. if you find yourself over-riding methods to make them no-ops then this generally indicates that the child class doesn't really respect the contract and invariants of the base class and there is a potential issue to resolve. This can be difficult to fully achieve in LabVIEW so you often have to make compromises and document use cases for the next developer who follows you.

 

Generally the best rule of thumb is to keep your inheritance hierarchies as small as you can to avoid changes in base classes rippling through your hierarchy. Composition can help reduce dependency coupling but, again - this can be hard to achieve easily in LabVIEW.

 

In your example of a power supply with an additional method - this is a functionality that only pertains to a certain unit and only makes sense for that unit. In other OOP languages the natural thing would be to move the behaviour into another hierarchy and inject it in but in LabVIEW this is labor-intensive. My gut feel in this case would be to move the method down to the base HAL and implement the method in each child class - with the exception of the class that actually understand the request, all others can throw a run-time error ("Request not appropriate for the LAVA Power Supply type"). It's not ideal since you can't statically verify your application code but it is a reasonable compromise.  It does also force you to deploy your entire HAL again but that's another story.

Link to post
Share on other sites

Another option is to push greater responsibility down into the specific classes and have the application-specific code continue to deal in parent-class methods.  For example, your power supply with a special feature probably needs a way for the User to configure that feature and observe the results.  The application code could call methods like “Has special features.viâ€, “Show Configuration window.viâ€, “Get text description of status.viâ€.   If the power supply has special features then show a description of the status of those features and show a button that lets the User open the Configuration window (possibly in a subpanel).   

  • Like 2
Link to post
Share on other sites

I have to reluctantly disagree with drjdpowell,

 

You can create a sub-VI.  Connectors are

1) An Array of parent objects INPUT

2) a single parent object INPUT

3) a single parent OUTPUT

 

In this VI, you can check all of the objects int he input array until you find one which fits (choose your own method, either preserve runt ime class or your own method depoending on whether you want to allow EXACT matches or whether you also want to allow ancestors to be returned).  Output this one from your loop and before writing it to the output terminal, run it again through a "preserve run-time class" with the Input terminal as the upper middle input.

 

Now, when using this VI in the IDE, if you wire a Felidae to the input, it will output the first class which is compatible with this type (felidae or ancestor) but the output will be THE SAME TYPE as the INPUT to the VI (Strictly typed).  And of course when I say "the same type" I meant he same WIRE type since obviousloy the exact object type can only be determined at run-time.  I was amazed to come across this functionality years ago but have used it on many occasions since.  It's like outputting a parent object and then running "preserve run-time class" on it, but the IDE is clever enough to realise that the sub-VI is already doing this, thus saving duplicate code.

 

LVOOP autoadapt

 

This kind of auto-adapting of object type is relatively unknown in LabVIEW but it's a really cool thing, the subVI thusly produced almost acts like an XNode.

Edited by shoneill
Link to post
Share on other sites

Are we actually disagreeing?  Your pointing out a way for calling code, that explicitly knows what a "dog" is, to call subVIs that only deal in "animals".   If the calling code has reason to explicitly be written for "dogs", then that's good.   But if you want the calling code to be generic and work with any animal (what's that? an "animal-abstraction layer") you can still support special functions if you define the right methods.  Writing code where, if you click on a dog, you get a button marked "bark", doesn't require that code to explicitly work with anything other than animals.

Link to post
Share on other sites

Yes, from what you have written, I believe we are disagreeing.  The OP had a problem with a piece of code from a post which explicitly describes what I'm proposing.

 

There seems to be a simple misunderstanding here since the broken wire the OP has in his code as actually explained and accounted for in the link he himself provides in the first post.  While the idea of encapsulated configuration panels is a great one, it shoots WAY beyond the scope of the original question as I read it (YMMV).

 

Explicitly, the following was asked with regard to the OPs broken wire:

"My question to you all is, "Am I correct in my understanding or is there a better way to deal with this case?" "

 

My post answers this.  And yes, it is a way for a piece of code to EXPLICITLY interact with a specific type of object from a mixed object array.  Because that was the question posed. :unsure:

Edited by shoneill
Link to post
Share on other sites

But option B works fine if you make the changes I mention in my post.  There's no need for C at all.  It answers a completely different question (A good answer I might add, I've no problem with the correctness or quality of the proposed solution, just that it goes way beyond the problem posted.)

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.