Extended Example: Engine Components Revisited

 < 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.

A Basis for Comparison

Figure 16-4 gives the UML class diagram for the aircraft engine components model originally presented in chapter 12.

click to expand
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.

click to expand
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.

Polymorphic Engine Component Code

The code for the revised polymorphic engine component example is given below:

icomponent.h

icomponent.h

start example
#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
end example

component.h

component.h

start example
#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
end example

component.cpp

component.cpp

start example
#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;} 
end example

pump.h

pump.h

start example
#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
end example

pump.cpp

pump.cpp

start example
#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;}
end example

sensor.h

sensor.h

start example
#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
end example

sensor.cpp

sensor.cpp

start example
#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;}
end example

waterpump.h

waterpump.h

start example
#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
end example

waterpump.cpp

waterpump.cpp

start example
#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";}
end example

oilpump.h

oilpump.h

start example
#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
end example

oilpump.cpp

oilpump.cpp

start example
#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";}
end example

fuelpump.h

fuelpump.h

start example
#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
end example

fuelpump.cpp

fuelpump.cpp

start example
#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";}
end example

airflowsensor.h

airflowsensor.h

start example
#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
end example

airflowsensor.cpp

airflowsensor.cpp

start example
#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;}
end example

oxygensensor.h

oxygensensor.h

start example
#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
end example

oxygensensor.cpp

oxygensensor.cpp

start example
#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;} 
end example

temperaturesensor.h

temperaturesensor.h

start example
#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
end example

temperaturesensor.cpp

temperaturesensor.cpp

start example
#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;  }
end example

engine.h

engine.h

start example
#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
end example

engine.cpp

engine.cpp

start example
#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;} 
end example

smallengine.h

smallengine.h

start example
#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
end example

smallengine.cpp

smallengine.cpp

start example
#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; }
end example

engineutils.h

engineutils.h

start example
#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
end example

main.cpp

main.cpp

start example
#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; }
end example

Discussion of the Polymorphic Engine Component Code

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.

IComponent and Derived Classes

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.

What is Meant by a Pure Virtual vs. a Virtual Member Function Declaration.

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.

Engine and SmallEngine

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.

Running the Polymorphic Engine Component Program

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.

click to expand
Figure 16-6: Results of Running Polymorphic Engine Program



 < Day Day Up > 



C++ for Artists. The Art, Philosophy, and Science of Object-Oriented Programming
C++ For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504028
EAN: 2147483647
Year: 2003
Pages: 340
Authors: Rick Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net