views:

127

answers:

5

I am creating entities for a simulation using aggregation and composition.

In the following C++ example:

class CCar
{
    CCar( CDriver* pDriver )
    { m_pDriver = pDriver; }

    CDriver* m_pDriver;

    CEngine m_Engine;

    CDriverControls m_Controls;
};

in the above example, a car consists of an engine and a set of driving controls (by composition). A car must also have a driver (by aggregation).

But this only explains the hierarchial relationships - a driver belongs to a car, and an engine and controls also belong to the car. But these members all relate to each other also - a driver must perform an action on the controls, the controls must perform actions on the engine. These relationships also work in multiple directions - the engine can stall and cause the controls to seize up, or the controls could spin wildly and hurt the driver? And what if the driver doesnt like the sound of the engine and leaves the car? How do these relationships work?

I am compositing many different entities from many different objects which often interact with other objects, and am interested in how to manage these relationships in a designed way.

thankyou!

edit:

as responses suggest, one way to manage this is through pointing the car to the driver, and giving the driver a pointer to the car, etc. This makes sense and solves this specific example. However, in a design sense, this increases responsibility of the driver, where this object is tasked with keeping track of which car it belongs to, but surely this is the duty of the container to keep track of which objects belong together? Likewise, tasking CCar with managing these relationships will turn CCar into a blob. Is there a designed solution to dealing with these kinds of relationships?

+1  A: 

You build those into the methods of each class. What you're describing is the behavior of each class. Your requirements suggest that the relationships are bi-directional as well. Your Controls class will have methods that take an Engine parameter and call its methods. The Engine will have limits on its RPM, HP, torque, etc., manipulated by the Control, that will have limits built into them (e.g., "If your RPM drops too low, stall out").

It's more than just composition. Your build behavior and rules into the methods. The methods might take parameters that express what you need.

duffymo
A: 

You probably want a two way association between the car and the driver:

CCar( CDriver* pDriver ) :
     m_pDriver(pDriver)
{
    m_pDriver->SetCar(this);
}

Then the driver can access the members of Car through the Car's public interface.

Eclipse
+1  A: 

The question should be "does my application need this relationship?" For example, if you are modelling steering a car for a simple driving game, you probably don't need to worry about the motor for the sunroof at all. The steering wheel may need to know it is connected to the road wheels, but there is no need for the reverse relationship.

Bottom line - in the real world everything is connected, but in the computer models we make of that world to solve particular problems, they are not.

anon
thanks for the reply.the example given is really just an example (although probably could have used a better one!), but i was trying to illustrate a problem i am facing. I am not actually writing a program about cars and drivers.
+1  A: 

It may be useful to emphasize interfaces rather than composition, aggregation, or inheritance. For example, your driver class could be written such that it can use the "steering wheel" interface. Naturally, your implementation of the steering wheel provides an implementation of the "steering wheel" interface. Likewise, the car supplies a "car interface" which the steering wheel implementation might be written to take advantage of.

Your implementations may use composition, aggregation, and inheritance. But in this approach it is really the interfaces that drive the design. Whether you use composition, aggregation, or inheritance in a given instance becomes merely an implementation detail.

Dan Moulding
thankyou, this is a great start for me to solving this.
A: 

To solve this question, it is first important to establish what each component does.

CCar - a container holding components and aggregates.

CDriver - an object representing a driver

CEngine - an object representing an engine

etc.

For a small and simple program, a simplified design should be used where the driver is given a pointer to the car

CCar( CDriver* pDriver )
{
m_pDriver = pDriver;
m_pDriver->SetCar(this);
}

For a larger application this is unacceptable where a CCar may require new components adding, etc. and it would be poor design practice to give a driver access to the whole CCar - here the driver would be able to change not only the steering wheel, but the cars colour, etc. which is clearly not the intention.

What about just giving the driver access to the bits it needs?

m_pDriver->SetSteeringWheel( m_SteeringWheel );
m_pDriver->SetHandBrake( m_HandBrake );

this solves that problem, now the driver has no access to the cars other attributes ( such as colour ). However, it gives the CDriver class more responsibilities. Where CDriver may be able to use alot of controls, the class can get very large, and holds the responsibility for operating these steering wheel and handbrake objects. What if the driver gets in a different type of car that doesnt have the same controls as the others? Now the driver has to figure out how to operate the vehicle with the controls it has? Extra logic. Extra blob.

The solution to all of this is to use a mediator class (or variant), to control how the driver interacts with the vehicle. This could be done in one of two ways, the driver can have a mediator to the car, which controls how the driver interacts with the car. Or the driver can have a mediator for each component or aggregate of the car that it must deal with. This is probably a better solution as the mediators can be reused for different types of car. The mediators must be able to handle the bi-directional relationships between components.

The CCar, being the container, is responsible for maintaining the mediators and hence the relationships between its components. Just the way it should be.

The mediator is responsible for handling this relationship between the components.

class CMediatorDriverToSteeringWheel
{
CMediatorDriverToSteeringWheel( CDriver* pDriver, CSteeringWheel* pSteeringWheel )
{
m_pDriver = pDriver;
m_pSteeringWheel = pSteeringWheel;
m_pDriver->AddMediator(this);
m_pSteeringWheel->AddMediator(this);
}
};

... 

CCar::CCar( CDriver* pDriver )
{
m_pDriver = pDriver;
new CMediatorDriverToSteeringWheel( m_pDriver, &m_SteeringWheel );
new CMediatorDriverToHandbrake( m_pDriver, &m_HandBrake );
}
thanks to all other answers, you all gave great contributions that helped me think about my question, but no answer really seemed to cover the entire issue. I continued researching and find this to be the most relevant answer. I am still open minded as to other solutions (as I am sure there are many) so please give more answers if you think you have a better solution!