Jump to content
Steen Schmidt

"No child-specific info in Parent class" vs. "The want for dynamic dispatching"

Recommended Posts

Hi,

 

Here comes a rather basic question but which I'm always struggling with since you can often argue both ways. Hopefully some of you have a few nuggets to share here... 

 

1) A parent class should have no knowledge of any child class' concrete type, i.e. only data and methods that is inherently common to all decendents of that parent should be defined by it.

2) For dynamic dispatching to work the parent class must define virtual methods for dynamic dispatchees to work from.

 

Those two rules often contradict each other. Methods vetted against 1) will often eliminate the possibility of 2). A specific example:

 

Currently at GPower we're working on a set of classes that enables us to do a lot of stuff on all the different LabVIEW files. So, we have a parent file.lvclass and then a collection of specific children that inherits from "file", for instance vi.lvclass, lvproj.lvclass, ctl.lvclass, facadevi.lvclass etc.

 

A) Generic stuff that has to do with any file obviously goes into file.lvclass data (together with data member access methods for each). For instance IsWriteprotected?, FileSize, FilePath and such. A FileRefnum should probably go into each child class to enable us to have it most specific.

 

B) Then there are data elements that definetely has to do with each concrete file type and thus should be in each child class' private data, such as AllowsDebugging?, IsFPStyleOK?, Icon etc. - as those are only valid for a subset of file types.

 

C) Now it gets a bit more murky; If we define data elements in file that each child can use to flag if it has this or that feature (like HasIcon? or SupportsProtection?), then you can ask each and every file type if it support that, before calling a method to work on that. For the subset of file types that supports a given feature (those that has an icon for instance) it'd be great to have dynamic dispatching though. But for that to work file.lvclass would have to implement virtual methods like GetIcon() and SetIcon() (where the latter maybe recalculates the IsIconStyleOK? data field for instance). But implementing these (numerous it turns out) virtual methods in the parent class, suddenly riddles the parent class with specific knowledge or nomenclature that is very specific to different subsets of descendent file types.

 

I always vet the parent against each piece of information I'd like to put in there by asking myself something like this: Does my generic file know anything about disk write protection? Yes, all files deal with that, so it can safely go into file.lvclass. Does my generic file know anything about block diagrams? No, only a handful LabVIEW file types knows about block diagrams, so I can't put it into file.lvclass. But the latter forces me to case out the different file types in my code, so I can call the different specific methods to work on block diagrams for those file type objects that supports this. Hence, if I want to be able to just wire any file type object into a "block diagram worker" method I need dynamic dispatching, and that only works if my parent has a virtual method for me to override with meaningful code in a few child classes...

 

How do you go about selecting this balance, on one hand abstracting the parent from the concrete types, and on the other hand implementing the necessary dynamic dispatch virtual methods in the parent?

 

Regards,

Steen



I must add that I'm currently leaning towards the more pure approach of not mixing abstract and concrete. Thus I tend to omit dynamic dispatching except for the very generic cases. I only allow the "do I support this feature" flags into the parent data, and then I implement static dispatch methods for each concrete type - even though these static dispatch methods then will often be quite similar, but that is solved with traditional LabVIEW modularization and code reuse (like subVIs, typedefs and such).

 

It works well, but I feel I don't get the full benefit of dynamic dispatching. But that is what I'd do in C++...

 

/Steen

Share this post


Link to post
Share on other sites

Do you use multiple levels of abstract classes?  File.lvclass<— LVfile.lvclass<— VI.lvclass, for example?  A HasBlockDiagram method would be defined in the LVfile class (default result FALSE) and would be overridden in VI.lvclass to return TRUE (but not overridden in LVLIB.lvclass).  

Share this post


Link to post
Share on other sites
Do you use multiple levels of abstract classes?  File.lvclass<— LVfile.lvclass<— VI.lvclass, for example?  A HasBlockDiagram method would be defined in the LVfile class (default result FALSE) and would be overridden in VI.lvclass to return TRUE (but not overridden in LVLIB.lvclass).  

 

Currently only a single level of abstraction, where File.lvclass is understood to be "LabVIEW files", not xls, txt, ini etc.

 

Had LabVIEW supported multiple inheritance I could have made several super classes each defining their own interface, for instance;

 

File.lvclass (responsible for all generic file traits like disk write protection, file path etc.)

LVFile.lvclass (inherits from File, and adds generic file attributes that are common to LV files only, like an owning LV instance if the file is in memory and that sort)

