Jump to content


Photo
- - - - -

How do you handle Aggregation?


  • Please log in to reply
15 replies to this topic

#1 Stobber

Stobber

    Very Active

  • Members
  • PipPipPip
  • 58 posts
  • Location:Austin, TX USA
  • Version:LabVIEW 2011
  • Since:2005

Posted 13 January 2012 - 04:34 PM

I've noticed a budding trend in my LV development that I want to stop and consider. When I have a class that aggregates other classes -- meaning that the owning object has a different lifetime than the owned object(s) -- I've started putting the owned object into a DVR and holding the DVR in the owning object. The reasoning for this is that I can check for a null refnum on the DVR when I want to see whether an owned object indeed exists as a member of the owning object. It leads to extra work every time I access an owned object, but I can make merge VIs and templates for most of that.

I've attached an example of this. Is this stupid, or does it make sense?

Alternatives I can think of are:
  • Store the owned object directly in the owning class. When I unbundle the owned object, compare against its default value to see whether it's actually there. There are problems with this, though. If I change defaults in the class definition, they aren't reflected in any constants I've placed on the block diagrams of calling methods. I may also need to use the defaults as a valid configuration for the owned object.
  • Add a Boolean data member to the owning class that indicates whether an owned object has been loaded. I hate this approach though, because it's a configuration parameter, not a data member. I don't like that I have to lug a bunch of meta-information about the object around with its real data.
  • Make every owner hold an array of owned objects, but know that the array size is limited to 1. Then I can check for an empty array when looking for the owned object. Of course, there's a memory allocation every time I add an owned object to an owner, and there are extra array operations every time I access or modify the owned object.

Attached Files



#2 ShaunR

ShaunR

    LabVIEW Archetype

  • Members
  • PipPipPipPipPipPip
  • 2,224 posts
  • Version:LabVIEW 2009
  • Since:1994

Posted 13 January 2012 - 05:27 PM

To me it kinda makes sense. It is a symptom of having no way to destroy an object. The by-val way of creating a pseudo-destroy method is as you describe in No3 since you can clear the array which is a surrogate for destroying the objects. You then pray to the great god MemMan and hope that the garbage collection will get rid of allocations (or at the very least, reused). The by-ref is a way of achieving the same pseudo-destroy, since you can destroy a ref (and just ignore what it returns).

The difference in the destroying is that the owner is responsible for destroying the objects, rather than a function of whatever object you want to get rid of. In LV it's not a biggie because you have no control over de-allocations anyway, but in other languages you may have several different de-allocation procedures to satisfy that the owner may not be aware of.
A positive attitude may not solve all your problems, but it will annoy enough people to make it worth the effort. (Herm Albright 1876-1944).

Founder and general mischief maker on www.labview-tools.com.
SQlite aficionado and websocket zealot.
If it 'aint in LabVIEW, then you 'aint got a clue!

#3 ned

ned

    Extremely Active

  • Members
  • PipPipPipPip
  • 392 posts
  • Location:Menlo Park, CA
  • Version:LabVIEW 2009
  • Since:1999

Posted 13 January 2012 - 05:57 PM

The description makes sense; haven't opened the attachment. It's common in C to check for a null pointer, and this seems analagous.

#4 Stobber

Stobber

    Very Active

  • Members
  • PipPipPip
  • 58 posts
  • Location:Austin, TX USA
  • Version:LabVIEW 2011
  • Since:2005

Posted 13 January 2012 - 07:28 PM

The description makes sense; haven't opened the attachment. It's common in C to check for a null pointer, and this seems analagous.


Yep, it is analogous, but many things common in C make no sense in a by-value dataflow language, so I want to make sure the rest of the LV expert community agrees that this is a good way to do it.

#5 drjdpowell

drjdpowell

    The 500 club

  • Premium Member
  • 543 posts
  • Location:Oxford, UK
  • Version:LabVIEW 2011
  • Since:1999

Posted 14 January 2012 - 11:13 AM

