Re: Aggregation vs composition

From: Michael Rauscher (michlmann_at_gmx.de)
Date: 05/27/04


Date: Thu, 27 May 2004 18:01:23 +0200

Hi Daniel,

excuse the late and short answer.

Daniel T. schrieb:
> In article <c8sc1m$h1s$07$1@news.t-online.com>,
> Michael Rauscher <michlmann@gmx.de> wrote:
>>[...]
>>
>>
>>>Say you saw code like this:
>>>
>>>Engine* makeEngine() {
>>> return new Engine;
>>>}
>>>
>>>void destroyEngine(Engine* e) {
>>> delete e;
>>>}
>>>
>>>void func() {
>>> Engine* e = makeEngine();
>>> // do something with e
>>> destroyEngine( e );
>>>}
[...]
>
> Exactly! And the code that calls new and delete are also responsible for
> creation and destruction, as well as the code that calls that code and
> so on up the line. Well, not all the way up the line, there is a break
> point: if the calling code has no way of knowing that a particular
> object exists, then it cannot be responsible for deleting that object
> even if it calls code to delete said object.

As the associated client doesn't contain the parts, it can not be
responsible for destroying it. In opposite, the auto_ptr is part of Car
and as composition is transitive, the engine is part of Car, too.

>
> In the example above 'func' is every bit as responsible for creating and
> destroying the engine as makeEngine and destroyEngine. The
> responsibility is shared because the object is shared.

It's useless to talk about a handful of functions if not knowing the
semantics. E.g. I'd say nothing against a composite that uses another
object (factory) to create the engine. I'd also say nothing against a
composite Car that uses a utility class/object and calls
destroyEngine(e) on it. This all doesn't change the semantics, that Car
is the composite.

>
> Maybe I should just come out and make my point here. If a particular
> method in a particular class unconditionally destroys a part (and must
> do so according to the documentation,) then any client object that calls
> that method is every bit as responsible for the parts destruction as the
> whole.

Only, the whole that contains the parts (and maybe offers methods to
remove them) can be taken in responsibility for destroying them.

[...]
>
>>>And since you brought creation into it:
>>
>>Sorry :-( It was the UML that brought creation into it.
>
>
> You have to admit though that we have been avoiding the subject.

Yes, because it's irrelevant to

[A]<#>----[B]<----[C]

[...]
>>As I wrote in an other posting, Composition doesn't *imply* creation,
>>since a composite object may
>>a) remove a part and give it to another composite object
>>b) remove a part and the part may assume responsibility on itself
>>
>>At least a) implies, that a composite object may accept parts and take
>>responsibility for them, like in your example.
>
>
> But only composite objects may create the part according to the
> description in the UML standard. For example, whatever does create the
> engine that the above accepts, *must* also be a "Whole" to Engines part.

No. But please, forget about creation. It just complicates things and
I've just citated the UML and excused for that... It's irrelevant to our
topic.

[...]
>>This applies to every language where the programmer has full control on
>>object creation/destruction or memory allocation/dealloction
>>respectively. The examples are shown in C++ because we have full
>
> control
>
>>on object creation/destruction and therefore can strictly follow UML's
>>"implementation terms" to disprove your assumptions ;-)
>
>
> So you are agreeing that your interpretation of Composition only applies
> to languages that have some sort of explicit object destruction... I'm

No. But it's straight-forward to implement composition using C++. This
way one can concentrate on what a composition is without having side
effects that GC languages imply.

> left to wonder how a relationship should be protrayed if one doesn't
> know yet what language will be in use? How should Composition be
> implemented in languages that don't need such a mechinism?

Again, I don't rely on a specific language. I just used C++ to show
examples.

And: it should be implemented in the same way (see below).

The postcondition of destroying the whole is, that the parts doesn't
exist anymore.

IOW: if the part exists after destroying the whole, then the system
hasn't implemented the composition right.

In a language, where one can destroy objects explicitly, it's not a big
deal to ensure this condition.

Now, you have a language that doesn't let you destroy objects
explicitly. But you have to ensure the postcondition. One can do this is
two ways
(a) don't let the parts be referenced by other objects than the whole knows
(b) ensure that the whole is the last object that references the parts