LVBD.lvclass (hosts all that has to do with block diagram)

LVFP.lvclass (hosts all that has to do with front panel)

LVIcon.lvclass

LVProtection.lvclass

LVExecution.lvclass

...

 

Then VI.lvclass would inherit from all of LVFile, LVBD, LVFP, LVIcon, LVProtection, and LVExecution. PolyVI.lvclass would inherit from the same, except for LVExecution, while LVProj.lvclass would only inherit from LVFile.

 

But we only have singular inheritance, so the above (most correct) encapsulation is not possible in LabVIEW. Within this single string of inheritance I must choose where to put the IsFPStyleOK? data field for instance. If I put it in LVFile then LVProj is also forced to lug around IsFPStyleOK? even though it has no idea of a front panel. If I on the other hand move IsFPStyleOK? down into VI.lvclass and VIT.lvclass (and the others with a front panel) then I can't use dynamic dispatch for that (since VI and VIT has no common parent below LVFile).

 

I could inject an abstraction layer between LVFile and VI/VIT/FacadeVI... for instance - that could be LVFP.lvclass which would own an IsFPStyleOK? data field. Then the inheritance tree for VI would look like this: File <- LVFile <- LVFP <- VI. But again without multiple inheritance I can never flesh out single abstraction paths without overlap to each LabVIEW file type.

 

Thus my question; should I start pushing class data members and dynamic dispatch methods upwards to the first common parent (and don't mind breaking abstraction), or should I stick with strict abstraction and then live without that much dynamic dispatch and having to duplicate data fields across several same-level classes? I love clean abstraction, I think it's one of the really great things about OOP. Murky abstraction - mixing in stuff that doesn't really belong in that class - is a huge code smell, a stinker really.

 

I wish for multiple inheritance in LabVIEW. I'm glad we're allowed to use components from more than a single LVLIB inside a VI ;).

 

/Steen

Share this post


Link to post
Share on other sites

I have started compositing my classes.  You could include a "Protection" class in your different file formats which offloads the protection functionality into a seperate class.  That was inheritance is no longer an issue.  You then have a file class with a bunch of sub-classes which you can access one by one and query whether they're implemented or not.

  • Like 1

Share this post


Link to post
Share on other sites

(Thanks, Steen, for the heads up to this thread :-] )

 

Here's a quote from another conversation that relates to this thread:

 

JackDunaway wrote:

I've long considered myself insufficiently-talented to design object models [in LabVIEW] that "feel right". Bouncing methods and fields to-and-fro between classes -- waffling over inheritance vs composition to correctly model application domains -- i was displeased with myself and my work at the end of each day for still not getting it right (have you ever felt this way?)....

 

Until a relatively recent realization -- many, many of my application domains simply cannot be modeled with the currently-insufficiently-expressive LVOOP language features. Class Mixins seem to resolve much of my heartache (though, i could probably be convinced of Traits or Interfaces)... (I suggest this makes LVOOP and other LabVIEW OOP APIs easier to comprehend for new users and be developed by advanced users, simply because the implementation mentally maps to the physical domain. The main motivation here is re-use; pain points include unsustainable proliferation of proxy methods)

 

tl;dr -- you've got an application domain where neither inheritance nor composition is the correct re-use pattern.

 

Using LVOOP today, I would tend to take drjdpowell's advice of inheritance as far as you can without breaking the map between the object model and your domain -- this might get you a little ways down the road of pushing common methods/fields up the hierarchy.

 

Then, take shoneill's advice of composition. You'll have some grunt-work writing proxy wrappers to the methods you have composed in each of the subclasses (consequentially, introducing overhead and points of brittleness between the interfaces of wrapper and wrappee).

 

Finally, take a tissue to those (manly) tears you've shed over your lack of Traits or Mixins or similarly-sufficient LVOOP facilities to express your application domain. Have a bourbon. Sleep peacefully with your system "that'll do just fine". Dream wistfully of What Can Be.

 

(By the way, you mention Multiple Inheritance, I mention Mixins and Traits -- naturally we must toss Interfaces into the mix -- no need for us to get caught up in semantics and differences between these, further than to agree collectively we gotta have something. Once it's on the docket with a budget for the next LabVIEW release, let's dive into the nitty-gritty of which makes the most sense for our language)

Share this post


Link to post
Share on other sites
JackDunaway wrote:

