Re: Aggregation vs composition

From: Daniel T. (postmaster_at_eathlink.net)
Date: 05/13/04


Date: Thu, 13 May 2004 12:19:04 GMT


"Matthias Hofmann" <hofmann@anvil-soft.com> wrote:

>Daniel T. <postmaster@eathlink.net> schrieb:
>>
>> My god this has been a long discussion... I want to ask about something
>> here. First let me bring back the Car example:
>>
>> class Car {
>> Engine* e;
>> bool canDelete;
>> public:
>> Car(): e( 0 ), canDelete( true ) { }
>> ~Car() { delete e; }
>> void doneWithEngine() { canDelete = true; }
>> Engine* getEngine() { canDelete = false; return e; }
>> };
>>
>> You insist that Car is solely responsible for Engine. Now compare to:
>>
>> class Car {
>> auto_ptr<Engine> e;
>> bool canDelete;
>> public:
>> Car(): e( 0 ), canDelete( true ) { }
>> ~Car() { }
>> void doneWithEngine() { canDelete = true; }
>> Engine* getEngine() { canDelete = false; return e.get(); }
>> };
>>
>> As far as any object outside of Car is concerned, nothing has changed.
>> Personally, I wouldn't even change the UML diagram. According to your
>> statments so far though, Car is no longer solely responsible for its
>> engine?
>
>Well, good point. I agree that the interface of Car remains the same and
>that the UML diagram should not be changed, unless the fact that an auto_ptr
>is used plays an important role, which I think is not the case.
>
>What happens in this example is that the Car's destructor will invoke the
>auto_ptr's destructor (you can't see it in the code, but the compiler
>arranges for this to happen). The Car has delegated the responsibility for
>destroying Engine, in such a way that everything is the same as before.
>
>>From an implementation point of view, I'd say auto_ptr now has sole
>responsibility for the part. From a design view, it is still Car, because
>auto_ptr does not show up there. But I think I can see your point: The Car
>and the auto_ptr kind of share the responsibility, so that there is the
>class/caller relationship we have discussed.

Now, take it one step higher:

class Car {
   Engine* e;
   bool canDelete;
public:
   Car(): e( new Engine ), canDelete( true ) { }
   ~Car() { delete e; }
   void doneWithEngine() { canDelete = true; }
   Engine* getEngine() { canDelete = false; return e; }
   // other methods
};

class CarClient {
   Car c;
public:
   CarClient() {
      c.getEngine(); // sets canDelete to false
   }
   // member-functions that use car and engine
   // calling getEngine each time it is needed
   // assume that 'c' is never exposed to any other objects
};

Is it still Car that is responsible for Engine, or CarClient? Compare
this to the auto_ptr example above.

The point of all these examples is this: You have ascribed much
importance to a particular word in a particular language 'delete', and
have asserted that Composition and 'delete' are intimately linked. But
you forget that 'delete' doesn't necessarily free up memory; it may
simply mark the object as unused (see any number of small object
allocators that have been implemented for example.)

Ultimately though, from a design perspective what is important isn't the
function that actually releases the memory, but what objects use other
objects. UML is a design tool, not an implementation tool. So, from a
design perspective, what is the difference between these two diagrams?

   0..1
[A]----------->[B]<------[C]

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

The difference is simple. In the former diagram, A is not solely
responsible for the B it holds, in the latter diagram, it is.

Now comes the hard part, what does it mean to be "solely responsible"?
You have asserted that it means that the system as a whole must ensure
that, of the classes diagramed, A must be the last to see B objects
alive. Now I ask, how does this system wide responsibility make A
"solely responsible" for its Bs?

>> Another example:
>>
>> class A {
>> Engine* e;
>> public:
>> A(Engine* e): e(e) { }
>> ~A() { delete e; }
>> };
>>
>> void b(Engine* e) {
>> A a(e);
>> }
>>
>> class Car {
>> Engine* e;
>> bool canDelete;
>> public:
>> Car(): e( 0 ), canDelete( true ) { }
>> ~Car() { b(e); }
>> void doneWithEngine() { canDelete = true; }
>> Engine* getEngine() { canDelete = false; return e; }
>> };
>>
>> Who's solely responsible for the engine now?
>
>I think this is easy. The class called A is responsible for the engine *it*
>holds and Car is responsible for the engine that *it* holds. If each object
>has a pointer to the same engine, you're in trouble: you violated the rules
>of UML, as a part may not be shared by two composite wholes, and your
>program is likely to do weird things (probably crash sooner or later).
>
>The diagram for that case is:
>
>[A]<#>-----------[Engine]--------<#>[Car]

Be careful, Car doesn't delete any engines in this example... or does it?



Relevant Pages

  • Re: Aggregation vs composition
    ... > confusing holding a reference with calling delete on a reference. ... It is still Car who is responsible, ... The only possible way for the engine to be deleted once ... responsibility. ...
    (comp.object)
  • Re: North wales deaths
    ... I'm skeptical that a car could hold the ... mooted here several times - that the driver is always responsible. ... to a dscussion on appropriate speeds for the conditions, ... > responsibility for what occurs when I drive a car? ...
    (uk.rec.cycling)
  • Re: Aggregation vs composition
    ... First let me bring back the Car example: ... > class Car { ... > You insist that Car is solely responsible for Engine. ... responsibility for the part. ...
    (comp.object)
  • Re: meritocracy?
    ... lights, people will soon stop running red lights. ... where "owner onus" (the responsibility of the registered car ... innocent kids in prison for over ten years. ...
    (talk.origins)
  • Re: 110mph in a 30mph zone!!
    ... Can't you just for once acknowledge that driving a car is a huge ... responsibility for the speeds at which you drive your car? ... recognition of the damage your car can do and handle it accordingly. ... I'm a lot more evil than you think. ...
    (uk.rec.driving)