*
POPULAR

  • Store the owned object directly in the owning class. When I unbundle the owned object, compare against its default value to see whether it's actually there. There are problems with this, though. If I change defaults in the class definition, they aren't reflected in any constants I've placed on the block diagrams of calling methods. I may also need to use the defaults as a valid configuration for the owned object.

Do this one. The default object will always update to reflect the class definition (I’m pretty sure, please correct me if I’m wrong) as default objects don’t save values, they just point to the class definition. To get around the second problem, always have a parent class that is “virtual”, meaning that only child objects are ever actually created and used. Then you can use the default parent object as “null”. Having a virtual parent class is very useful for lots of other reasons, so it is usually good to put it in regardless.


— James

PS, here is your example, modified to make “Config” a virtual parent, overridden by a ConcreteConfig class, and eliminating the DVR:
Attached File  Aggregation Discussion with virtual parent.zip   45.1K   39 downloads

Edited by drjdpowell, 14 January 2012 - 11:31 AM.


#6 Val Brown

Val Brown

    The 500 club

  • Members
  • PipPipPipPipPip
  • 702 posts

Posted 14 January 2012 - 04:30 PM

To me it kinda makes sense. It is a symptom of having no way to destroy an object. The by-val way of creating a pseudo-destroy method is as you describe in No3 since you can clear the array which is a surrogate for destroying the objects. You then pray to the great god MemMan and hope that the garbage collection will get rid of allocations (or at the very least, reused). The by-ref is a way of achieving the same pseudo-destroy, since you can destroy a ref (and just ignore what it returns).

The difference in the destroying is that the owner is responsible for destroying the objects, rather than a function of whatever object you want to get rid of. In LV it's not a biggie because you have no control over de-allocations anyway, but in other languages you may have several different de-allocation procedures to satisfy that the owner may not be aware of.


As you all might guess, I see this a bit differently -- given that we're all functioning in LV which is byval in its exposed interface. The idea that the byref construct is "better" comes IMO from a history of having learned it first (generally in a CS program) and then assuming that the optimal way to program is to follow that paradigm and that would include the necessity of being the MemMan god, instead of just knowing how it operates for you in a certain well implemented byval environment like LV. So you have two broad choices:

1. try to force LV to be as byre-like as possible (which is the basis for a number of these suggestions, like the virtual parent)
2. accept and use the native tools and this would mean esp, release the need to believe that you can optimize memory management IFF you were allowed to be god.

If you adopt the second course then creating the refnum and checking for a null refnum is IMO the way to go. It's simple, direct, is inline with the paradigm and it works, esp because you can count on the native MemMan god of LV to do its job.

Just my two cents worth.
All of my objects are byval

#7 Aristos Queue

Aristos Queue

    LV R&D: I write C++/# so you don't have to.

  • Premium Member
  • 2,620 posts
  • Location:Austin, TX
  • Version:LabVIEW 2011
  • Since:2000

Posted 15 January 2012 - 07:29 AM

*
POPULAR

You then pray to the great god MemMan and hope that the garbage collection will get rid of allocations

FOR THE NTH TIME: THERE IS NO GARBAGE COLLECTOR IN LABVIEW.

#8 Daklu

Daklu

    Bringing the Fu to you

  • Premium Member
  • 1,752 posts
  • Location:Seattle
  • Version:LabVIEW 2009
  • Since:2006

Posted 15 March 2012 - 12:50 AM

