Selected C++ Example #5
//Example #5
// This example with its five parts will illustrate how a
// uses relationship may be implemented. The most common
// method of implementing the uses relationship is via
// containment. Containment relationships always imply
// uses, but uses do not always imply containment. The second
// example illustrates uses via containment. How does a Meal
// know the name of its Melon object when it wants to use
// it? Answer: It knows its name because it contains it, i.e.,
// Meals contain Melons.
// This brings up an interesting point. In a design
// discussion, if a member of the design team states that
// some class X uses class Y, always ask, ''How does the X
// object know the name of the Y object?''. While the answer is
// often, ''Because X contains the Y object,'' there are five
// other possibilities. We will discuss these in the context
// of a car using a gas station.
// How does the Car object know the name of the GasStation
// object? It clearly doesn't contain it; Cars do not contain
// GasStations.
// The first, and most popular, method is that the Car object
// is given the GasStation object as an explicit parameter to
// its method. This is illustrated by the Car1 class.
// The second method is for all Car objects to go to the
// same global GasStation. They know its name by hardcoding
// it into the Car's method(s). Some consider this to be
// a special case of the first method since global variables
// are really implicit parameters. Because it has a different
// form, we implement it in the class Car2.
// The third method is for the wealthy. When their Car runs
// out of gasoline, they build a GasStation, pump gas, and
// tear down the gas station. While this is ridiculous in
// the domain of cars and gas stations, it is appropriate in
// many domains. The Car3 class captures this method of using
// local objects.
// The fourth method is that when a car is born, god gives
// it its gas station for later use. Each car has a one-to-one
// association with its gas station. We will talk more about
// associations in Chapter 7, but this serves as a good example
// of the implementation of associations. Do not confuse
// an association with containment by reference. The
// relationships are very different at design time, however,
// C++ does not distinguish them. The class Car4 implements
// this feature.
// The fifth method is for the Car class to ask a third-party
// object for a gas station. Of course, this only postpones
// the problem. How did we know the name of the third-party
// object? It must be one of the other five methods: i.e.,
// containment, passed as an argument, a global object,
// a local object, an association, or ... ask a fourth-
// party object ... (ad nauseum).
// The Car5 class illustrates this implementation of uses by
// asking a contained map object for a gas station.
#include <iostream.h>
// The GasStation is the same class in all five methods of
// implementing uses relationships. The only interest is how
// we gain knowledge of the name of a particular gas station
// we want to use. Our gas station has a constructor; it
// can take gas deliveries, it can change its price (no grades
// of gasoline here, for simplification), and most
// importantly, it can dispense gasoline to anyone willing
// to pay. In our case, Car objects.
class GasStation{
private:
double quantityOfGas;
double price;
public:
GasStation(double, double quantity=0.0);
void take_gas_delivery(double);
void change_price(double);
double give_gasoline(double);
};
GasStation::GasStation(double start_price, double quantity)
{
quantityOfGas = quantity;
price = start_price;
}
void
GasStation::take_gas_delivery(double quantity)
{
quantityOfGas += quantity;
}
void
GasStation::change_price(double new_price)
{
price = new_price;
}
double
GasStation::give_gasoline(double amount)
{
double gas_purchased;
gas_purchased = amount / price;
quantityOfGas -= gas_purchased;
if (quantityOfGas < 0) {
gas_purchased += quantityOfGas;
quantityOfGas = 0.0;
}
return(gas_purchased);
}
// The Car1 class implements the first method of implementing
// a uses relationship which is not containment. The Car's
// get_gasoline method takes not only money, but also a
// GasStation object, as an argument.
class Car1 {
private:
double milesPerGallon;
double fuelCapacity;
double gasInTank;
double mileage;
public:
Car1(double, double, double=0.0, double=0.0);
void get_gasoline(GasStation&, double);
void drive(double);
void print();
};
Car1::Car1(double mpg, double capacity, double starting_gas,
double miles)
{
milesPerGallon = mpg;
fuelCapacity = capacity;
gasInTank = starting_gas;
mileage = miles;
}
// The drive method computes the gasoline needed to travel the
// desired distance. It then checks to see if there is enough
// gasoline in the car. If not, a message is printed to the user and
// the car moves as far as it can. If the car has less than 10% of its
// remaining capacity, a warning is printed to the user.
void
Car1::drive(double miles)
{
double gas_needed;
gas_needed = miles / milesPerGallon;
if (gas_needed > gasInTank) {
mileage += gasInTank * milesPerGallon;
cerr << ''You ran out of gasoline after travelling '';
cerr << gasInTank * milesPerGallon << '' miles.\n'';
gasInTank = 0.0;
}
else {
mileage += miles;
gasInTank -= gas_needed;
if (gasInTank < 0.1 * fuelCapacity) {
cerr << ''Warning: You have only enough gas
to go '';
cerr << GasInTank * miles_per_gallon << ''
miles.\n'';
}
}
}
// The get_gasoline method is the most interesting. In this
// implementation the Car class knows the name of its gas station
// because it is passed into the method. The gas station is used to
// get the gasoline, and a number of checks are made to be sure the
// gas station wasn't out of gas or that the user didn't spill some
// gas. Of course, this implementation could be made more
// elaborate but this will suffice to demonstrate the
// implementation of ''Car uses GasStation. ''
void
Car1::get_gasoline(GasStation& myGasStation, double money)
{
double gas_received;
gas_received = myGasStation.give_gasoline(money);
if (gas_received == 0) {
cerr << ''Sorry the gas station was out of gas.\n'';
}
else {
gasInTank += gas_received;
if (gasInTank > fuelCapacity) {
cerr << ''You spilled '' << (gasInTank -
fuelCapacity);
cerr << '' gallons of gas on the ground.\n'';
gasInTank = fuelCapacity;
}
}
}
void
Car1::print()
{
cout << ''The car gets '' << miles_per_gallon;
cout << '' miles per gallon.\nIt currently has '';
cout << gasInTank << '' gallons of gasoline on board.\n'';
cout << ''Its maximum fuel capacity is '' << fuelCapacity;
cout << '' gallons\nand it has '' << mileage << '' miles.\n''
}
// The Car2 class demonstrates the implementation of a uses
// relationship through a global GasStation object. In this case,
// all Car2 objects go to the same GasStation, in this case
// ''Global_GasStation.'' Some designers consider this a special
// case of implementing the uses relationship through a parameter
// (e.g., Car1), but since it looks different in implementation, I
// felt it should be included.
GasStation Global_GasStation(1.25, 10000);
class Car2 {
private:
double milesPerGallon;
double fuelCapacity;
double gasInTank;
double mileage;
public:
Car2(double, double, double=0.0, double=0.0);
void get_gasoline(double);
void drive(double);
void print();
};
Car2::Car2(double mpg, double capacity, double starting_gas,
double miles)
{
milesPerGallon = mpg;
fuelCapacity = capacity;
gasInTank = starting_gas;
mileage = miles;
}
void
Car2::drive(double miles)
{
double gas_needed;
gas_needed = miles / milesPerGallon;
if (gas_needed > gasInTank) {
mileage += gasInTank * milesPerGallon;
cerr << ''You ran out of gasoline after travelling '';
cerr << gasInTank * milesPerGallon << '' miles.\n'';
gasInTank = 0.0;
}
else {
mileage += miles;
gasInTank -= gas_needed;
if (gasInTank < 0.1 * fuelCapacity) {
cerr << ''Warning: You have only enough gas
to go '';
cerr << GasInTank * milesPerGallon << ''
miles.\n'';
}
}
}
// Note the use of the Global_GasStation object to get_gasoline.
void
Car2::get_gasoline(double money)
{
double gas_received;
gas_received = Global_GasStation.give_gasoline(money);
if (gas_received == 0) {
cerr << ''Sorry the gas station was out of gas.\n'';
}
else {
gasInTank += gas_received;
if (gasInTank > fuelCapacity) {
cerr << ''You spilled '' << (gasInTank -
fuelCapacity);
cerr << '' gallons of gas on the ground.\n'';
gasInTank = fuelCapacity;
}
}
}
void
Car2::print()
{
cout << ''The car gets '' << milesPerGallon;
cout << ''miles per gallon.\nIt currently has '';
cout << gasInTank << '' gallons of gasoline on board.\n'';
cout << ''Its maximum fuel capacity is '' << fuelCapacity;
cout << ''gallons\nand it has'' << mileage << '' miles.\n'';
}
// The Car3 class implements its uses relationship through a local
// object that is built ''on-the-fly'' at runtime. Whenever the
// get_gasoline() method is called on Car3 objects, the method
// builds itself a gas station, uses the gas station, and then
// destroys it.This is inefficient but is used in some
// implementations,
class Car3 {
private:
double milesPerGallon;
double fuelCapacity;
double gasInTank;
double mileage;
public:
Car3(double, double, double=0.0, double=0.0);
void get_gasoline(double);
void drive(double);
void print();
};
Car3::Car3(doublempg, double capacity, double starting_gas,
double miles)
{
milesPerGallon = mpg;
fuelCapacity = capacity;
gasInTank = starting_gas;
mileage = miles;
}
void
Car3::drive(double miles)
{
double gas_needed;
gas_needed = miles / milesPerGallon;
if (gas_needed > gasInTank) {
mileage += gasInTank * milesPerGallon;
cerr << ''You ran out of gasoline after travelling '';
cerr << gasInTank * milesPerGallon << '' miles.\n'';
gasInTank = 0.0;
}
else {
mileage += miles;
gasInTank -= gas_needed;
if (gasInTank < 0.1 * fuelCapacity) {
cerr << ''Warning: You have only enough gas
to go '';
cerr << gasInTank * milesPerGallon <<
'' miles.\n'';
}
}
}
// Note the creation and use of local_station in order to get
// gasoline for the car. While a bit silly in the domain of cars and
// gas stations, there are domains where the use of a local object
// as part of the implementation of a method is perfectly
// appropriate.
void
Car3::get_gasoline(double money)
{
double gas_received;
GasStationlocal_station(1.25, 1000);
gas_received = local_station.give_gasoline(money);
if (gas_received == 0) {
cerr << ''Sorry the gas station was out of gas.\n'';
}
else {
gasInTank += gas_received;
if (gasInTank > fuelCapacity) {
cerr << ''You spilled '' << (gasInTank -
fuelCapacity);
cerr << '' gallons of gas on the ground.\n'';
gasInTank = fuelCapacity;
}
}
}
void
Car3::print()
{
cout << ''The car gets '' << milesPerGallon;
cout << '' miles per gallon.\nIt currently has '';
cout << gasInTank << '' gallons of gasoline on board.\n'';
cout << ''Its maximum fuel capacity is '' << fuelCapacity;
cout << ''gallons\nand it has'' << mileage << ''miles.\n'';
}
// The Car4 class uses a different twist in implementing its uses
// relationship. When each Car4 object is built, it is told who its
// gas station is. This gas station is stored in the Car4 object for
// later use. In this case, the class will make a copy of the gas
// station, which is safer because it avoids problems of the gas
// station given to the Car4 object being destroyed before the Car4
// object. If data sharing is desired, then only the pointer,
// and not the stuff to which it points, should be copied. For an
// example of this form of shallow copying which also provides a
// safety mechanism, see the Air Traffic Controller example in
// Chapter 9 (Example #3). Do not confuse association with
// containment by reference. While the Car4 class does have a
// pointer to a GasStation as a data member, this is not containment
// by reference, it is association through a referential attribute.
// While C++ does not let us distinguish these two relationships,
// the distinction is available AND important to designers. If this
// were containment, we could ignore GasStations at some high
// level of design. The fact that it is assocation means we cannot.
class Car4 {
private:
double milesPerGallon;
double fuelCapacity;
double gasInTank;
double mileage;
GasStation* myStation;
public:
Car4(GasStation*, double, double, double=0.0, double=0.0);
~Car4();
void get_gasoline(double);
void drive(double);
void print();
};
// Note the constructor copying the GasStation passed to it using
// the default copy constructor for GasStation. (Note: GasStation
// is a fixed-sized class so the default copy constructor does not
// cause any memory leakage/heap corruption problems. See Appendix B
// for a more thorough explanation of memory leakage/heap corruption
// problems of copy constructors/assignment operators.
Car4::Car4(GasStation* station, double mpg, double capacity,
double starting_gas, double miles)
{
myStation = new GasStation(*station);
milesPerGallon = mpg;
fuelCapacity = capacity;
gasInTank = starting_gas;
mileage = miles;
}
Car4::~Car4()
{
delete myStation;
}
void
Car4::drive(double miles)
{
double gas_needed;
gas_needed = miles / milesPerGallon;
if (gas_needed > gasInTank) {
mileage += gasInTank * milesPerGallon;
cerr << ''You ran out of gasoline after travelling '';
cerr << gasInTank * milesPerGallon << '' miles.\n'';
gasInTank = 0.0;
}
else {
mileage += miles;
gasInTank -= gas_needed;
if (gasInTank< 0.1* fuelCapacity) {
cerr << ''Warning: You have only enough gas
to go '';
cerr << gasInTank * milesPerGallon <<
'' miles.\n'';
}
}
}
// Note the use of the referential attribute in the get_gasoline method.
void
Car4::get_gasoline(double money)
{
double gas_received;
gas_received = myStation->give_gasoline(money);
if (gas_received == 0) {
cerr << ''Sorry the gas station was out of gas.\n'';
}
else {
gasInTank += gas_received;
if (gasInTank > fuelCapacity) {
cerr << ''You spilled '' << (gasInTank -
fuelCapacity);
cerr << '' gallons of gas on the ground.\n'';
gasInTank = fuelCapacity;
}
}
}
void
Car4::print()
{
cout << ''The car gets '' << milesPerGallon;
cout << '' miles per gallon.\nIt currently has '';
cout << gasInTank << '' gallons of gasoline on board.\n'';
cout << ''Its maximum fuel capacity is '' << fuelCapacity;
cout << '' gallons\nand it has '' << mileage << '' miles.\n'';
}
// The Car5 class implements its uses relationship by asking a
// third-party class, in this case a Map object. Of course, asking
// a third-party only postpones the answer to the question, ''How
// does a class know the name of the object it wishes to use? ''We
// will have to use one of the other five methods of implementing
// uses. In this case, I chose containment, i.e., Car 5 contains a
// Map object.
// We first implement our Map class. I have chosen a naive algorithm
// for finding a GasStation on a Map. Each Map object has four
// quadrants and a car has an x, y location. The map takes the x-
// and y-coordinates of a car and returns one of four stations.
// While it is true that this is naive (the attentive will notice
// that a Map has no way of getting gasoline deliveries to its
// GasStations), it is sufficient to demonstrate uses.
class Map {
private:
GasStation* quadrant[4];
public:
Map();
~Map();
GasStation* get_station(int, int);
};
// The constructor for Map simply builds the four GasStations , one
// for each quadrant.
Map::Map()
{
quadrant[0] = newGasStation(1.45, 1000);
quadrant[1] = newGasStation(1.30, 200);
quadrant[2] = newGasStation(2.10, 10000);
quadrant[3] = newGasStation(1.10, 678);
}
Map::~Map()
{
int i;
for (i=0; i < 4; i++) {
delete quadrant[i];
}
}
// When a Car5 object asks the Map for a station, it gives the Map its
// x- and y-coordinates. The Map returns the appropriate
// GasStation.
GasStation*
Map::get_station(int x, inty)
{
if (x > 0) {
if (y > 0)
return(quadrant[0]);
else
return(quadrant[3]);
}
else {
if (y > 0)
return(quadrant[1]);
else
return(quadrant[2]);
}
}
// The Car5 class contains the Map by value. Even if it contained it
// by reference, this would still be a containment relationship.
// It would not be association through a referential attribute
// like the Car4 class. The difference is significant at design time.
// I can state that Maps are not as important a class in this domain
// as Car5 and GasStation objects because the former is contained in
// a top-level class and does not have to be discussed at high-level
// design time.
class Car5 {
private:
int loc_x, loc_y;
double milesPerGallon;
double fuelCapacity;
double gasInTank;
double mileage;
Map myMap;
public:
Car5(int, int, double, double, double=0.0, double=0.0);
void get_gasoline(double);
void drive(double);
void print();
};
// The constructor for Map is called automatically before the
// constructor for each Car5 object. The destructor for Map will
// likewise be called whenever a Car5 object is destroyed.
Car5::Car5(int x, int y, double mpg, double capacity,
double starting_gas, double miles)
{
loc_x = x;
loc_y = y;
milesPerGallon = mpg;
fuelCapacity = capacity;
gasInTank = starting_gas;
mileage = miles;
}
void
Car5::drive(double miles)
{
double gas_needed;
gas_needed = miles / milesPerGallon;
if (gas_needed > gasInTank) {
mileage += gasInTank * milesPerGallon;
cerr << ''You ran out of gasoline after travelling '';
cerr << gasInTank * milesPerGallon << '' miles.\n'';
gasInTank = 0.0;
}
else {
mileage += miles;
gasInTank -= gas_needed;
if (gasInTank < 0.1 * fuelCapacity) {
cerr << ''Warning: You have only enough gas
to go '';
cerr << gasInTank * milesPerGallon <<
''miles.\n'';
}
}
}
// Note the use of the contained, third-party object called MyMap to
// get a GasStation for use by the get_gasoline method.
void
Car5::get_gasoline(double money)
{
double gas_received;
GasStation *myStation = myMap.get_station(loc_x, loc_y);
gas_received = myStation->give_gasoline(money);
if (gas_received == 0) {
cerr << ''Sorry the gas station was out of gas.\n'';
}
else {
gasInTank += gas_received;
if (gasInTank>fuelCapacity) {
cerr << ''You spilled '' << (gasInTank -
fuelCapacity);
cerr << '' gallons of gas on the ground.\n'';
gasInTank = fuelCapacity;
}
}
}
void
Car5::print()
{
cout << ''The car at location ('' << loc_x << '', '' << loc_y <<
'') gets '';
cout << milesPerGallon << '' miles per gallon.\nIt currently
has '';
cout << gasInTank << '' gallons of gasoline on board.\n'';
cout << ''Its maximum fuel capacity is '' << fuelCapacity;
cout << '' gallons\nand it has '' << mileage << '' miles.\n''
}
void
main()
{
// The following code tests the Car1 class :
GasStation g1(1.18, 300), g2(1.45, 2300);
Car1 mycar1(22, 18, 10);
mycar1.print();
mycar1.drive(200);
mycar1.print();
mycar1.drive(100);
mycar1.print();
mycar1.get_gasoline(g1, 15.00);
mycar1.print();
// The following code tests the Car2 class :
Car2 mycar2(30, 20, 3);
mycar2.print();
mycar2.drive(200);
mycar2.print();
mycar2.get_gasoline(20.00);
mycar2.print));
mycar2.get_gasoline(10.00);
mycar2.print();
// The following code tests the Car3 class:
Car3 mycar3(9, 13, 5);
mycar3.print();
mycar3.drive(38);
mycar3.print();
mycar3.get_gasoline(10.00);
mycar3.print();
mycar3.drive(150);
mycar3.print();
// The following code tests the Car4 class:
Car4 mycar4(&g2, 18, 25, 12);
mycar4.print();
mycar4.get_gasoline(15.00);
mycar4.print();
mycar4.drive(150);
mycar4.print();
mycar4.get_gasoline(3.69);
mycar4.print();
// The following code tests the Car5 class:
Car5mycar5(10, -34, 35, 18, 15);
myCar5.print();
myCar5.drive(250);
mycar5.print();
myCar5.drive(250);
myCar5.print();
myCar5.get_gasoline(10.00);
myCar5.print();
myCar5.get_gasoline(20.00);
mycar5.print();
}
|