I've long considered myself insufficiently-talented to design object models [in LabVIEW] that "feel right". Bouncing methods and fields to-and-fro between classes

 

I actually came here to look for answers (and possibly post a question) related to this very thing. I found myself moving methods around to try and get them into a common place which made sense (to no avail). Ironic that this is the first thread in the OO section right now. Just putting in my $0.02 that you're not the only one feeling this way.

Share this post


Link to post
Share on other sites
I actually came here to look for answers (and possibly post a question) related to this very thing. I found myself moving methods around to try and get them into a common place which made sense (to no avail). Ironic that this is the first thread in the OO section right now. Just putting in my $0.02 that you're not the only one feeling this way.

 

Yeah, the important thing to realize here is that you're not incompetent; your LVOOP toolchain just makes you feel incompetent.

 

Without a background in Python or Java or C++ or Ruby or PHP or Scala or ... -- with only a background with LVOOP -- you're not necessarily going to realize the root of the problem modeling systems.

 

This is me; I learned OO on the LVOOP platform. Since, I'm studying elsewhere to diagnose my lack of productivity and lack of confidence in my designs using LVOOP.

 

This is not a slam against current LVOOP; I'm all about continuous improvement and kaizen and working toward better toolchains and systems; it's important we continue funneling positive energy toward improving LVOOP. Other wildly popular languages are still continuing to develop and release new OO (and generally, core language) features today and over teh past years, and it's important to encourage LabVIEW to do the same!

Share this post


Link to post
Share on other sites

Introduce OOP to a LV programmer and you get much happiness.

Introduce LV to an OOP programmer and you get much frustration.

Share this post


Link to post
Share on other sites


I agree with shoneill that thinking in terms of composition is helpful.  In particular, the strategy pattern might be quite useful in this case, although I'm having a hard time understanding the application's intended use of these calls.  (One can use the strategy pattern to eliminate the unwanted case structures, certainly.  I'm not sure why the application is invoking these methods--in particular the queries--on file in the first place, though.  It may make perfect sense, but I would investigate that first if I were reviewing the design.)  For the record, I would welcome the introduction of Java-style interfaces into LabVIEW, but I think multiple inheritance would cause more problems than it would solve. My experience to date is that when I think "multiple inheritance would be helpful here," I end up solving the problem by a redesign that is in the end much more robust and easier to understand than a multiple inheritance solution could ever be.

Share this post


Link to post
Share on other sites

Composition would be a way for us to maintain proper abstraction while living without multiple inheritance - thanks for reminding me of the debate composition vs inheritance ;-)

 

PaulL, if I understand you correctly you're asking me about what I would use this particular File class for? Let me explain in short - here's a fraction of the class diagram that we've been discussing here (illustrating the composition solution):

 

post-15239-0-16073800-1383307468_thumb.p

 

Our use case is an application ("Batch Editor") that will allow us to do a number of things on a collection of files;

 

- You can add any number of LabVIEW files to your current workset in the Batch Editor.

- You can then select one or more files within this workset to perform operations on.

- Examples of operations:

-- Change the write protection of all the files in your current selection within the workset.

-- Setting or clearing "Allow Debugging" in bulk.

-- Sorting your view to see which files in your workset has reentrancy enabled.

-- Calculate if all your files follow a predefined style (BD, FP, Icon, VI Description etc.), and to some extent correct bad style in bulk (FP for instance). Style would be a loadable XML file (probably), and define such things as label position, caption contents, control allignements etc. etc. Define your own style for your company, for your current project etc.

 

OO will be a good strategy for modularizing many parts of this Batch Editor app. Makes sense?

 

/Steen

Share this post


Link to post
Share on other sites

Steen, thank you for the model, this helps as a talking point.

 

Even though composition may solve your problem, one caveat is introducing a non-trivial amount of work in the form of creating proxy methods.

 

For example, your VIFile composes an LVIcon, which has the method LVIcon::SetIcon(). Your application that uses the VIFile API probably needs access to the method SetIcon(), meaning you'll need to create a VIFile proxy that wraps SetIcon().

 

Likewise, that proxy will need to be written in PolyVIFile, LVLibraryFile, LVControlFile...

 

Now, we've created several maintenance points. What if the LVIcon::SetIcon() conpane (interface) changes? We'll have to go update all the existing conpanes (interfaces) of the proxy wrappers.

 

