Jump to content

Feeding composite objects back into parent with a DVR?


Recommended Posts

Hi,

 

I would like to play an idea out to you about having a composite class put itself back in its owning object by a DVR. Is that a good or a terrible idea?

 

Background

I need a class that represents some different LabVIEW files, and which can offer some operations on those files; LVFile.lvclass. LVFile.lvclass has some children (LV file types) and it owns a composite object LVFileRefs.lvclass:

 

post-15239-0-38537900-1438083662.png

 

LVFileRefs

LVFileRefs contains the following:

 

- An enum stating the file type.

- A reference to the LabVIEW file (class reference, library reference, project reference, or VI reference).

- The path to the file on disk (if it is saved to disk).

- Info if the refnum was created by the object or passed in from the outside (used by the Close method).

 

I need LVFileRefs as a composite object (instead of that data living inside LVFile) since New starts by instantiating an LVFileRefs object before New can decide which child to instantiate:

 

post-15239-0-60464600-1438084231.png

 

One of the responsibilities of LVFileRefs is to keep disk path and file reference synchronized. This means that you could start out with a VI in memory for instance, and create an LVFile object for that. This does not yet have a File Path. If you then save that VI, then LVFileRefs will update its internal File Path field from <Not-A-Path> to the proper path. If for whichever reason the original file refnum goes stale, LVFileRefs will also be able to open a new refnum from File Path if possible. All transparent to the user. So LVFileRefs maintains a firm grip of the file whether it being in memory only or on disk, and it can tell you which type it is (some types take more programming to tell apart than others, and type can change as well). All stuff that makes writing apps that handle many different file types simpler.

 

No DVR approach

Normally I wouldn't expose a naked refnum to the user, but one of the features of LVFile is actually to serve the proper refnum, so in this case I do. That is currently done with accessors on LVFile, and then an LVFileRef method:

 

post-15239-0-49812600-1438087042.png

 

