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(); } |