As one should always care about (b) (in C++ too) it'd be implemented in
the same way.

[...]
>>>>Compare (Organization doesn't hide departments)
>>>>
>>>>[Organization]<#>-----[Deparment]<-------[Person]
>>>>
>>>>with (Organization completely hides departments)
>>>>
>>>>[Organization]<#>-----[Deparment]
>>>> /|\
>>>> |
>>>> [Person]
>>>>
>>>>Problem: The latter diagram doesn't show that a person works in a
>>>>department.
>>>
>>>
>>>Why not simply:
>>>
>>> [Organization]<>-----[Deparment]<-------[Person]
>>>
[...]
> Clients of Organization don't need a special diamond to know that they
> must not destroy Departments. No object is permitted to destroy an
> object that is being used by some other object.

Who said, that the departments are currently used?

E.g. the aggregation allows clients to get the departments, then destroy
the organization, then to work with the departments and in the end to
destroy the departments. With composition you can't do that, because you
know that the departments get destroyed as soon as you destroy the
organization.

>>>>
>>>>+-----------+
>>>>| Person |
>>>>+-----------+
>>>>|-depId:int |
>>>>+-----------+
[...]
>>
>>This is just a technical issue. Logically we have a reference to a non
>>existing object.
>>
>>How would you document the depId attribute of Person? E.g. "the ID of
>>the perhaps existing department that this Person probably works in"?
>>This would lead into millions ;-) of checks.
>
>
> Actually, I would document the depId attribute as something that
> Employee must feed to Orginazation for a particular set of methods. The
> whole point is that Employee isn't dependent on departments, so why
> imply a dependancy in the documentation that doesn't exist?

To be more concrete, let me specify the following invariant:

context Person inv:
     self.deptId >= 0 implies self.organization.departments->select(d |
d.deptId = self.deptId)->notEmpty()

Would you now say, that because of this invariant of class Person,
Organization isn't a composition of Departments anymore?

>
>>What I want to say is: it's in first place not a technical but a
>>logical
>>issue, that prevents us from destroying used objects.
>
>
> In GC languages, the symantics of the language prevents us from
> destroying used objects. In C++, the desire to write programs that don't
> crash prevents us from destroying used objects.
>

In first place I want my application to be consistent. To me, technical
issues are the consequence of logical issues.

>
>
>>>In general, I would rather see the
>>>composition relationship dropped, converted to aggregation than see
>>>the
>>>composition relationship inappropriately maintained like this.
>>
>>With changing the relationship to an aggregation you get some important
>>information lost: at least the fact, that only the whole may destroy
>>departments.
>
>
> If we assume that no object is allowed to destroy an object used by
> others (a perfectly resaonsble assumption IMV because we want our
> programs to not crash,) then no information has been lost at all. Only a
> part that is no longer useful may be destroyed... If it is no longer
> useful, then it really doesn't matter who destroys it, does it.

Yet, it does.



Relevant Pages

  • Re: Aggregation vs composition
    ... Is this not Composition, ... responsibility for them, like in your example. ... Just because there's a method that returns a reference doesn't imply ... that this reference is used to destroy the referenced object. ...
    (comp.object)
  • Re: Aggregation vs composition
    ... responsibility is shared because the object is shared. ... Is this not Composition, simply because Car is ... may destroy the part. ... >> other language that I'm aware of. ...
    (comp.object)
  • Re: Aggregation vs composition
    ... aggregation and composition in UML are flawed concepts. ... > responsibility is shared because the object is shared. ... > may destroy the part. ...
    (comp.object)
  • Re: Aggregation vs composition
    ... Would you insist that it stay a Composition? ... Not only does X destroy the ... > int, it has no other choice but doing so as it cannot prevent the ... they would have no responsibility in your failure. ...
    (comp.object)
  • Re: Aggregation vs composition
    ... > Compare the two diagrams below: ... because it depends on the language. ... The second diagram shows a composition. ... "Destroy" is also a conceptual thing as ...
    (comp.object)