Re: Aggregation vs composition
From: Daniel T. (postmaster_at_eathlink.net)
Date: 05/13/04
- Next message: Thomas Gagné: "Re: Why is OO popular?"
- Previous message: Rob: "Re: Aggregation vs composition"
- In reply to: Matthias Hofmann: "Re: Aggregation vs composition"
- Next in thread: Robert C. Martin: "Re: Aggregation vs composition"
- Reply: Robert C. Martin: "Re: Aggregation vs composition"
- Reply: Matthias Hofmann: "Re: Aggregation vs composition"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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?
- Next message: Thomas Gagné: "Re: Why is OO popular?"
- Previous message: Rob: "Re: Aggregation vs composition"
- In reply to: Matthias Hofmann: "Re: Aggregation vs composition"
- Next in thread: Robert C. Martin: "Re: Aggregation vs composition"
- Reply: Robert C. Martin: "Re: Aggregation vs composition"
- Reply: Matthias Hofmann: "Re: Aggregation vs composition"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|