Since LVFileRefs can update its embedded refnum it is important to write the LVFileRefs object back into the owning LVFile object (or else Close for instance wouldn't close the correct refnum, and several other issues).

 

DVR approach

Having to write back the LVFileRefs object is error prone - the user could forget to do it, and having to do it in the first place is irritating. I'd rather you could omit the write back step:

 

post-15239-0-90975300-1438088011.png

 

The idea is to have LVFile embed a DVR to itself inside its LVFileRefs object, with which LVFileRefs can write itself back into its owning LVFile object with whenever LVFileRefs has updated itself. There shouldn't be any racing possible, as LVFileRefs is actually the representation of the LV file, and DVR access is mutexed.

 

Am I insane considering this?

 

Cheers,

Steen

Edited by Steen Schmidt
Link to comment

Bugger, I can't put a DVR to the owning class (LVFile) inside LVFileRefs' data control, since LVFile obviously has LVFileRefs in its data control  :angry:.

 

Let me think of a workaround here - first I'll have to get my head around why this restriction is in place. Is it perchance a recurrence thing?

 

/Steen

Edited by Steen Schmidt
Link to comment

Hmm, I think the best solution is not to expose the LVFileRefs class at all, but instead create LVFile methods for LVFileRefs access. I've actually had that implemented earlier, but found it superfluous. Perhaps it is, but it eliminates having the same item referenced in two public objects.

 

Usually for composite classes I prefer to have a "GetInnerObject" method on the owning class, and use the Inner class' methods on itself*. In this exact case I think I'll have to make an exception.

 

*One gripe I have with this is that I can't easily protect methods of the inner class by setting an appropriate scope, unless I make the inner class a friend of its owner. And I don't like the friend paradigm too much, as I find that it couples the owner and the inner class strongly.

 

/Steen

Link to comment

Making the functionality in your non-DVR approach a method of the File class is probably the best way to go.  Whenever I have the thought "But the user needs to do X" then I know it needs an object method to do that because most users are stupid (myself included, in fact, especially myself)

  • Like 1
Link to comment

I need LVFileRefs as a composite object (instead of that data living inside LVFile) since New starts by instantiating an LVFileRefs object before New can decide which child to instantiate

 

It looks like you need a Factory pattern. As soon as you know the type, create the proper child, it seems to me that you don't need to store the type as an enum anywhere.

Link to comment

It looks like you need a Factory pattern. As soon as you know the type, create the proper child, it seems to me that you don't need to store the type as an enum anywhere.

 

I do implement a factory pattern already; "New" determines type and with an object of LVFileRefs instantiates the proper child of LVFile. You do not mean the "factory method pattern" as outlined by GoF? I don't want to use inheritance here, I tried that initially, and found it too inflexible. I actually tend toward composition more than inheritance when I have the choice. I displayed the factory in my first post. Note there are four of these factories, one for each supported base refnum type (Class, Library, Project, and VI):

 

post-15239-0-14280500-1438109416.png

 

As I note above I think you want "New" to output the proper child object directly, right? I could do that, but then LVFileRefs would depend on children of LVFile, and thus couldn't be part of LVFile's data, and LVFileRefs wouldn't be reusable in any other context without hauling around LVFile.lvclass and all its descendants either. And I want LVFileRefs to be part of LVFile's data, as LVFileRefs' data fields are the cornerstone of each LVFile object.

 

I've shuffled the functionality around many times over the last 3 weeks, and the current code division is what gives me the best flexibility when I build an application with these classes. I could probably do without the enum as type, but it hurts very little (it's just a short typedef'ed list of strings) and relying solely on parsing which child is on the wire (whether it being by DD or by code) is cumbersome in several situations. Mainly those situations where the programmer needs to tell a human being what is going on. Instead of trying to convey some meaning out of a class name I'd much rather pop a name from the types list to put into an error string for instance.

 

OH! (EDIT): I just remembered the primary reason there is a type field; The type can change at runtime! There is only a Control.lvclass for instance, but that can have a type string of either "Control" or "Control template". If a control file at runtime changes into a control template (it gets saved as .ctt) I wouldn't at runtime be able to change the child on the wire. But I can change the type string.

 

I won't rule out that the Type enum could be factored out downstream, but for now it's convenient. I often find myself battling between "Purest" and "Convenient". And I tend to spend only 10% of the time to reach "Convenient" than I would have spent to reach "Purest". And my "Convenient" version is probably 70-80% pure when come release day, so an acceptable tradeoff.

 

I'm learning every day though, so please keep writing  :).

 

/Steen

Edited by Steen Schmidt
Link to comment

Just an idea: do you really need the LVFileRefs object? I feel like in your situation I would have put all the fields (path, refnum,...) in the LVFile object directly. Then each child object can have both the fields of the child class, and the fields of the parent class (LVFile). Obvisously you have spent much more time thinking of this specific situation than I did so my comments might miss the target, I guess I'm trying to brainstorm some ideas here  :book: 

Link to comment

Just an idea: do you really need the LVFileRefs object? I feel like in your situation I would have put all the fields (path, refnum,...) in the LVFile object directly. Then each child object can have both the fields of the child class, and the fields of the parent class (LVFile). Obvisously you have spent much more time thinking of this specific situation than I did so my comments might miss the target, I guess I'm trying to brainstorm some ideas here  :book: 

 

I've been there, and it went like this  :):

 

1) I actually started out with the LVFileRefs fields in the LVFile class.

 

2) I then wanted to make a subVI of the code that populated these fields to put at the head of the factory, as it's the same for every child. Let's call that subVI "NewRefs".

 

3) "NewRefs" had to have an object to operate on, and that object can't be the parent LVFile (if I've first instantiated a parent I can't cast that to a specific child later without copying - that wouldn't be the proper solution in this case).

 

4) "NewRefs" couldn't be a method of LVFile, as "NewRefs" essentially found out for me which child to instantiate. It would be foolish to separate the code that found out the type, from the code that created the object to contain that type info. It could be done of course, but I would basically have a subVI outside the LVFile class hierarchy which found out my child type, then passed out all its info (on 6 wires or in a cluster) into a write-accessor to identical fields within LVFile when the factory created the child.

 

