In this section we will look at aggregation, the "part of" connection. We will look at "what is aggregation?", how to identify aggregation, how to show aggregation on a class diagram, how to specify aggregation, and how to code aggregation.
Aggregation is a link between objects that is a strong association with "part of" semantics. Aggregation is a form of association and is sometimes referred to as "strong association". An aggregation relationship may be described as "part of" or "bill of materials" between an assembly object and part objects. For example a car "has a part" motor as shown in the figure below.
An aggregation association has a cardinality or multiplicity. This is the number of objects on one side of an aggregation that may be associated with an object on the other side of the aggregation. For example, a car has one motor. This is an example of a 1 to 1 aggregation. The aggregation symbol is a diamond with a solid line between classes with the cardinality shown on each end of the solid line as shown in the figure below.
which shows the following as discussed in [Rumbaugh-91]:
antisymmetric (if A is part of B, then B is not part of A),
propagation (sharing of common operations and attribute values from the assembly to the part possibly with modification). Propagation refers to the automatic triggering of operations in part objects.
Identify aggregation by looking for assembly and
part objects. For example, a car is an assembly with a motor part.
Look for objects that cannot exist without other objects. For
example, an invoice cannot exist without a product line item.
Look for objects that are so closely associated that they are
destroyed if the first object is destroyed. For example, if an
invoice is destroyed then the product line items are destroyed.
The key questions to find an aggregation relationship from [Rumbaugh-91]
are as follows:
Would you use the phrase "part of"? A paragraph
is "part of" a chapter.
Are some operations on the whole automatically applied
to its parts? Copy applies to chapter and paragraph.
Are some attribute values from the whole applied
to all or some parts? Chapter title applies to paragraph.
Is there intrinsic asymmetry where one object is
subordinate to other objects? A paragraph is subordinate to a
chapter.
An aggregation relationship requires defining additional semantic rules particularly for the creation, copy, and deletion of objects. For example, do you automatically delete the part when the assembly is deleted?
Aggregation is shown on the class diagram by connecting assembly and part classes. The figure below is a class diagram showing aggregation using a CASE tool.
This diagram in the figure below shows the aggregation
relationship between two classes. The car class is the assembly
class. The motor class is the part class. The car class and the
motor class have the same named operations. However the operate
operation has different parameters in the car and motor classes.
In aggregation, operations in the assembly object may be propagated
to a part object. Propagation means automatic triggering (invoking)
an operation from one object to another object. For example, a
message to invoke the operate operation in a car object will propagate
(trigger) to invoke the operate operation in the motor object.
The steps to create the class diagram showing aggregation
using a CASE tool are as follows:
>> Run the CASE tool and show the tool bar or pull down menu.
>> Select and place a class symbol for the assembly class.
>> Select and place a class symbol for the part class.
>> Select the cardinality (multiplicity) on each side of the relationship, such as 1 to Many.
>> Select the aggregation relationship symbol and connect the two classes.
>> For C++ code generation, enter an object name for the traversal path name to represent the aggregation.
Each aggregation relationship should be described
in a relationship specification form. Constraints (limitations
and rules) should be stated. For aggregation multiplicity constraints
should be stated. The semantics of part deletion and the propagation
of operations and attribute values should be stated. The following
is a sample relationship specification report created in a text
editor.
- Source Class - Car
- Destination Class - Motor
- Traversal Path Class/Type and Name - Motor currentMotor
- Order by Attribute Class/Type and Name - N/A
- Type of Relationship (Association/Aggregation) - Aggregation
- Relationship Constraints (rules) - motor weight must be less than total car weight
- Relationship Cardinality - 1 to 1
- Inverse Traversal Path Class/Type and Name - Car currentCar
- Implementation - currentMotor data member implements the aggregation relationship between Car class and Motor class. The inverse relationship is implemented with currentCar as a pointer to the Car object.
- Remarks.
Once the class diagram has been completed, then C++ code can be automatically generated with a CASE tool. The CASE tool implements each aggregation shown on the class diagram. We are presenting how to code aggregation in C++ to be able to understand and modify the generated C++.
As discussed in the association section, there are
many ways to code association and aggregation in C++. Aggregation
is a special form of association. Ways to code aggregation include
coding assembly class data members to hold part objects, pointers
to part objects, or objects of special aggregation classes. The
most straight forward way to code aggregation is to identify a
traversal path name. A traversal path name is a reference
to one or more part objects. A traversal path name is an assembly
class data member that may hold one of the following: a part object,
a collection of part objects, a pointer to a part object, or a
pointer to a collection of part objects.
To code the aggregation relationship, identify a
traversal path name to represent the aggregation. The traversal
path name becomes a data member in the assembly class. In the
simplest case a car has 1 motor. In the Car class identify a traversal
path name for the 1 to 1 association with the Motor Class. The
traversal path name in the Car class might be Motor currentMotor.
In C++ code, create a data member for the traversal path name,
such as currentMotor as an automatic C++ object. An automatic
part object is deleted automatically when the assembly object
is deleted. Alternatively create a dynamic object and manually
delete the part object when the assembly object is deleted. The
following is the form to create a data member for a part object:
<Part Class Name> <Traversal path name>.
The following is an example: Motor currentMotor.
An automatic object is used in this case because
a part object generally does not exist independently of the assembly
object. When the assembly is deleted, then the part is automatically
deleted. For example, a motor object is automatically deleted
when the car object is deleted.
Based upon the diagram in the figure below, the following
C++ code was generated to show the aggregation relationship.
Listing C++ code listing
showing 1 to 1 aggregation.
//////////////////////////Car.h file////////////////////////////////////
#ifndef __CAR_H
#define __CAR_H
#ifndef __MOTOR_H
#include "Motor.h"
#endif
class Car
{ int speed; //Attribute data member
Motor currentMotor; //1:1 aggregation part data
member
public:
Car () : speed(0)
{
}
void operate (int aSpeed) ;
~ Car ( ) { } //Destructor
};
#endif
//////////////////////////Motor.h file////////////////////////////////////
#ifndef __MOTOR_H
#define __MOTOR_H
class Motor
{ int rpm; //Attribute data member
public:
Motor () : rpm(0) { }
void operate (int aRPM) ;
~ Motor ( ) { } //Destructor
};
#endif
//////////////////////////Car.cpp file////////////////////////////////////
#include "Car.h"
void Car::operate(int aSpeed)
{ speed = aSpeed;
int rpm;
rpm = 100 * aSpeed;
currentMotor.operate (rpm); //message to invoke currentMotor.operate
}
//////////////////////////Motor.cpp file////////////////////////////////////
#include "Motor.h"
void Motor::operate(int aRPM)
{ rpm = aRPM);
}
//////////////////////////main.cpp file////////////////////////////////////
#include "Car.h"
int main ()
{
Car car1; //invokes the default constructor
car1.operate (55);
return 0;
}
To generate the C++ class declaration with data member
declarations, we used two scripts. A class declaration script
generated the Car.h file and a function definition script generated
the Car.cpp file. We created the main.cpp manually. The two scripts
are shown below.
//////////////////////////TRUNCATE_EIGHT$CLASS_NAME.h
file////////////////////////////////////
#ifndef __$CAPITALIZE_ALL$TRUNCATE_EIGHT$CLASS_NAME$_H
#define __$CAPITALIZE_ALL$TRUNCATE_EIGHT$CLASS_NAME$_H
[
#ifndef __$CAPITALIZE_ALL$TRUNCATE_EIGHT$AGGREGATION_ONE_CLASS$_H
#include "TRUNCATE_EIGHT$AGGREGATION_ONE_CLASS$.h"
#endif
]
class CLASS_NAME[NO_RETURN NO_REPEAT: NO_REPEAT public BASE_CLASS ,DELETE_LAST_SYMBOL]
{ [CPP_ATTRIBUTE_STATIC CPP_ATTRIBUTE_CONSTANT
ATTRIBUTE_TYPE ATTRIBUTE_NAME$; //Attribute data member]
[AGGREGATION_ONE_CLASS AGGREGATION_ONE_NAME$;
//1:1 aggregation part data member]
public:
CLASS_NAME ()
SELECT_WHEN LOGICAL_NOT ATTRIBUTE_IS_STATIC [NO_RETURN NO_REPEAT: ATTRIBUTE_NAME(ATTRIBUTE_INITIAL_VALUE),DELETE_LAST_SYMBOL]
}
[ CPP_OPERATION_VIRTUAL CPP_OPERATION_STATIC OPERATION_RETURN_TYPE OPERATION_NAME (CPP_OPERATION_PARAMETERS) CPP_OPERATION_CONSTANT CPP_OPERATION_PURE_VIRTUAL;
]
OPERATION_CPP_VIRTUAL_BASE_CLASS ~ CLASS_NAME ( ) { } //Destructor
};
#endif
//////////////////////////TRUNCATE_EIGHT$CLASS_NAME.cpp file////////////////////////////////////
#include "TRUNCATE_EIGHT$CLASS_NAME.h"
[
OPERATION_RETURN_TYPE CLASS_NAME::OPERATION_NAME(CPP_OPERATION_PARAMETERS) CPP_OPERATION_CONSTANT
{
OPERATION_CODE
}]
In this example, the CASE tool generated the class
declarations (.h files) for the car class and the motor class.
In the car class header file (car.h), it generated the include
directive to include the header file of the part class (motor.h).
In the car class declaration, it generated a private data member,
currentMotor, to hold an object of the motor class. It generated
the constructor.
The assembly object may initialize part objects in the constructor with an initializer list. If the initializer list is not provided then the part object constructor is implicitly invoked. Sample constructors are:
Car () { } //Part object constructor implicitly invoked
Car(): currentMotor () {}//Constructor with initializer list to explicitely
//initialize car and motor
If the part object is a dynamic object created with a new statement, then the assembly object must delete the part object in the destructor. A sample destructor to delete a dynamic part object aWindShieldWiper is
~Car () { delete aWindShieldWiper; }
To code C++ classes with 1 to 1 aggregation, create
a class declaration and function definitions as shown in the diagrams
below. Key aspects of the class declaration are shown on the left.
This information class diagram shows that a aggregation assembly
class declaration should have an include statement for the aggregation
part class header file, a class name, data members, traversal
data members holding an aggregation part object, a constructor,
traversal function members to access aggregation part object,
traversal data member accessor functions, function members, and
a destructor. This information class diagram shows that the aggregation
assembly class is connected to the aggregation part class with
the traversal data member. The traversal data member is used to
access the aggregation part object. The class diagram on the right
shows sample C++ identifiers that are also in the code listings.
Key aspects of coding an aggregation assembly class are listed below. The aggregation assembly class is responsible to create and delete aggregation part objects.
- Declare a #include compiler directive to include the aggregation part class, e.g. #include "Motor.h".
- Declare an aggregation part data member (traversal data member) to implement to aggregation relationship, e.g. Motor currentMotor;
- Define a default constructor to initialize an object with default data member values in the initializer list. Aggregation part data member is automatically created. Car ()
- Define a constructor with arguments to initialize an object with passed values. Optionally, pass an aggregation part object and assign aggregation part data member. Car (int aSpeed, Motor& currentMotor) : speed (aSpeed), currentMotor (currentMotor) { }
- Define a copy constructor to initialize a object with a copy of another object. Must assign the aggregation part data member. Car (const Car& aCar) {currentMotor = aCar.currentMotor; }
- Define an assignment operator (operator=) to assign one object to another. Must assign the aggregation part data member. Car& operator= (const Car& aCar) {currentMotor = aCar.currentMotor; }
- Define an equality operator (operator==) to compare two objects. Must compare the aggregation part data members. int operator== (const Car& aCar) { return (currentMotor == aCar.currentMotor) ; }
- Define input function - C++ insertion operator for use with cin. Must invoke the aggregation part object operator>> e.g. friend istream& operator>> (istream& is, Car& aCar) {is >> aCar.currentMotor; }
- Define the output function - C++ extraction operator for use with cout. Must invoke the aggregation part object operator<<, e.g. friend ostream& operator<< (ostream& os, Car& aCar) { os << aCar.currentMotor; }
- Define get and set assessor functions, e.g. Motor& getCurrentMotor () { return currentMotor; } Warning - returning a reference provides direct access to a private data member, e.g. car1.getCurrentMotor() = newMotor;
- Define a traversal function to access the aggregation part data member, e.g. void operate ()which invokes currentMotor.operate().
- Define a default destructor to deinitialize an object, ~Car () {}
- Define a main (test) function to create an aggregation
assembly object with a part object and invoke a traversal function
In coding the aggregation part class, optionally
declare an inverse traversal data member, e.g. Car
* aCar; Prior to compiling the aggregation part
class, insert a forward declaration before the aggregation part
class declaration, e.g. class Car; The complete C++ code listing
is below.
Listing C++ Code Listing
for 1 to 1 Aggregation
// Class: Car //ANSI C++
#ifndef __CAR_H //Required for Car class
#define __CAR_H
#ifndef __IOSTREAM_H //Required for cin and cout
#include <iostream.h>
#endif
#ifndef __CSTRING_H //Required for CString class
#include <CString.h>
#endif
//Required for 1:1 aggregation (part) classes
#ifndef __MOTOR_H //Motor
#include "Motor.h"
#endif
class Car
{ int speed; //Attribute data member
Motor currentMotor; //1:1 aggregation part data
member
public:
//Default constructor alternative to compiler provided default constructor
//Ensure correct initial values
//Initialization list has members in the order declared
//Association object data member pointers initialized to null association object
Car ();
//Constructor with arguments
//Update to argument list to initialize base class data members,
//e.g. (int aNumber) : BaseClass (aNumber)
Car ( int aspeed ) ;
//Copy Constructor
Car (const Car& aCar );
//Operator= Assignment Operator
Car& operator= (const Car& aCar);
//Operator== Equality Operator
int operator== (const Car& aCar) const;
//Operator<< for cout
friend ostream& operator<< (ostream&
os, const Car& aCar);
//Operator>> for cin
friend istream& operator>> (istream&
is, Car& aCar);
//Get accessor function for non-static attribute data member
int getspeed() const
{ return speed;
}
//Set accessor function for non-static attribute data member
void setspeed (const int aspeed)
{ speed = aspeed;
}
//Get accessor function for 1:1 aggregation part data member
const Motor& getMotor() const
{ return currentMotor;
}
//Set accessor function for 1:1 aggregation part data member
void setMotor (const Motor& acurrentMotor)
{ currentMotor = acurrentMotor;
}
void operate (int aSpeed) ;
~ Car ( ) { } //Destructor - Delete any pointer data members that used new in constructors
//Destructor should be virtual if and only if class contains at least one virtual function
//Objects destroyed in the reverse order of the construction order
};
#endif
//////////////////////////.cpp file/////////////////////////////////////////////////////
#include "Car.h"
void Car::operate(int aSpeed)
{ speed = aSpeed;
}
//Default constructor alternative to compiler provided default constructor
Car::Car ()
: speed(0)
{
}
//Constructor with arguments
//Update to argument list to initialize base class data members and const data members
//e.g. (int aNumber) : BaseClass (aNumber) , constMinimumValue (aMinimumValue)
Car::Car ( int aspeed )
{ speed = aspeed; //Initialization of attributes
}
//Copy constructor alternative to compiler provided default copy constructor
Car::Car (const Car& aCar )
{speed = aCar.speed;
currentMotor = aCar.currentMotor;
}
//Operator= Assignment Operator alternative to compiler provided operator=
Car& Car::operator= (const Car& aCar)
{ if (this == &aCar) return *this;
speed = aCar.speed;
currentMotor = aCar.currentMotor;
return *this;
}
//Operator== Equality Operator
int Car::operator== (const Car& aCar) const
{ return (
//Equality check for 1:1 aggregation part data members
( currentMotor == aCar.currentMotor) &&
//Equality check for attribute data members
(speed == aCar.speed)
);
}
//Operator<< extraction for cout
ostream& operator<< (ostream& os, const Car& aCar)
{ os << "Object Attribute Values - Class Car" << endl;
os << "speed: " << aCar.speed << endl;
os << " currentMotor: " << aCar.currentMotor << endl;
return os;
}
//Operator>> insertion for cin
istream& operator>> (istream& is, Car& aCar)
{ cout << "\nEnter Object Attribute Values or 0 - Class Car";
cout << "\nEnter speed : " << endl;
is >> aCar.speed;
is >> aCar.currentMotor;
return is;
}
In the 1 to many association identify a traversal
path name that holds a collection of part objects. For example,
a car has many wheels. In the car class identify a traversal path
name, such as tireArray or currentTires.
In C++ code, create a data member for the traversal path name,
such as tireArray or currentTires.
The following are several examples.
Tire tireArray[4]; //C++ array
List <Tire> currentTires; //C++
template class
A programmer may add and remove part objects from
the collection of part objects. For example, the programmer may
insert tire1 and tire2
to the collection. He may remove tire1 and tire2 from the collection.
Listing C++ code listing
showing 1 to many aggregation.
//////////////////////////Car.h file////////////////////////////////////
#ifndef __CAR_H
#define __CAR_H
#ifndef __TIRE_H
#include "Tire.h"
#endif
const int maxNumberOfTires = 4;
class Car
{ int speed; //Attribute data member
int currentTiresIndex; //Index for array of 1:M
aggregation part objects
//1:M aggregation part data member
//Change C array to C++ collection class with iterator
Tire currentTires [maxNumberOfTires ];
public:
Car () : speed(0)
{ currentTiresIndex = 0; //Index for array of 1:M aggregation part objects
}
void start () ;
~ Car ( ) { } //Destructor
};
#endif
//////////////////////////Tire.h file////////////////////////////////////
#ifndef __TIRE_H
#define __TIRE_H
class Tire
{ int pressure; //Attribute data member
public:
Tire () : pressure(0) { }
void checkTirePressure () ;
~ Tire ( ) { } //Destructor
};
#endif
//////////////////////////Car.cpp file////////////////////////////////////
#include "Car.h"
void Car::start()
{ int i = 0;
for ( i = 0; i < maxNumberOfTires; i++) currentTires[i].checkTirePressure();
}
//////////////////////////Tire.cpp file////////////////////////////////////
#include "Tire.h"
void Tire::checkTirePressure() {
}
//////////////////////////main.cpp file////////////////////////////////////
#include "Car.h"
int main ()
{
Car car1; //invokes the default constructor
car1.start();
return 0;
}
To generate the C++ class declaration with data member
declarations, we used two scripts. A class declaration script
generated the Car.h file and the function definition script generated
the Car.cpp file. We created the main.cpp manually. The two scripts
are shown below.
//////////////////////////TRUNCATE_EIGHT$CLASS_NAME.h
file////////////////////////////////////
#ifndef __$CAPITALIZE_ALL$TRUNCATE_EIGHT$CLASS_NAME$_H
#define __$CAPITALIZE_ALL$TRUNCATE_EIGHT$CLASS_NAME$_H
[
#ifndef __$CAPITALIZE_ALL$TRUNCATE_EIGHT$AGGREGATION_MANY_CLASS$_H
#include "TRUNCATE_EIGHT$AGGREGATION_MANY_CLASS$.h"
#endif
]
NO_OUTPUT_BEGIN Use the the CLASS_USER fields for typedef, enum, const declarations, e.g.
const int maxNumberOfTires = 4; NO_OUTPUT_END
CLASS_USER1
CLASS_USER2
class CLASS_NAME[NO_RETURN NO_REPEAT: NO_REPEAT public BASE_CLASS ,DELETE_LAST_SYMBOL]
{ [CPP_ATTRIBUTE_STATIC CPP_ATTRIBUTE_CONSTANT
ATTRIBUTE_TYPE ATTRIBUTE_NAME$; //Attribute data member]
[int AGGREGATION_MANY_NAME$Index; //Index for
array of 1:M aggregation part objects]
[ //1:M aggregation part data member
//Change C array to C++ collection class with iterator
AGGREGATION_MANY_CLASS AGGREGATION_MANY_NAME
LITERAL_SYMBOL[maxNumberOf$AGGREGATION_MANY_CLASS$s LITERAL_SYMBOL];]
[int ASSOCIATION_MANY_NAME$Index; //Index for
array of 1:M association objects]
public:
CLASS_NAME ()
SELECT_WHEN LOGICAL_NOT ATTRIBUTE_IS_STATIC [NO_RETURN NO_REPEAT: ATTRIBUTE_NAME(ATTRIBUTE_INITIAL_VALUE),DELETE_LAST_SYMBOL]
{ [ASSOCIATION_ONE_NAME = &null$ASSOCIATION_ONE_CLASS; //Initialization to null association object ]
[AGGREGATION_MANY_NAME$Index = 0; //Index for array of 1:M aggregation part objects ]
}
[ CPP_OPERATION_VIRTUAL CPP_OPERATION_STATIC OPERATION_RETURN_TYPE OPERATION_NAME (CPP_OPERATION_PARAMETERS) CPP_OPERATION_CONSTANT CPP_OPERATION_PURE_VIRTUAL;
]
OPERATION_CPP_VIRTUAL_BASE_CLASS ~ CLASS_NAME ( ) { } //Destructor
};
#endif
//////////////////////////TRUNCATE_EIGHT$CLASS_NAME.cpp file////////////////////////////////////
#include "TRUNCATE_EIGHT$CLASS_NAME.h"
[
OPERATION_RETURN_TYPE CLASS_NAME::OPERATION_NAME(CPP_OPERATION_PARAMETERS) CPP_OPERATION_CONSTANT
{ OPERATION_CODE
}
]
Key aspects of the class declaration are shown on
the class diagrams below. Key aspects of the class declaration
are shown on the left. This information class diagram shows that
an aggregation assembly class declaration should have an include
statement for the aggregation part class header file, a class
name, data members, traversal data members collection holding
aggregation part objects, a constructor, traversal function members
to access aggregation part object, traversal data member accessor
functions, function members, and a destructor. This information
class diagram shows that the aggregation assembly class is connected
to the aggregation part class with the traversal data member collection.
The traversal data member collection is used to access the aggregation
part objects. The class diagram on the right shows sample C++
identifiers that are also in the code listings.
Key aspects of coding a 1 to many aggregation assembly class are listed below. The aggregation assembly class is responsible to create and delete aggregation part objects.
- Declare an #include compiler directive to include the aggregation part class, e.g. #include "Tire.h". If required include the collection class holding aggregation part objects.
- Declare an aggregation part data member - array or collection object to implement to 1 to many aggregation relationship, e.g. Tire currentTires [maxNumberOfTires];
- Define a default constructor to initialize an object with default data member values. Array of aggregation part objects is initialized with default values. Car (){ }
- Define a constructor with arguments to initialize an object with passed values. Optionally pass a pointer to an array or collection object and assign the array or collection object. Optional iterator. Car (int aSpeed, Tire* pTires) { currentTires [0] = *pTires; }
- Define a copy constructor to initialize an object with a copy of another object. Must assign the array or collection object. Optional iterator. Car (const Car& aCar)
- Define an assignment operator (operator=) to assign one object to another. Must assign the array or collection object. Optional iterator. Car& operator= (const Car& aCar) {for (i = 0; i < maxNumberOfTires; i++) currentTires [i] = aCar.currentTires [i]; }
- Define an equality operator (operator==) to compare two objects. Must compare the array or collection object part. Optional iterator.
int operator== (const Car& aCar)
{ return ((currentTires [0] == aCar.currentTires [0])&& ;
(currentTires [1] == aCar.currentTires [1])) }
Note to compare two arrays == must be defined for array. Otherwise use element by element comparison.
- Define input function - C++ insertion operator for use with cin. Must invoke the aggregation part object operator>>. Iterator required. friend istream& operator>> (istream& is, Car& aCar) {for (i = 0; i < maxNumberOfTires; i++) is >> aCar.currentTires [i]; }
- Define the output function - C++ extraction operator for use with cout. Must invoke the aggregation part object operator<<. Iterator required. friend ostream& operator<< (ostream& os, Car& aCar) { for (i = 0; i < maxNumberOfTires; i++) os << aCar.currentTires [i]; }
- Define a get and set function for the collection of aggregation part objects, e.g. Tire* getCurrentTire () {return ¤tTires[0];}
- Define getFirst and exists assessor functions for individual aggregation part objects, e.g. Tire& getFirstTire () {return currentTires[0];}
- Define a default destructor to deinitialize an object, ~Car () {}
- Define a main (test) function to create an aggregation
assembly object with part objects and invoke a traversal function.
In the aggregation part class, optionally declare
an inverse traversal data member, e.g. Car * aCar;
Prior to compiling the aggregation part class, insert a forward
declaration before the aggregation part class declaration, e.g.
class Car.
The complete C++ code listing is below.
Listing C++ Code Listing
for 1 to M Aggregation
// Class: Car //ANSI C++
#ifndef __CAR_H //Required for Car class
#define __CAR_H
#ifndef __IOSTREAM_H //Required for cin and cout
#include <iostream.h>
#endif
#ifndef __CSTRING_H //Required for CString class
#include <CString.h>
#endif
//Required for 1:M aggregation (part) classes
#ifndef __TIRE_H //Tire
#include "Tire.h"
#endif
const int maxNumberOfTires = 4;
class Car
{ int speed; //Attribute data member
int currentTiresIndex; //Index for array of 1:M
aggregation part objects
//1:M aggregation part data member
//Change C array to C++ collection class with iterator
Tire currentTires [maxNumberOfTires ];
public:
//Default constructor alternative to compiler provided default constructor
//Ensure correct initial values
//Initialization list has members in the order declared
//Association object data member pointers initialized to null association object
Car ();
//Constructor with arguments
//Update to argument list to initialize base class data members,
//e.g. (int aNumber) : BaseClass (aNumber)
Car ( int aspeed ) ;
//Copy Constructor
Car (const Car& aCar );
//Operator= Assignment Operator
Car& operator= (const Car& aCar);
//Operator== Equality Operator
int operator== (const Car& aCar) const;
//Operator<< for cout
friend ostream& operator<< (ostream&
os, const Car& aCar);
//Operator>> for cin
friend istream& operator>> (istream&
is, Car& aCar);
//Get accessor function for non-static attribute data member
int getspeed() const
{ return speed;
}
//Set accessor function for non-static attribute data member
void setspeed (const int aspeed)
{ speed = aspeed;
}
//Get accessor function for 1:M aggregation collection
const Tire* getTireCollection () const ;
//Set accessor function for 1:M aggregation collection
void setTireCollection (Tire* const acurrentTiresCollection);
//Get accessor function for 1:M aggregation part data member
const Tire& getFirstTire() const ;
//Exists function for 1:M aggregation part data member
int existsTire (const Tire& aTire) const
;
void start () ;
~ Car ( ) { } //Destructor - Delete any pointer data members that used new in constructors
//Destructor should be virtual if and only if class contains at least one virtual function
//Objects destroyed in the reverse order of the construction order
};
#endif
//////////////////////////.cpp file/////////////////////////////////////////////////////
#include "Car.h"
void Car::start()
{
}
//Default constructor alternative to compiler provided default constructor
Car::Car ()
: speed(0)
{ currentTiresIndex = 0; //Index for array of 1:M aggregation part objects
}
//Constructor with arguments
//Update to argument list to initialize base class data members and const data members
//e.g. (int aNumber) : BaseClass (aNumber) , constMinimumValue (aMinimumValue)
Car::Car ( int aspeed )
{ speed = aspeed; //Initialization of attributes
currentTiresIndex = 0; //Index for array of 1:M aggregation part objects
}
//Copy constructor alternative to compiler provided default copy constructor
Car::Car (const Car& aCar )
{int i = 0;
currentTiresIndex = 0; //Index for array of 1:M aggregation part objects
speed = aCar.speed;
for ( i = 0; i < maxNumberOfTires; i++) currentTires [i ] = aCar.currentTires [i ] ;
}
//Operator= Assignment Operator alternative to compiler provided operator=
Car& Car::operator= (const Car& aCar)
{ if (this == &aCar) return *this;
int i = 0;
speed = aCar.speed;
for (i = 0; i < maxNumberOfTires; i++) currentTires [i ] = aCar.currentTires [i ] ;
return *this;
}
//Operator== Equality Operator
int Car::operator== (const Car& aCar) const
{ return (
//Equality check for 1:M aggregation parts
//Update for the correct number of aggregation part objects
( currentTires [0 ] == aCar.currentTires [0 ])&&
( currentTires [1 ] == aCar.currentTires [1 ])&&
( currentTires [2 ] == aCar.currentTires [2 ])&&
( currentTires [3 ] == aCar.currentTires [3 ])&&
//Equality check for attribute data members
(speed == aCar.speed)
);
}
//Operator<< extraction for cout
ostream& operator<< (ostream& os, const Car& aCar)
{ int i = 0;
os << "Object Attribute Values - Class Car" << endl;
os << "speed: " << aCar.speed << endl;
return os;
}
//Operator>> insertion for cin
istream& operator>> (istream& is, Car& aCar)
{ int i = 0;
cout << "\nEnter Object Attribute Values or 0 - Class Car";
cout << "\nEnter speed : " << endl;
is >> aCar.speed;
for (i = 0; i < maxNumberOfTires; i++) is >> aCar.currentTires [i ] ;
return is;
}
//Get accessor function for 1:M aggregation collection
const Tire* Car::getTireCollection () const
{ return currentTires ;
}
//Set accessor function for 1:M aggregation collection
void Car::setTireCollection (Tire* const aTireCollection)
{ for (int i = 0; i < maxNumberOfTires; i++) currentTires [ i ] = aTireCollection [ i ];
}
//Exists function for 1:M aggregation part data member
int Car::existsTire (const Tire& aTire) const
{ for (int i = 0; i < maxNumberOfTires; i++)
{ if ( currentTires [i ] == aTire ) return 1;
}
return 0;
}
//Get accessor function for 1:M aggregation part data member
const Tire& Car::getFirstTire() const
{ return currentTires [ 0 ] ;
}
Sometimes it is necessary to have relationships in two directions both in a forward direction and an inverse direction. Use attach and detach operations to provide access from the assembly to part objects. Additionally, attach and detach operations may be used to provide access from a part object to the assembly object for the inverse relationship. The figure below and listing below show an aggregation relationship between the Car class and the Motor class. There is an association relationship between the Motor class and the Car class. There is an inverse association relationship between the Car class and the Transmission class.
Listing C++ code implementing
aggregation and association relationships.
//Single file cartrans.cpp
#include "iostream.h"
class Car; //forward reference
class Transmission; //forward reference
------------------------Motor Class --------------------------------
class Motor {
Car *aCar;
Transmission *aTransmission;
public:
void operate () ;
void attachToCar (Car ¤tCar);
void attachToTransmission (Transmission ¤tTransmission);
};
void Motor::operate () {int aNumber = aCar->getCarID ();
cout << "The car number is " << aNumber << ".\n";
cout << "The motor is operating.\n";
aTransmission -> operate ();}
void Motor::attachToCar (Car ¤tCar)
{ aCar = ¤tCar;}
void Motor::attachToTransmission (Transmission ¤tTransmission)
{aTransmission = ¤tTransmission;}
------------------Transmission Class -------------------------------
class Transmission {
public:
void operate () {cout << "The transmission is operating.\n";};
};
-------------------------Car Class----------------------------------
class Car {
int carID;
Motor currentMotor;
Transmission aTransmission;
public:
int getCarID () {return carID;}
Car ();
void operate ();
};
Car::Car () {carID = 0;}
void Car::operate () {currentMotor.attachToCar (*this);
currentMotor.attachToTransmission (aTransmission);
currentMotor.operate (); }
---------------------------Main Function----------------------------
int main () {
Car car1;
car1.operate ();
return 0;
}
//Sample Output
//The car ID is 0.
//The motor is operating.
//The transmission is operating.
How we model aggregation can affect the quality of
the software in terms of correctness, reliability, extendibility,
and reusability. For example, the use of aggregation leads to
"black box" modules that can be easily modified or changed
out without affecting other modules. This improves the extendibility
of the software. Aggregation supports encapsulation in which an
assembly object hides the part objects.
The use of aggregation with propagation of operations
is a software quality technique. It supports the creation of understandable,
extendible software. One of the important aspects of aggregation
is the automatic propagation (triggering) of operations from an
assembly object to a part object. Once we have identified the
operations in an assembly class, then these named operations may
be identified in the part classes. The assembly object acts as
a boss telling the part objects, the workers, what to do. Actual
work and calculations are done in part objects.
Aggregation supports strong cohesion. The assembly object groups together related part objects. All the part objects support the purpose of the assembly object. We can check to ensure that there are no excessive or out-of-place part objects.
In O-O modeling it is important to identify and describe relationships between classes and between objects. A relationship is a link or connection between classes or between objects. The association relationship is the "has a" relationship. The aggregation relationship is the "part of" relationship. Aggregation is a special form of association with special semantics (rules) for deletion and propagation of operations. Relationships are shown on the class diagram. Relationships may be specified in a relationship specification. Using a CASE tool, a class diagram showing relationships between classes may be created. The CASE tool may generate source code to implement the classes and the relationships.