(Hadn't seen this post before...)

Store the owned object directly in the owning class. When I unbundle the owned object, compare against its default value to see whether it's actually there.

This doesn't make sense to me. As far as LV is concerned, a default object is just as real as a non-default object. I'm guessing what you really are looking for is a way to know if the bundled object has actually been configured. With that in mind, here are other options I use at various times:
  • Labview doesn't have constructors, but one of the first things I do when I create a new class is add a "Create MyClass" method to it. I add required inputs for any data members the class needs to operate correctly. By convention all my objects are instantiated using the creator method instead of by dropping a class constant. In your case, your "Create OwningObject" vi would have a required input terminal for "Owned.lvclass," and users will not be able to instantiate an owning object without also giving it a configured owned object, assuming they are following the convention. (This avoids your problem 90% of the time.)
  • Ditch the comparisons and let your owning object perform its operations on the default object. Ideally I give the default object some reasonably simple and useful functionality. If that's not possible and the operation fails you can return a descriptive error message. Ultimately it's up to the developer to make sure they are using your api correctly. (This covers another 9% of the cases.)
  • Use LVObject as a placeholder for the owned class in the owning class. Create a private vi to retrieve and downcast the object to the owned class. If the downcast fails you'll know the user has not set the object correctly. This is very similar to what James suggested, but you don't have to create a separate parent class that doesn't do anything. (Another 0.9% covered.)
  • Include an "IsConfigured" parameter in the owned (not owning) object. I'm not particularly fond of this approach, but sometimes it is necessary.

Certified LabVIEW Architect
Dak's First Law of Problem Solving: If the solution looks simple, I don't know enough about the problem.

Yes, the QSM is flexible. So is Jello. That doesn't make it good construction material.

There are two secrets to success:
Secret #1 - Never tell everything you know.


#9 MikaelH

MikaelH

    The 500 club

  • Premium Member
  • 576 posts
  • Location:Sydney
  • Version:LabVIEW 2012
  • Since:1996

Posted 15 March 2012 - 02:48 AM

Labview doesn't have constructors,


Unless you use referenced based OO, like GOOP3 or GOOP4 ;-)
Posted Image

#10 mje

mje

    The 500 club

  • Premium Member
  • 813 posts
  • Location:Milford MA USA
  • Version:LabVIEW 2011
  • Since:1997

Posted 16 March 2012 - 01:57 PM

Also somehow missed this discussion the first time around.

I've noticed a budding trend in my LV development that I want to stop and consider. When I have a class that aggregates other classes -- meaning that the owning object has a different lifetime than the owned object(s) -- I've started putting the owned object into a DVR and holding the DVR in the owning object.


I don't understand how an aggregation model can ever be implemented purely by value. The very fact that you're aggregating something with a lifetime independent of the aggregator means the items being aggregated are exposed and potentially likely used outside of the scope of the aggregator. If these objects are operated on in an external scope and you're aggregating by-value, how would the aggregated values ever possibly contain accurate state information?

The aggregator model to me requires some level of referencing mechanism, be it DVRs or otherwise. If anyone can generate an example illustrating otherwise I'd love to see it, but I suspect such examples would actually be composition (where the lifetime of the contained objects is tied to the container).

#11 Daklu

Daklu

    Bringing the Fu to you

  • Premium Member
  • 1,752 posts
  • Location:Seattle
  • Version:LabVIEW 2009
  • Since:2006

Posted 16 March 2012 - 03:27 PM

I don't understand how an aggregation model can ever be implemented purely by value...

If anyone can generate an example illustrating otherwise I'd love to see it, but I suspect such examples would actually be composition (where the lifetime of the contained objects is tied to the container).

Here's how I do it. I think it qualifies as aggregation but I'm interested to hear what you think.

Suppose I have a two classes, Car and Engine. I don't want to instantiate a Car object without a valid Engine implementation, so on Create Car.vi I add a required input for Engine (which I instantiate with its own creator method.) Furthermore, the Car.Destroy method return the Engine object that was present when the car was destroyed. I can create an Engine object and manipulate it before creating the Car object. I can also manipulate the Engine object after the Car object is destroyed.

What I can't do is directly manipulate the state of the Engine object inside the car via DVR or references. If the Engine needs to be manipulated I either write Car methods that delegate to Engine methods, or I write accessors for the Engine object. I prefer delegation as it preserves encapsulation, but if discover I'm writing delegate Car methods for every Engine method I likely have a design flaw. At that point I'd probably go back to accessors for the Engine object (or perhaps have an Engine input terminal on the relevant Car methods.) The user calls Car.GetEngine to retrieve the object, performs operations on it, and calls Car.SetEngine to put it back in the Car.