5) Much better to encapsulate the code that manages type within a class of its own, namely LVFileRefs. LVFileRefs has a simple and understandable role:

 

- It can eat one of four refnum types and find out which specific type it is (that dredded enum).

- It holds that type and refnum, and the associated file path.

- It offers methods to keep that refnum alive and to update the path if the file is saved or moved, it knows the rules of which type changes are allowed (ctl to ctt for instance, but not lvclass to lvproj), and can output sensible error messages when needed.

 

6) By encapsulating this into LVFileRefs I could add that class by composition to LVFile, which in turn let me use LVFileRefs to initiate the factory. As a bonus I can use LVFileRefs for any other purpose where I just need a LabVIEW file reference managed, without logging around the LVFile hierarchy.

 

Update: Even simpler answer to your question: I have actually put those fields into LVFile.lvclass. They are just encapsulated in their own class to facilitate reuse and instantiation pre to the LVFile object itself. Bonus is encapsulation of associated methods, scoping of subroutines etc. Those are the benefits of composition in my mind.

 

/Steen

Edited by Steen Schmidt
Link to comment

How about using the "Chain of Responsibility" for part of your factory?

 

Have all of the child classes accept a refnum and "ask" them if they can instantiate on that refnum.  That way you can make your way through the list of known children until you find one willing to instantiate on the wire.  This way you have kept the instantiation part of the class but still have the ability to retain the refnum itself within the class in question.

 

You can still use composition with your LVFileRefs object held within the individual classes, but you need not expose the entire object to the user (especially requiring the user to re-wire the object back in!).

 

So the part of your post where you say ""NewRefs" had to have an object to operate on", you have ALL child objects to operate on, but the first one which says "Yup, I can work with that refnum" gets passed on.  If none want to take responsibility, the factory method fails.  If several want to take responsibility, the first to report willingness wins.

Link to comment

How about using the "Chain of Responsibility" for part of your factory?

 

Have all of the child classes accept a refnum and "ask" them if they can instantiate on that refnum.  That way you can make your way through the list of known children until you find one willing to instantiate on the wire.  This way you have kept the instantiation part of the class but still have the ability to retain the refnum itself within the class in question.

 

[...]

 

So the part of your post where you say ""NewRefs" had to have an object to operate on", you have ALL child objects to operate on, but the first one which says "Yup, I can work with that refnum" gets passed on.  If none want to take responsibility, the factory method fails.  If several want to take responsibility, the first to report willingness wins.

 

Since there is no constructor for LV classes an object can only "instantiate itself" by proxy, meaning an external VI that runs through the options filtering on errors from each child's "New" method or what it should be called. I actually do 95% that. Here is the frame from the NewLVFileByPath VI that attempts to create a VI child:

 

post-15239-0-57543200-1438158570.png

 

If more of the responsibility should be left for the children, or really if less of the responsibility should be outside the child as nothing more goes into it, it would be by skipping the pre-filtering:

 

post-15239-0-59659700-1438159313.png

 

But blindly rifling through each child's New methods in a while loop would be much more cumbersome to implement than what I have above, so this is one of those cases where convenient beats purest by far in my mind.

 

You can still use composition with your LVFileRefs object held within the individual classes, but you need not expose the entire object to the user (especially requiring the user to re-wire the object back in!).

 

That would mean that each child would have LVFileRefs in its data. That by itself would suggest to me that LVFileRefs should be in a more generic class (as it is now). Exposing the LVFileRefs member isn't a consequence of where in the class hierarchy that field exists, but instead a consequence of that data changing and should be written back into the object in question, whether that object being a child or the parent. It would be the same if the data member was a Boolean or a numeric.

 

But I've stopped exposing LVFileRefs and have made LVFile accessors for it. That works rather well when using the API:

 

post-15239-0-39431100-1438157626.png

 

