16.10 USING ROLE-PLAYING CLASSES


16.10 USING ROLE-PLAYING CLASSES

The approaches to OO design laid out so far-the one using repeated inheritance and the one based on mixin classes-will work for domains that lend themselves to mutually exclusive categorization. By mutually exclusive categorization we mean apples versus oranges kind of categorization-a fruit cannot be an apple and an orange at the same time. In both the repeated-inheritance and the mixin-classes examples of the previous two sections, an Employee can only be a Manager, or a Salesperson, or a SalesManager, and so on. Those programs will not allow for an Employee to be a Manager and a SalesPerson at the same time. This situation can be described by saying that each Employee in those programs is locked into one role. Yes, we could extend the class hierarchies in those examples to create further combination roles, but at some point we would run into combinatorial issues, not to speak of the inheritance loops that we might create in the process.

But there exist important domains that do not allow for mutually exclusive categorization. Even in the simple employee "domain" we have been considering, it is indeed possible for an Employee to be a Manager and a SalesPerson at the same time. We will now show how one can base OO design on role-playing classes that allow objects to play multiple roles simultaneously. In the particular employee-domain example of a role-based hierarchy we will present in this section, the same individual will be allowed to acquire multiple roles, but at any given time only one role will be active.[2]

Shown in Figure 16.15 is a role-based class hierarchy for achieving the same functionality as in the previous two sections. The hierarchy of role-playing classes hangs from the root class Role. So, as shown in the figure, a Manager IsA Role, a SalesPerson IsA Role, an ExecutiveManager IsA Role, and a SalesManager IsA Role. Whereas the links shown in the hierarchy that hang from Role are those of generalization/ specialization, the link between Employee and Role is an association. An Employee can have an arbitrary number of roles, including no roles. The purpose of the Role class vis-à-vis the Employee class is made clear by the following partial definition of the latter:

      class Employee {      protected:          string name;          string address;          EducationLevel education;          PeopleSkill pSkill;          Leadership leadership;          vector<Role*> allRoles;          Role* activeRole;          // . . . . . . . . .      }; 

click to expand
Figure 16.15

Note the new data members, allRoles and activeRole. The data member allRoles is supposed to store a list of all the roles that an employee is qualified to play. So if an Employee is qualified to serve both as a Manager and as a SalesPerson, both these roles would be in the vector allRoles. With a slight modification of the system, we could even store in this vector the different types of managerial obligations that an Employee could fulfill. For example, the vector could contain more detailed items such as "Manager of Design Department," "Manager of Development Department," and so on. The data member activeRole is supposed to capture the notion that an Employee can only be active in one role at one time.

The functionality of the Employee class with respect to the various roles is described by the following functions:

      class Employee {          // . . . . .      public:          // . . . . .          Role* setActiveRole( Role* role );          Role* getActiveRole() const;          void addToRoles( Role* newRole );          void removeRole( Role* role );          // . . . . .      }; 

Obviously, now it will be possible for an employee to be, say, a manager at one time and then to be a salesperson at another time.

In our example, the class Role that sits at the root of the hierarchy of the role playing classes contains code that we want inherited by all the concrete roles in the hierarchy. We have defined it in the following manner:

      class Role {      protected:           string roleName;           int roleExperience;          // in years      public:           Role() {}           Role( string arole, int experience );           Role( const Role& other );           string getRoleName() const;           void setRoleName( string name );           int getRoleExperience() const;           void setRoleExperience( int yy );           virtual bool operator==( const Role& role );           void printRoleExperience() const;           virtual void print() const;           virtual ~Role();      }; 

Using the same definition for the mixin class ManagerType as shown in the last section, we can now define the Manager role class as follows:

      class ManagerType {      protected:           virtual double productivityGainYtoY() = 0;           virtual bool computeEmployeeSatisfaction() = 0;      };      class Manager : public Role, public ManagerType {           Department dept;      public:          Manager();          Manager( string roleName );          Manager( Department aDept );          double productivityGainYtoY();          bool computeEmployeeSatisfaction();          void print();          ~Manager();      }; 

where the data member dept of Manager is private because it will have no meaning for the subclass ExecutiveManager since an ExecutiveManager supervises multiple departments and, we will assume, does not belong to any one of them. We have provided the class with a no-arg constructor because we need it in the ExecutiveManager class. Remember, when a superclass constructor is not invoked explicitly, the system implicitly invokes its no-arg constructor. It goes without saying that Manager must provide implementations for the pure virtual functions of ManagerType. This class can then be extended in the following manner:

      class ExecutiveManager : public Manager {          short level;          vector<Department> departments;      public:          ExecutiveManager( short lvl );          void addDepartment( Department dept );          void setLevel( int lvl );          double productivityGainYtoY();          void print();          ~ExecutiveManager();      }; 

Since an ExecutiveManager supervises multiple departments, the productivityGainYtoY() function for such an individual will be different from this function for a Manager. So its new definition here will override the definition in Manager.

The rest of the hierarchy, consisting of the role classes SalesPerson and SalesManager, is set up in a similar manner. Shown below is the code for this example:

 
//RolePlayers.cc #include "MI_Utilities.h" /////////////////// incomplete def of class Role //////////////////// class Role; ////////////////////////// class Employee /////////////////////////// class Employee { protected: string name; string address; EducationLevel education; PeopleSkill pSkill; Leadership leadership; vector<Role*> allRoles; Role* activeRole; public: Employee( string nam, string add, EducationLevel edLevel ) : name( nam ), address( add ), education( edLevel ), pSkill( pUnknown ), leadership( lUnknown ), allRoles( vector<Role*>() ), activeRole( 0 ) {} Employee( const Employee& other ); void setActiveRole( Role* role ); Role* getActiveRole() const { return activeRole; } void addToRoles( Role* newRole ); void removeRole( Role* role ); voidremoveAllRoles(); void setPeopleSkill( PeopleSkill skill ) { pSkill = skill; } string getName() const { return name; } string getAddress() const { return address; } void setEducationLevel(EducationLevel eduLvl) {education = eduLvl;} EducationLevel getEducationLevel() const { return education; } Leadership getLeadership() const { return leadership; } void setLeadership( Leadership lead ) { leadership = lead; } void print(); // needs role definitions ~Employee() {} // see text for why this is do-nothing //(A) }; ///////////////////////////// class Role //////////////////////////// class Role { protected: string roleName; int roleExperience; // in years public: Role() {} Role( string arole, int experience ) : roleName( arole ), roleExperience( experience ) {} Role( const Role& other ) : roleName( other.roleName ), roleExperience( other.roleExperience ) {} string getRoleName() const { return roleName; } void setRoleName( string name ) { roleName = name; } int getRoleExperience() const { return roleExperience; } void setRoleExperience( int yy ) { roleExperience = yy; } virtual bool operator==( const Role& role ) { return ( roleName == role.roleName ) ? true : false; } void printRoleExperience() const { cout << "Years in this role: " << roleExperience << endl; } virtual void print() const {} virtual ~Role() {} }; //////////// Employee member functions that need Role defs /////////// Employee::Employee( const Employee& other ) : name( other.name ), address( other.address ), education( other.education ), pSkill( other. pSkill ), leadership( other.leadership ), allRoles( other.allRoles ), activeRole( other.activeRole ) {} void Employee::addToRoles(Role* newRole){allRoles.push_back(newRole);} void Employee::removeRole( Role* role ) { vector<Role*>::iterator iter = allRoles.begin(); while ( iter != allRoles.end() ) { if ( *iter == role ) { allRoles.erase( iter ); } iter+;+;; } } void Employee::removeAllRoles() { allRoles = vector<Role*>(); } void Employee::setActiveRole( Role* role ) { activeRole = role; } void Employee::print() { cout << name << endl; cout << address << endl; cout << EducationLevels[ education ] << endl; cout << "People skill:" << PeopleSkills[ (int) pSkill ] << endl; cout << "Leadership quality:" << LeaderQualities[(int) leadership]; cout << endl; if ( activeRole != 0 ) cout << "ACTIVE ROLE: " << activeRole->getRoleName() << endl; if ( allRoles.size() != 0 ) { cout << "LIST OF ALL ALLOWABLE ROLES: " << endl; vector<Role*>::iterator iter = allRoles.begin(); while ( iter != allRoles.end() ) { cout << (*iter)->getRoleName() << endl; (*iter+;+;)->printRoleExperience(); } } } //////////////// mixin class ManagerType (abstract) //////////////// class ManagerType { protected: virtual double productivityGainYtoY() = 0; virtual bool computeEmployeeSatisfaction() = 0; }; ////////////////////// class Manager (IsA Role) ///////////////////// class Manager : public Role, public ManagerType { Department dept; public: Manager() {} // Needed by the ExecutiveManager constructor Manager( string roleName ) : Role( roleName, 0 ) {} Manager( Department aDept ) : Role( "Manager of " +; aDept.getName(), 0 ), dept( aDept ) {} double productivityGainYtoY() { int lastProd = dept.getProductionLastYear(); int prevProd = dept.getProductionPreviousYear(); return 100 * ( lastProd-prevProd ) / (double) prevProd; } bool computeEmployeeSatisfaction() { return treu; } void print() { printRoleExperience(); dept.print(): } ~Manager() {} }; //////////////////// class ExecutiveManager //////////////////// // An ExecutiveManager supervises more than one department class ExecutiveManager : public Manager { short level; vector<Department> departments; public: ExecutiveManager( short lvl ) : Manager( "Executive Manager" ), level( lvl ) { departments = vector<Department>(); } void addDepartment(Department dept) {departments.push_back( dept );} void setLevel( int lvl ) { level = lvl; } // overrides Manager's productivityGainYtoY(): double productivityGainYtoY() { double gain = 0.0; vector<Department>::iterator iter = departments.begin(); while ( iter != departments.end() ) { int lastProd = iter->getProductionLastYear(); int prevProd = iter->getProductionPreviousYear(); gain +;= ( lastProd-prevProd ) / (double) prevProd; } return gain/departments.size(); } void print() { printRoleExperience(); if ( departments.size() != 0 ) { cout << "Departments supervised: " << endl; vector<Department>::iterator iter = departments.begin(); while ( iter != departments.end() ) iter+;+;->print(); } } ~ExecutiveManager() {} }; ////////////////// mixin class SalesType (abstract) ///////////////// class SalesType { protected: virtual double salesVolume() = 0; virtual double productivityGainYtoY() = 0; }; ///////////////////////// class SalesPerson ///////////////////////// class SalesPerson : public Role, public SalesType { double salesVolLastYear; double salesVolPrevYear; public: SalesPerson( string rolename ) : Role( rolename, 0 ) {} SalesPerson() : Role( "Sales Person", 0 ), salesVolLastYear( 0 ), salesVolPrevYear( 0 ) {} void setSalesVolLastYear(double sales){ salesVolLastYear = sales; } void setSalesVolPrevYear(double sales){ salesVolPrevYear = sales; } double salesVolume() { return salesVolLastYear; } double productivityGainYtoY() { return 100 * (salesVolLastYear - salesVolPrevYear) / salesVolPrevYear; } void print() { cout << "Sales Department" << endl; printRoleExperience(); } ~SalesPerson() {} }; ////////////////////////// class SalesManager /////////////////////// class SalesManager : public SalesPerson, public ManagerType { vector<SalesPerson*> sellersSupervised; public: SalesManager() : SalesPerson( "Sales Manager" ), sellersSupervised( vector<SalesPerson*>() ) {} // overrides SalesPerson's productivityGainYtoY(): double productivityGainYtoY(){ double gain = 0.0; vector<SalesPerson*>::iterator iter = sellersSupervised.begin(); while ( iter != sellersSupervised.end() ) { gain +;= (*iter+;+;)->productivityGainYtoY(); } return gain/sellersSupervised.size(); } void print() { printRoleExperience(); if ( sellersSupervised.size() != 0 ) { cout << "Sales Persons supervised: " << endl; vector<SalesPerson*>::iterator iter = sellersSupervised.begin(); while ( iter != sellersSupervised.end() ) (*iter+;+;)- >print(); } } ~SalesManager() {} }; /////////////////// special function that uses RTTI ////////////////// bool checkReadyForPromotion( Employee* e ) { //(B) Role* r = e->getActiveRole(); Manager* m = dynamic_cast<Manager*>( r ); if ( m != 0 ) { // add additional promotion criteria to the following test // as necessary (left as an exercise to the reader) if ( m->getRoleExperience() >= MinYearsForPromotion ) { cout << "yes, ready for promotion in the active role\n"; return true; } else{ cout << "Not ready for promotion in the active role\n"; return false; } } SalesPerson* s = dynamic_cast<SalesPerson*>( r ); if ( s != 0 ) { if ( s->productivityGàinYtoY() > 50 ) { cout << "yes, ready for promotion in the active role\n"; return true; } else { cout << "Not ready for promotion in the active role\n"; return false; } } else { cout << "Unable to determine if ready for promotion\n"; return false; } } /***** class bad_cast {} //(C) bool checkReadyForPromotion( Employee* e ) { Role& role_ref = *( e->getActiveRole() ); try { Manager& m_ref = dynamic_cast<Manager&>( role_ref ); if ( m_ref.getRoleExperience() >= MinYearsForPromotion ) { cout << "yes, ready for promotion in the active role\n" return true; } else cout << "No, not ready for promotion in the active role\n"; } catch( bad_cast& b ) { cout << "Unable to determine if ready for promotion" << endl; return false; } } *****/ //////////////////////////////// main /////////////////////////////// int main() { Department d1( "Design" ); Department d2( "Manufacturing" ); Department d3( "Development" ); cout << "TEST 1: " << endl; //(D) Employee* emp1 = new Employee("Zippy Zester","Zeetown",HighSchool); Role* role1 = new Manager( d1 ); Role* role2 = new Manager( d2 ); role1->setRoleExperience( 2 ); role2->setRoleExperience( 12 ); emp1->addToRoles( role1 ); emp1->addToRoles( role2 ); emp1->setActiveRole( role2 ); emp1->print(); //(E) checkReadyForPromotion( emp1 ); //(F) cout << endl << endl; cout << "TEST 2: " << endl; //(G) Employee* emp2 = new Employee("Deny Deamon","Deensville",College); emp2->setPeopleSkill( Friendly ); emp2->setLeadership( CanLeadLargeGroups ); Role* role3 = new Manager( d1 ); Role* role4 = new Manager( d2 ); role3->setRoleExperience( 23 ); role4->setRoleExperience( 7 ); emp2->addToRoles( role3 ); emp2->addToRoles( role4 ); Role* role5 = new SalesPerson(); role5->setRoleExperience(18); Salesperson* sp = static_cast<SalesPerson*>( role5 ); sp->setSalesVolLastYear( 200 ); sp->setSalesVolPrevYear( 100 ); emp2->addToRoles( role5 ); emp2->setActiveRole( role5 ); emp2->print(); //(H) checkReadyForPromotion( emp2 ); //(I) delete emp1; delete emp2; delete role1; delete role2; delete role3; delete role4; delete role5; return 0; }

The reader is probably curious as to why the destructor for Employee in line (A) has a do-nothing body considering that this class has a pointer data member, activeRole, and a data member that is a vector of pointers, each element of the vector pointing to an object of type Role. The reason is that we have chosen to pass around pointer to unique Role objects; in other words, we do not copy the Role objects themselves. So when a new role is added to an employee's vector of roles, it is just a pointer to a previously constructed Role object. (An alternative design choice would have been to duplicate the Role object and store the pointers to the duplicates in both the activeRole and the allRoles data members of the Employee class.) So all the Role objects are created and destroyed at only one place-in main.

The main of the above program constructs two test cases. The first test case, beginning in line (D), is for an Employee who can play multiple managerial roles, the active role in the test case being "Manager of Manufacturing." The second test case, beginning in line (G), is for an employee whose active role is as a salesperson. The output of the program, produced by the statements in lines (E), (F), (H), and (I), is displayed below

      TEST 1:      Zippy Zester      Zeetown      highschool      People skill: unknown      Leadership quality: unknown      ACTIVE ROLE: Manager of Manufacturing      LIST OF ALL ALLOWABLE ROLES:      Manager of Design      Years in this role: 2      Manager of Manufacturing      Years in this role: 12      yes, ready for promotion in the active role      TEST 2:      Deny Deamon      Deensville      college      People skill: friendly      Leadership quality: can lead large groups      ACTIVE ROLE: Sales Person      LIST OF ALL ALLOWABLE ROLES:      Manager of Design      Years in this role: 23      Manager of Manufacturing      Years in this role: 7      Sales Person      Years in this role: 18      yes, ready for promotion in the active role 

The code shown in the program RolePlayers.cc also includes a function checkReadyForPromotion() in line (B). This function and the commented out code that follows are subjects of the next section.

[2]It would be easy to modify the program so that at any given time a set of roles would be active, as opposed to just one role.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

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