The alternative to proxy wrappers is to create a getters/setters for the composed objects -- though this is suboptimal, and often breaks atomicity. You're breaking encapsulation, and potentially exposing the application calling the API to race conditions or even deadlock.

 

Now, assume LVOOP gave us Mixins or Traits.

 

The method SetIcon() is implemented by the LVIcon Trait (rather than the LVIcon Class), and then VIFile, PolyVIFile and so forth would declare the Trait LVIcon. The method SetIcon() is written only once, and can accept object types of VIFile, PolyVIFile, and so forth, in order to manipulate the parent objects which have declared those traits.

 

One regard where Traits or Mixins differ from Interfaces is that Traits and Mixins define implementations, rather than just interface and contract definitions.

 

The main motivations pushing for Traits or Mixins in LVOOP are all tied to developer productivity and mental well-being. Conceptually, it can make systems easier to synthesize by providing more accurate maps to mental models, and the logistics of maintaining codebases is substantially reduced (since we agree, codebases are a liability, not an asset).

Share this post


Link to post
Share on other sites

Yes, we realized the pain about proxy methods about 2 minutes after we sat down and did a couple of different implementations of composition. That pain stems mainly from the fact that we are forced to create explicit accessor methods for classes. There's no way in LVOOP to specify public or friend scope for class data.

 

I'm contemplating some ideas for class data access á la unbundle and bundle directly on the object wire instead of the current explicit accessor. This is just off the top of my head, and haven't been thought through at all yet, so it may not be desirable at all when I give it some more thought :rolleyes:.

 

I'll get back to you on the traits and mixins topic later, right now I'll go cook up some dinner :).

 

Cheers,

Steen

Share this post


Link to post
Share on other sites

(Replying to post #10):

Thanks for including the diagram.  That helps, and admittedly it looks much like I thought it would (which is a good thing).

 

I would like to pursue my earlier question regarding how the application uses the queries.

 

In particular, for actions (or even queries, if these are needed) one can implement a null strategy that, in the event that something doesn't apply, does nothing and returns a suitable value.

 

If the intent is to sort the items in the view so that the application only lists the relevant items on which one can perform an action, I think reflection could be useful.

 

I'm getting ahead of things, though.  Maybe you can explain how the application uses queries?

Share this post


Link to post
Share on other sites

I'm drawn into this discussion as I see two interesting parts to this problem:

(1) What design pattern best suits the application?

(2) How can the Batch Editor know which operations are available for a given file set of mixed types?

 

(2) could easily be accomplished if reflection were available - simply query for the object hierarchy and determine the common set of available methods.  Alternatively, reflection-ish behavior can be "faked" by maintaining a look-up table with this information. 

 

But, let's revisit (2) after answering (1).  It seems to me that your problem is textbook case for Visitor Pattern.  Read up on Gang of Four Visitor Pattern (here or here are satisfactory renditions).  Don't get hung up on NI community example here (though it serves as an interesting specific use-case, I think it fails to capture the basic intent).

Then try something like this:

post-4721-0-55543200-1385143332_thumb.pn

 

I also attached some java-ish pseudo code to demonstrate the call chain from main, through the composite FileGroup, to the Visitable Objects (your 'File' class structure), to the Visitors (the file operations).

 

Using this pattern, you may feel like you're duplicating a lot of information from the Visitable classes to the Visitor classes in order to make it available to the client, but on the upside you're avoiding creating a bunch of proxy methods every time you want to extend the available operations set. 

 

Back to (2).  For the Batch Editor to "know" the available operations, you can add a 'checkAccept' method alongside every 'accept' method, and a 'checkVisit' method alongside every 'visit' method.  The 'checkVisit' methods in the concrete Visitor classes simply return 'TRUE' if they are successfully called.  'checkAccept' methods return the 'checkVisit' result to the client.  In this manner, then client can call 'checkAccept' on every File in a FileGroup for every operation (Visitor), to quickly determine (without modifying anything) which operations are available for a selected file set.

 

One final note, if you do decide to use this approach, it might make sense to enforce "friend" scope to methods in your File structure classes to only allow calls from their corresponding Visitors.  This makes it clear that the only public API to your File structure should be the accept (and checkAccept) methods.  Though this strengthens the API, it does require extra maintenance as new operations are added.

 

Maybe there's a better pattern for this application, but this is my gut reaction.  Please shoot holes in it as you discover its deficiencies. 

 

visitorPatternPseudoCode_FileBatchEditor.txt

Share this post


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.