Having the child contain its own refnum would be better from an encapsulation standpoint, but unfortunately DD requires the same data types on all instances' conpanes (so a "GetRefnum" method couldn't be DD and return a VI refnum for a VI and a Class refnum for a class). Without that ability (nor any good way of handling a more generic refnum on the user's block diagram) it remains simpler to have those refnums in the parent object.

 

/Steen

And just to clarify here is the "New" method of VI.lvclass where it returns an error if it's asked to instantiate a wrong object type:

 

Good object type:

post-15239-0-13465500-1438159970.png

 

Wrong object type (where it's also obvious where the "Type enum" comes in handy):

post-15239-0-45253200-1438160115.png

Edited by Steen Schmidt
Link to comment

Bugger, I can't put a DVR to the owning class (LVFile) inside LVFileRefs' data control, since LVFile obviously has LVFileRefs in its data control  :angry:.

 

Let me think of a workaround here - first I'll have to get my head around why this restriction is in place. Is it perchance a recurrence thing?

 

I haven't read the rest of the thread closely, but in case this can help you - you can do this, but the data type needs to be that of a parent class (like LV Object). You can then use the accessor which reads the DVR to cast it down to LVFile.

Link to comment

I haven't read the rest of the thread closely, but in case this can help you - you can do this, but the data type needs to be that of a parent class (like LV Object). You can then use the accessor which reads the DVR to cast it down to LVFile.

 

Nope, a class can't have itself in it's data control.

 

Since LVFile contains an object of LVFileRefs, LVFileRefs can't contain a DVR of LVFile.

 

/Steen

Link to comment

Again, the type should be of a parent class. You only cast the object down to the correct class at run-time. This does work. I don't remember trying specifically with a DVR, but I believe that should work too.

 

Which type should be a parent of what? I have this relationship, and none of those two clases currently have a parent:

 

post-15239-0-15218200-1438174358.png

Link to comment

Which type should be a parent of what? I have this relationship, and none of those two clases currently have a parent:

 

attachicon.gifLVFile UML.png

 

LVFileRefs should have a DVR to a LabVIEW Object (not LVFile). At run-time, you populate this with a DVR of a LVFile. You create an accessor which reads the DVR and casts it down to an LVFile DVR for using it (you might need to do the casting inside the IPE structure).

Link to comment

LVFileRefs should have a DVR to a LabVIEW Object (not LVFile). At run-time, you populate this with a DVR of a LVFile. You create an accessor which reads the DVR and casts it down to an LVFile DVR for using it (you might need to do the casting inside the IPE structure).

 

Ah, now I understand, thanks  :). I assumed I would incur a runtime error on such a cast.

 

This brings me back to me wondering why the direct inclusion of a circular reference isn't allowed, and in turn why the runtime cast to the self-referencing object does not result in an allocation failure - one of the two ought to be the case. I must think about this some more. The answer is probably quite simple...

 

Thanks Yair!

 

/Steen

Link to comment

This brings me back to me wondering why the direct inclusion of a circular reference isn't allowed, and in turn why the runtime cast to the self-referencing object does not result in an allocation failure - one of the two ought to be the case. I must think about this some more. The answer is probably quite simple...

 

It is, and it's two sides of the same coin - the static link is simple to understand - LVF has an LVFR, which has an LVF, and so on. There is an issue with resolving the type, because you have to keep adding more layers infinitely. The dynamic version works because this doesn't actually need to happen - when you stuff the LVF DVR into LVFR, the actual object inside the DVR has its own LVFR which doesn't point to any other object (its data type is LVO and the DVR is empty), so the recursion stops at that point.

Link to comment

It is, and it's two sides of the same coin - the static link is simple to understand - LVF has an LVFR, which has an LVF, and so on. There is an issue with resolving the type, because you have to keep adding more layers infinitely. The dynamic version works because this doesn't actually need to happen - when you stuff the LVF DVR into LVFR, the actual object inside the DVR has its own LVFR which doesn't point to any other object (its data type is LVO and the DVR is empty), so the recursion stops at that point.

 

Yes, it's simple to understand the difference between the recursive object and the one that is not. What I mean is:

 

1) The DVR is a pointer, that pointer does not have to be resolved to any object before it is referred at runtime. Problem shouldn't exist at all at edit time. Type information is only an editor-aid, exactly like in TestStand where every object contains a refnum to itself (ThisContext) - basically an infinite recursive path if parsed (but the object tree is never unfolded to infinity in the editor):

 

