/*
    The Role of a class 

    Motto: a public derived class should be 
           a specialization of its base class

    In one respect, the program has lost ground in the transformation.
    The original program specified only once, in Card::rebate(), that 
    the default rebate for cards i s45. With the current definition of 
    Component, the values of all non-zero rebates must be specified in 
    the object declarations. The program has no place to record a default
    rebate specifically for cards. The program does need to distinguish 
    cards from monitors. Inheritance can provide appropriate specialization
    with distinct constructors for cards and monitors.

    We must reintroduce the classes Card and Monitor to provide constructors
    with the appropriate rebate defaults.

    The specialization in the derived classes is limited to their 
    constructors. The default rebates are specified as default argument
    values to the constructors for Card and Monitor.

 */

#include <iostream>
using namespace std;

enum CARD       {CDROM, TAPE, NETWORK };
enum MONITOR    { MONO, COLOR };

class Component
{
public:
        Component( int p, char *n, int r = 0);
    int     netPrice();
    int     price()  { return m_price; }
    char   *name()   { return m_name; }
    int     rebate() { return m_rebate; }
private:
    int     m_price;
    char   *m_name;
    int     m_rebate;
};

Component::Component( int p, char *n, int r)
                : m_price(p), m_name(n), m_rebate(r)  { } 

int   Component::netPrice()
{
    return price() - rebate();
}

class Card : public Component
{
public:
    Card( int p, char *n, int r = 45)
                : Component( p, n, r)  {   }
};

class Monitor : public Component
{
public:
    Monitor( int p, char *n, int r = 0)
                : Component( p, n, r)  {   }
};

/*
   Generally, public inheritance is used when the derived class is a
   specialization of the base class, that is, when the classes exhibit 
   the "is a kind of" relationship. A Card is a kind of Component; 
   a Monitor is a kind of Component. In this case, the specialization 
   applies only during construction - once constructed, all Component
   objects behave uniformly. Limiting the variation in the derived
   classes to initialization - constructor specialization - is a
   legitimate use of inheritance.

   It is interesting to note, that Card and Monitor now differ in a 
   "default value", implemented as a constructor default argument value.
   In the original version of the program, Card and Monitor differed 
   in a "default behaviour", implemented as a virtual function.

 */

Card    Network(600, "Network");
Card    CDRom(1500, "CDRom", 135);
Card    Tape(1000, "Tape");
Monitor Color(1500, "Color");
Monitor Monochrome(500, "Mono");

/*
   In place of five Component objects, there are now three Card objects
   and two Monitor objects. Only one rebate value need be specified; 
   only the CDRom object deviates from its default.
   
 */

class Computer
{
public:
            Computer( Card *c, Monitor *m );
    int     netPrice();
    void    print();
private:
    Card    *card;
    Monitor *mon;
};

/*
  The reintroduction of Card and Monitor can simplify the program
  in other way. The arguments to Computer::Computer specify one CARD
  and one MONITOR value. The enumerations place a level of indirection
  between the constructor and the information it needs, without adding
  any flexibility. The constructor really needs the Component objects
  and has to map each enumeration value to an object. 

  Maintaining this mapping is complicated. To add another Monitor for
  example GreyScale, the programmer has to
    (1) add GREY_SCALE to the MONITOR enumeration type,
    (2) declare a GreyScale object, and
    (3) add a GREY_SCALE case to the constructor's switch statement.

  Card and Monitor are distinct types. If Computer::Computer takes
  pointers to objects as its arguments, the enumerations and the switch
  statements can be removed.
 
 */

Computer::Computer( Card *c, Monitor *m )
{
    card = c;
    mon  = m;
}

int   Computer::netPrice()
{
    return mon->netPrice() + card->netPrice();
}

void    Computer::print()
{
    cout << "Configuration = " << card->name() 
         << ", "               << mon->name()  
         << ", net price = "   << netPrice() 
         << endl;
}

/*
   Here you must modify the client code: you construct Computer
   with the addresses of the objects rather than the enumerators

 */

int main()
{
    Computer nm( &Network, &Monochrome );
    Computer cm( &CDRom,   &Monochrome );
    Computer tm( &Tape,    &Monochrome );
    Computer nc( &Network, &Color );
    Computer cc( &CDRom,   &Color );
    Computer tc( &Tape,    &Color );

    nm.print();
    cm.print();
    tm.print();
    nc.print();
    cc.print();
    tc.print();

    return 0;
}