| < Day Day Up > |
|
This section presents an extended code example that illustrates the use of dynamic polymorphic behavior in system design. My goal here is to show you how designing with dynamic polymorphism in mind enables you to treat classes of objects and sub objects orthogonally.
Figure 16-4 gives the UML class diagram for the aircraft engine components model originally presented in chapter 12.
Figure 16-4: Original Aircraft Engine Components Model
In this model, the Engine class contained various types of engine components, each declared in a separate class with no relation to each other although they all declared a similar public interface. We can capitalize on the concept of interface similarity by rethinking the design in terms of dynamic polymorphic behavior. Contrast and compare this model with the UML class diagram presented in figure 16-5.
Figure 16-5: UML Class Diagram Showing Polymorphic Engine Components
Referring to figure 16-5, the IComponent class sits at the top of the inheritance hierarchy and provides a set of pure virtual interface functions. Class Component inherits directly from IComponent and implements some of the generalized behavior common to all engine components. Note that Component is an abstract base class because although it inherits the getComponentType() virtual function from IComponent it leaves its implementation to its subclasses.
The Pump and Sensor classes inherit from Component and extend its behavior accordingly. Pump and Sensor are abstract base classes because they inherit getComponentType() from Component but leave its implementation to their subclasses.
The classes WaterPump, OilPump, FuelPump, AirFlowSensor, OxygenSensor, and TemperatureSensor are the concrete components that will actually be instantiated when it comes time to construct a SmallEngine object.
The Engine class is an abstract base class that stands alone to provide an interface for its derived classes. In this example, there is only one Engine subclass named SmallEngine. The SmallEngine class contains three Pumps and three Sensors. By using base class pointers of type Pump and Sensor the design of the SmallEngine class is simplified in that all Pump and Sensor behavior can be address via a common interface. Any behavior common to all engine components can be accessed via the IComponent interface.
The code for the revised polymorphic engine component example is given below:
icomponent.h
#ifndef I_COMPONENT_H #define I_COMPONENT_H #include "engineutils.h" #if MESSAGE_TRACE #include <iostream> using namespace std; #endif class IComponent{ public: IComponent(){ #if MESSAGE_TRACE cout<<"IComponent Object Created!"<<endl; #endif } virtual ~IComponent(){ #if MESSAGE_TRACE cout<<"IComponent Object Destroyed!"<<endl; #endif } virtual bool isWorkingProperly() = 0; virtual void setStatus(PartStatus the_status) = 0; virtual PartStatus getStatus() = 0; virtual void registerWithEngineNumber(short the_engine_number) = 0; virtual short getRegisteredEngineNumber() = 0; virtual char* getComponentType() = 0; virtual int getComponentCount() = 0; virtual void enable() = 0; virtual void disable() = 0; }; #endif
component.h
#include "icomponent.h" class Component : public IComponent{ public: Component(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~Component(); virtual void setStatus(PartStatus the_status); virtual PartStatus getStatus(); virtual void registerWithEngineNumber(short the_engine_number); virtual short getRegisteredEngineNumber(); virtual int getComponentCount(); virtual bool isWorkingProperly(); virtual void enable(); virtual void disable(); private: PartStatus its_status; short registered_engine_number; static int component_count; }; #endif
component.cpp
#include "component.h" #include "engineutils.h" int Component::component_count = 0; Component::Component(PartStatus the_status, short the_engine_number):its_status(the_status), registered_engine_number(the_engine_number) { component_count++; #if MESSAGE_TRACE cout<<"Component Object Created!"<<endl; #endif } Component::~Component(){ component_count--; #if MESSAGE_TRACE cout<<"Component Object Destroyed!"<<endl; #endif } void Component::setStatus(PartStatus the_status){ its_status = the_status; } PartStatus Component::getStatus(){ return its_status;} void Component::registerWithEngineNumber(short the_engine_number){ registered_engine_number = the_engine_number; } short Component::getRegisteredEngineNumber(){ return registered_engine_number;} int Component::getComponentCount(){ return component_count;} bool Component::isWorkingProperly(){ bool return_val = false; switch(its_status){ case WORKING : return_val = true; break; case NOTWORKING: break; default: break; } return return_val; } void Component::enable(){ its_status = WORKING;} void Component::disable(){ its_status = NOTWORKING;}
pump.h
#ifndef PUMP_H #define PUMP_H #include "component.h" #include "engineutils.h" class Pump : public Component{ public: Pump(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~Pump(); virtual bool start(); virtual bool stop(); virtual int getPumpCount(); private: static int pump_count; enum PumpStatus {STOPPED, RUNNING}; PumpStatus its_status; }; #endif
pump.cpp
#include "pump.h" #include "engineutils.h" int Pump::pump_count = 0; Pump::Pump(PartStatus the_status, short the_engine_number): Component(the_status, the_engine_number), its_status(STOPPED){ pump_count++; #if MESSAGE_TRACE cout<<"Pump Object Created!"<<endl; #endif } Pump::~Pump(){ pump_count--; #if MESSAGE_TRACE cout<<"Pump Object Destroyed!"<<endl; #endif } bool Pump::start(){ bool return_val = false; PartStatus component_status = getStatus(); switch(component_status){ case WORKING : its_status = RUNNING; return_val = true; break; case NOTWORKING : its_status = STOPPED; break; default : break; } return return_val; } bool Pump::stop(){ bool return_val = false; switch(its_status){ case RUNNING : its_status = STOPPED; return_val = true; break; default : break; } return return_val; } int Pump::getPumpCount(){ return pump_count;}
sensor.h
#ifndef SENSOR_H #define SENSOR_H #include "component.h" #include "engineutils.h" class Sensor : public Component { public: Sensor(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~Sensor(); virtual void calibrate(); virtual void unCalibrate(); virtual void enableSensing(); virtual void disableSensing(); virtual float getReading() = 0; virtual void setReading(float reading) = 0; virtual bool isWorkingProperly(); virtual int getSensorCount(); private: static int sensor_count; enum SensorStatus {SENSING_ENABLED, SENSING_DISABLED}; enum SensorCalibration {UNCALIBRATED, CALIBRATED}; SensorStatus its_status; SensorCalibration its_calibration; }; #endif
sensor.cpp
#include "sensor.h" #include "engineutils.h" int Sensor::sensor_count = 0; Sensor::Sensor(PartStatus the_status, short the_engine_number) : Component(the_status, the_engine_number), its_status(SENSING_ENABLED), its_calibration(CALIBRATED){ sensor_count++; #if MESSAGE_TRACE cout<<"Sensor Object Created!"<<endl; #endif } Sensor::~Sensor(){ sensor_count--; #if MESSAGE_TRACE cout<<"Sensor Object Destroyed!"<<endl; #endif } void Sensor::enableSensing(){its_status = SENSING_ENABLED;} void Sensor::disableSensing(){its_status = SENSING_DISABLED;} void Sensor::calibrate(){ its_calibration = CALIBRATED;} void Sensor::unCalibrate(){its_calibration = UNCALIBRATED;} bool Sensor::isWorkingProperly(){ bool return_val = false; switch(its_calibration){ case CALIBRATED : switch(its_status){ case SENSING_ENABLED : setStatus(WORKING); return_val = true; break; case SENSING_DISABLED : setStatus(NOTWORKING); return_val = false; break; default: break; } case UNCALIBRATED : setStatus(NOTWORKING); disableSensing(); break; default: break; } return return_val; } int Sensor::getSensorCount(){ return sensor_count;}
waterpump.h
#ifndef WATER_PUMP_H #define WATER_PUMP_H #include "pump.h" #include "engineutils.h" class WaterPump : public Pump { public: WaterPump(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~WaterPump(); virtual char* getComponentType(); }; #endif
waterpump.cpp
#include "waterpump.h" #include "engineutils.h" WaterPump::WaterPump(PartStatus the_status, short the_engine_number) : Pump(the_status, the_engine_number){ #if MESSAGE_TRACE cout<<"WaterPump Object Created!"<<endl; #endif } WaterPump::~WaterPump(){ #if MESSAGE_TRACE cout<<"WaterPump Object Destroyed!"<<endl; #endif } char* WaterPump::getComponentType(){ return "Water Pump";}
oilpump.h
#ifndef OIL_PUMP_H #define OIL_PUMP_H #include "pump.h" #include "engineutils.h" class OilPump : public Pump { public: OilPump(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~OilPump(); virtual char* getComponentType(); }; #endif
oilpump.cpp
#include "oilpump.h" #include "engineutils.h" OilPump::OilPump(PartStatus the_status, short the_engine_number) : Pump(the_status, the_engine_number){ #if MESSAGE_TRACE cout<<"OilPump Object Created!"<<endl; #endif } OilPump::~OilPump(){ #if MESSAGE_TRACE cout<<"OilPump Object Destroyed!"<<endl; #endif } char* OilPump::getComponentType(){ return "Oil Pump";}
fuelpump.h
#ifndef FUEL_PUMP_H #define FUEL_PUMP_H #include "pump.h" #include "engineutils.h" class FuelPump : public Pump{ public: FuelPump(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~FuelPump(); virtual char* getComponentType(); }; #endif
fuelpump.cpp
#include "fuelpump.h" #include "engineutils.h" FuelPump::FuelPump(PartStatus the_status, short the_engine_number) : Pump(the_status, the_engine_number){ #if MESSAGE_TRACE cout<<"FuelPump Object Created!"<<endl; #endif } FuelPump::~FuelPump(){ #if MESSAGE_TRACE cout<<"FuelPump Object Destroyed!"<<endl; #endif } char* FuelPump::getComponentType(){ return "Fuel Pump";}
airflowsensor.h
#ifndef AIRFLOW_SENSOR_H #define AIRFLOW_SENSOR_H #include "engineutils.h" #include "sensor.h" class AirFlowSensor : public Sensor { public: AirFlowSensor(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~AirFlowSensor(); virtual char* getComponentType(); virtual void setReading(float reading); virtual float getReading(); private: float its_reading; }; #endif
airflowsensor.cpp
#include "airflowsensor.h" #include "engineutils.h" AirFlowSensor::AirFlowSensor(PartStatus the_status, short the_engine_number) : Sensor(the_status, the_engine_number), its_reading(0){ #if MESSAGE_TRACE cout<<"AirFlowSensor Object Created!"<<endl; #endif } AirFlowSensor::~AirFlowSensor(){ #if MESSAGE_TRACE cout<<"AirFlowSensor Object Destroyed!"<<endl; #endif } char* AirFlowSensor::getComponentType(){ return "AirFlow Sensor";} float AirFlowSensor::getReading(){return its_reading;} void AirFlowSensor::setReading(float reading){ its_reading = reading;}
oxygensensor.h
#ifndef OXYGEN_SENSOR_H #define OXYGEN_SENSOR_H #include "engineutils.h" #include "sensor.h" class OxygenSensor : public Sensor { public: OxygenSensor(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~OxygenSensor(); virtual char* getComponentType(); virtual float getReading(); virtual void setReading(float reading); private: float its_reading; }; #endif
oxygensensor.cpp
#include "oxygensensor.h" #include "engineutils.h" OxygenSensor::OxygenSensor(PartStatus the_status, short the_engine_number) : Sensor(the_status, the_engine_number), its_reading(0){ #if MESSAGE_TRACE cout<<"OxygetSensor Object Created!"<<endl; #endif } OxygenSensor::~OxygenSensor(){ #if MESSAGE_TRACE cout<<"OxygenSensor Object Destroyed!"<<endl; #endif } char* OxygenSensor::getComponentType(){ return "Oxygen Sensor";} float OxygenSensor::getReading(){return its_reading;} void OxygenSensor::setReading(float reading){ its_reading = reading;}
temperaturesensor.h
#ifndef TEMPERATURE_SENSOR_H #define TEMPERATURE_SENSOR_H #include "engineutils.h" #include "sensor.h" class TemperatureSensor : public Sensor { public: TemperatureSensor(PartStatus the_status = WORKING, short the_engine_number = 0); virtual ~TemperatureSensor(); virtual char* getComponentType(); virtual float getReading(); virtual void setReading(float reading); private: float reading; }; #endif
temperaturesensor.cpp
#include "temperaturesensor.h" #include "engineutils.h" TemperatureSensor::TemperatureSensor(PartStatus the_status, short the_engine_number) : Sensor(the_status, the_engine_number), reading(0){ #if MESSAGE_TRACE cout<<"TemperatureSensor Object Created!"<<endl; #endif } TemperatureSensor::~TemperatureSensor(){ #if MESSAGE_TRACE cout<<"TemperatureSensor Object Destroyed!"<<endl; #endif } char* TemperatureSensor::getComponentType(){ return "Temperature Sensor";} float TemperatureSensor::getReading(){ return reading;} void TemperatureSensor::setReading(float reading){ this->reading = reading; }
engine.h
#ifndef ENGINE_H #define ENGINE_H #include "engineutils.h" class Engine { public: Engine(); virtual ~Engine(); short getEngineCount(); short getEngineNumber(); EngineStatus getEngineStatus(); void setEngineStatus(EngineStatus the_status); virtual void enableComponents() = 0; virtual bool checkReady() = 0; virtual bool startEngine() = 0; virtual bool stopEngine() = 0; private: static short engine_count; short engine_number; EngineStatus its_status; }; #endif
engine.cpp
#include "engine.h" #if MESSAGE_TRACE #include <iostream> using namespace std; #endif short Engine::engine_count = 1; Engine::Engine():engine_number(engine_count++), its_status(NOTREADY){ #if MESSAGE_TRACE cout<<"Engine Object Created!"<<endl; #endif } Engine::~Engine(){ #if MESSAGE_TRACE cout<<"Engine Object Destroyed!"<<endl; #endif } short Engine::getEngineCount(){return engine_count;} short Engine::getEngineNumber(){return engine_number;} EngineStatus Engine::getEngineStatus(){return its_status;} void Engine::setEngineStatus(EngineStatus the_status){its_status = the_status;}
smallengine.h
#ifndef SMALL_ENGINE #define SMALL_ENGINE #include "engine.h" #include "icomponent.h" #include "sensor.h" #include "pump.h" #include "fuelpump.h" #include "waterpump.h" #include "oilpump.h" #include "oxygensensor.h" #include "airflowsensor.h" #include "temperaturesensor.h" class SmallEngine : public Engine { public: SmallEngine(); virtual ~SmallEngine(); virtual void enableComponents(); virtual bool checkReady(); virtual bool startEngine(); virtual bool stopEngine(); private: Pump* its_fuelpump; Pump* its_waterpump; Pump* its_oilpump; Sensor* its_oxygensensor; Sensor* its_airflowsensor; Sensor* its_temperaturesensor; }; #endif
smallengine.cpp
#include "smallengine.h" SmallEngine::SmallEngine(){ its_fuelpump = new FuelPump(WORKING, getEngineNumber()); its_waterpump = new WaterPump(WORKING, getEngineNumber()); its_oilpump = new OilPump(WORKING, getEngineNumber()); its_oxygensensor = new OxygenSensor(WORKING, getEngineNumber()); its_airflowsensor = new AirFlowSensor(WORKING, getEngineNumber()); its_temperaturesensor = new TemperatureSensor(WORKING, getEngineNumber()); #if MESSAGE_TRACE cout<<"SmallEngine Object Created!"<<endl; #endif } SmallEngine::~SmallEngine(){ delete its_fuelpump; delete its_waterpump; delete its_oilpump; delete its_oxygensensor; delete its_airflowsensor; delete its_temperaturesensor; #if MESSAGE_TRACE cout<<"SmallEngine Object Destroyed!"<<endl; #endif } void SmallEngine::enableComponents(){ its_fuelpump->enable(); its_waterpump->enable(); its_oilpump->enable(); its_oxygensensor->enable(); its_airflowsensor->enable(); its_temperaturesensor->enable(); } bool SmallEngine::checkReady(){ bool return_val = false; if(its_fuelpump->isWorkingProperly() && its_waterpump->isWorkingProperly() && its_oilpump->isWorkingProperly() && its_oxygensensor->isWorkingProperly() && its_airflowsensor->isWorkingProperly() && its_temperaturesensor->isWorkingProperly()){ setEngineStatus(READY); return_val = true; }else{ setEngineStatus(NOTREADY); return_val = false; } return return_val; } bool SmallEngine::startEngine(){ bool return_val = false; if(getEngineStatus() == READY){ setEngineStatus(RUNNING); return_val = true; }else{ return_val = false; } return return_val; } bool SmallEngine::stopEngine(){ setEngineStatus(NOTREADY); return true; }
engineutils.h
#ifndef __ENGINE_UTILITIES_H #define __ENGINE_UTILITIES_H enum PartStatus {NOTWORKING, WORKING}; enum EngineStatus {NOTREADY, READY, RUNNING}; /******************************** Set to true for message traces ********************************/ #define MESSAGE_TRACE true #endif
main.cpp
#include <iostream> using namespace std; #include "engine.h" #include "smallengine.h" int main(){ Engine* engine_ptr = new SmallEngine(); engine_ptr->enableComponents(); if(engine_ptr->checkReady()){ engine_ptr->startEngine(); cout<<"Engine Started!"<<endl; } else cout<<"Engine could not start!"<<endl; delete engine_ptr; return 0; }
A combination of pure-virtual, virtual, and non-virtual functions is used to achieve various types of inheritance behavior. These types of behavior are discussed below.
The IComponent abstract base class declares a set of engine component interface functions. Since it is an abstract base class its intended use is in this design is solely as a base class from which derived classes inherit IComponent's public interface.
The Component class inherits IComponent's interface and implements much of the generalized component behavior. However, Component fails to implement the getComponentType() function and therefore becomes an abstract base class.
The classes Pump and Sensor inherit from Component. Pump extends the functionality of Component and adopts the generalized behavior as implemented in Component. The Sensor class also extends the functionality of Component but overrides the isWorkingProperly() function because of the specialized behavior required of Sensor components. However, since Pump and Sensor fail to implement the getComponentType() function they too become abstract base classes.
The remaining concrete Component classes inherit from either Pump or Sensor and implement the getComponentType() function.
Since IComponent declares all of its interface functions to be pure virtual, with the exception of the destructor and constructor, it dictates that subclasses must override them eventually. Compare this with the expectation of an ordinary virtual function declaration. When the Component class overrides IComponent's pure virtual functions the new functions remain virtual, but now, since an implementation for them exists in Component, subclasses of Component may choose to live with the implementation they inherit or provide their own specialized behavior through overriding.
The Engine class declares several non-virtual functions in addition to some pure virtual functions. The non-virtual functions will be inherited by Engine subclasses but they cannot be overridden. Engine subclasses may, of course, redeclare any of the non-virtual functions the Engine class declares if the design so dictates, however, polymorphic behavior will not be achieved when those derived class objects are referenced by a Engine base class pointer.
The pure virtual functions declared by Engine must be implemented somewhere. In this example they are implemented in the SmallEngine class.
The SmallEngine class is an aggregate class comprised of three pumps and three sensors. The containment is by value, as indicated by the solid diamonds on the association lines in figure 16-5, because the lifetime of each component object is constrolled by a SmallEngine object. The components come into existence when a SmallEngine object is created and they are destroyed when a SmallEngine object is destroyed.
The main() function code listed on the previous page declares an Engine base class pointer, creates an instance of SmallEngine, and then checks the readiness status of the engine by sending a checkReady() message. If the checkReady() message returns true the engine is started and a message saying so is printed to the terminal.
If the checkReady() message fails an alternate message is sent to the terminal. The checkReady() message can fail if any of engine's components are not properly initialized or are not working properly. Fault conditions can be inserted into individual components although this functionality is not demonstrated or tested in the main() function.
Figure 16-6 shows the results of running the polymorphic engine component program.
Figure 16-6: Results of Running Polymorphic Engine Program
| < Day Day Up > |
|