Certified LabVIEW Architect
Dak's First Law of Problem Solving: If the solution looks simple, I don't know enough about the problem.

Yes, the QSM is flexible. So is Jello. That doesn't make it good construction material.

There are two secrets to success:
Secret #1 - Never tell everything you know.


#12 mje

mje

    The 500 club

  • Premium Member
  • 813 posts
  • Location:Milford MA USA
  • Version:LabVIEW 2011
  • Since:1997

Posted 16 March 2012 - 03:51 PM

Gotcha, that makes sense.

I actually see an error in my post, I was trying to say I expect the alternatives to be containment. All these silly semantics. Aggregation and containment are different forms of composition. I trip over these words all the time.

Anyways, to me what you describe is containment, because the Engine becomes attached to a Car, and once attached is only accessible through the Car. I completely agree though that the line gets blurry, because you definitely can have an Engine before you have the Car, and it could also be removed, or returned when you're done with the car...

#13 Daklu

Daklu

    Bringing the Fu to you

  • Premium Member
  • 1,752 posts
  • Location:Seattle
  • Version:LabVIEW 2009
  • Since:2006

Posted 16 March 2012 - 05:27 PM

All these silly semantics. Aggregation and containment are different forms of composition.

I thought containment was just a general term encompassing aggregation and composition. Wikipedia implies containment is a specific form of composition, which is in turn a specific form of aggregation...

In UML, composition is depicted as a filled diamond and a solid line...
The more general form, aggregation, is depicted as an unfilled diamond and a solid line.
Composition that is used to store several instances of the composited data type is referred to as containment.

(Italics mine.)

I'm not particularly fond of that topology and I'm not sure where the author got it from. The two linked references don't appear to support it. MSDN says,

Aggregation is actually a specialized case of containment/delegation.

though their definitions appear to be linked to specific feature available in COM.



I trip over these words all the time.

Me too. I feel like sometimes I get nit-picky with terminology but it sure gets hard to understand what others are talking about when we can't agree on the definitions of fundamental concepts.

Certified LabVIEW Architect
Dak's First Law of Problem Solving: If the solution looks simple, I don't know enough about the problem.

Yes, the QSM is flexible. So is Jello. That doesn't make it good construction material.

There are two secrets to success:
Secret #1 - Never tell everything you know.


#14 Aristos Queue

Aristos Queue

    LV R&D: I write C++/# so you don't have to.

  • Premium Member
  • 2,620 posts
  • Location:Austin, TX
  • Version:LabVIEW 2011
  • Since:2000

Posted 16 March 2012 - 09:36 PM

The first time this came up, the only thing I replied to was the factual error -- I'm tired of that garbage collector myth being propagated.

What I avoided was any commentary on the main post because I didn't feel like it, but today, I feel like asking:
Why do your objects have lifetime? Processes (VIs and communications channels) have lifetime. Data does not. These patterns that you're developing in your code come about because of the co-joining of these two concepts, an artifact of other programming languages that are piss poor programming models for a parallel environment.

#15 MikaelH

MikaelH

    The 500 club

  • Premium Member
  • 576 posts
  • Location:Sydney
  • Version:LabVIEW 2012
  • Since:1996

Posted 17 March 2012 - 03:51 AM

...other programming languages that are piss poor programming models for a parallel environment.

:thumbup1:
Posted Image

#16 crelf

crelf

    I'm a LAVA, not a fighter.

  • V I Engineering, Inc.
  • 5,742 posts
  • Version:LabVIEW 2012
  • Since:1993

Posted 19 March 2012 - 02:50 AM

Why do your objects have lifetime? Processes (VIs and communications channels) have lifetime. Data does not. These patterns that you're developing in your code come about because of the co-joining of these two concepts, an artifact of other programming languages that are piss poor programming models for a parallel environment.

:thumbup1:

post-181-1170858537.png