post-15239-0-29012900-1438176413.png

 

2) Even if some level of parsing was necessary, a circular notation could be used in the object. I had to deal with this often for HP when doing math software for their graphical calculators. Such a specifier would be similar to a recursive function call in code, a symbol that at runtime resolves to my own function pointer in the stack, and at edit time just informs the editor to treat this object in a special way (for instance to open myself if I double-click on my own subVI icon on my BD). In math it's typically about pattern matching, where the circular identifier in symbolic mode throws the parser into a look-up table with predefined results involving special functions, and in numeric mode stops the recursive unfolding when sufficient numeric precision has been achieved.

 

That's what I mean with I don't know why LabVIEW does not allow me to do this - we got recursive VI calls eventually...

 

/Steen

Link to comment

ByDVR.png

 

How do you get a strictly-typed VI Reference out of your LV FILE Object here?

 

Your LVFileRefs is generic, right?  Do you choose the "Get VI Reference" manually?


In my earlier post, I didn't mean to add the LVFileRefs object into the Children, I meant for it to stay in the Parent.  If it's in the Parent, it's also in the Children.  I was a bit unclear on that and you seem to have picked up on something I didn't mean to infer, sorry.


I simply wanted to state that a "chain of responsibility" for creating the concrete implementation objects does not preclude composition in any way.


Do you have the factory method for instantiating your child objects as part of your parent class?

 

I would much prefer the chain of responsibility because using dynamic loading of classes, it allows instant scalability.  The way youa re doing things you have chained your parent to each of its children, unless I have misunderstood something.  Giving a parent class knowledge of a concrete child implementation seems wrong to me

Link to comment

ByDVR.png

 

How do you get a strictly-typed VI Reference out of your LV FILE Object here?

 

Your LVFileRefs is generic, right?  Do you choose the "Get VI Reference" manually?

 

I select the GetVIRefnum method manually (it's a method of LVFile), and it'll throw an error if the child object on the wire isn't one of those that uses a VI refnum (it could be a Project for instance). Note that it doesn't expose the LVFileRefs object anymore:

 

post-15239-0-40006800-1438183352.png

 

 

Do you have the factory method for instantiating your child objects as part of your parent class?

 

I would much prefer the chain of responsibility because using dynamic loading of classes, it allows instant scalability.  The way youa re doing things you have chained your parent to each of its children, unless I have misunderstood something.  Giving a parent class knowledge of a concrete child implementation seems wrong to me

 

I agree that the parent shouldn't know anything about its children. The instantiation method (factory) is external to the LVFile class, but internal to the lvlib that all the classes is in. It occurs to me that putting LVFileRefs inside that same lvlib ties it together with the other classes, something I'd like to avoid - oh well, still things to tweak.

 

Anyways, outside the LVFile hierarchy exists a polyVI that offers 5 instances: New by path, or by class, library, project, or VI refnum:

 

post-15239-0-91968200-1438183751.png

 

Each of these 5 instances within the polyVI uses a selection of "New" methods from either a child class or from LVFile to create the new LVFile object. The only "New" method used from LVFile is NewUnsupported, which is output in special cases ("unsupported" types like .zip or folders for instance - meant to facilitate that you can have your app read up an entire folder hierarchy without having to filter errors out yourself, just don't handle any of the Unsupported file objects afterwards).

 

/Steen

post-15239-0-90975300-1438088011.png

Edited by Steen Schmidt
Link to comment

That's what I mean with I don't know why LabVIEW does not allow me to do this - we got recursive VI calls eventually...

 

I haven't worked through all the implications, but I wouldn't be surprised if you're right and this isn't strictly necessary (because the DVR doesn't need an actual object allocated) and with enough work, NI could resolve this in the same way it resolved static recursion. My guess is that if this is the case, this is simply an inheritance from static type recursion, where LV does need to allocate a value statically, since you can't have a null object.

Edited by Yair
Link to comment

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.