// Purpose.  Extension Object design pattern.
//
// Summary.  Discusses how to use aggregation (instead of mixin multiple
// inheritance) to add "interfaces" (or methods) to a class at run-time.
// This approach overcomes some limitations in the Visitor and Decorator
// patterns - but - it requires the use of RTTI, and, it makes client code
// more complex.
//
// Whereas Decorator requires that the core class's interface remain fixed
// as successive "wrappers" are applied, Extension Object allows the class's
// interface to grow incrementally and dynamically.
//
// The focus of the Extension Object pattern is to engineer a class to
// support additional methods, or services.  Clients that want to leverage
// an interface extension, query the object first to see if it supports
// the extension before attempting to use it.
//
// The pattern can tempt the designer to rely on this technique to
// rationalize reactive coding rather than commit oneself to proactive
// design.  The paper offered no discussion of access to private and
// protected members of Image by Extension objects.

#include <iostream>
#include <string>
using std::cout;
using std::string;

class Extension {
public:
   virtual ~Extension() { }
   void registerOwner( class Image* o ) { owner = o; }
protected:
   Image* owner;
};

class MosaicExtension : public Extension { public:
   void doMosaic();
};

class HeatExtension : public Extension { public:
   void doThermalSpectrum();
};


class Image {
   string name;
public:
   Image( string n ) : name(n) { }
   string getName()  { return name; }
   void display()    { cout << "display: " << name << '\n'; }
   virtual Extension* getExtension( const type_info& ) { return 0; }
};


void MosaicExtension::doMosaic() {
   cout << "  doMosaic: " << owner->getName() << '\n'; }
void HeatExtension::doThermalSpectrum() {
   cout << "  doThermalSpectrum: " << owner->getName() << '\n'; }


class Lsat : public Image {
   Extension*  exten;
public:
   Lsat( string n, Extension* ex ) : Image( n ) {
      exten = ex;
      exten->registerOwner( this );
   }
   ~Lsat() { delete exten; }
   /*virtual*/ Extension* getExtension( const type_info& ti ) {
      if (typeid(MosaicExtension) == ti) return exten;
      return Image::getExtension( ti );
   }
};

class IR : public Image {
   Extension*  exten;
public:
   IR( string n, Extension* ex ) : Image( n ) {
      exten = ex;
      exten->registerOwner( this );
   }
   ~IR() { delete exten; }
   /*virtual*/ Extension* getExtension( const type_info& ti ) {
      if (typeid(HeatExtension) == ti) return exten;
      return Image::getExtension( ti );
   }
};


void main( void ) {
   Image* images[4] = {
      new Lsat( "Western hemisphere", new MosaicExtension() ),
      new IR( "Desert Storm",         new MosaicExtension()   ),
      new Lsat( "Amazon rain forest", new HeatExtension() ),
      new IR( "Gulf of Oman",         new HeatExtension()   ) };
   Extension*        ext;
   MosaicExtension*  mosaicExt;
   HeatExtension*    heatExt;

   for (int i=0; i < 4; i++) {
      images[i]->display();
      if (ext = images[i]->getExtension( typeid(MosaicExtension) )) {
         if (mosaicExt = dynamic_cast<MosaicExtension*>( ext ))
            mosaicExt->doMosaic();
      } else if (ext = images[i]->getExtension( typeid(HeatExtension) )) {
         if (heatExt = dynamic_cast<HeatExtension*>( ext ))
            heatExt->doThermalSpectrum();
   }  }
   for (i=0; i < 4; i++) delete images[i];
}

// display: Western hemisphere
//   doMosaic: Western hemisphere
// display: Desert Storm
// display: Amazon rain forest
// display: Gulf of Oman
//   doThermalSpectrum: Gulf of Oman