Dynamic role-switching


introduction framework patterns components conclusions references appendix experience
For many applications static type hierarchies do not provide the flexibility needed to model dynamically changing roles. For example we may wish to consider a person as an actor capable of various roles during his lifetime, some of which may even coexist concurrently. The characteristic feature of the dynamic role switching idiom underlying the actor pattern is that it allows us regard a particular entity from multiple perspectives and that the behavior of that entity changes accordingly. We will look at a possible realization of the idiom below.

Taking our view of a person as an actor as a starting point, we need first to establish the repertoire of possible behavior.

    enum Role { Person = 0 , Student, Employer, Final };
    
    class actor {                      // defines the repertoire
      public:
        actor() { } 
        virtual void walk()  { if (exists()) self()->walk();  }
        virtual void talk()  { if (exists()) self()->talk();  } 
        virtual void think() { if (exists()) self()->think(); }
        virtual void act()   { if (exists()) self()->act();   }
    
        virtual void become(Role) { }               // only for a person
        virtual void become(actor*) { }
    
        virtual actor* self() { return this; }         // an empty self
        int exists() { return self() != this; }        // who am I
      };
  
Apart from the repertoire of possible behavior, which consists of the ability to walk, talk, think and act, an actor has the ability to establish its own identity (self) and to check whether it exists as an actor, which is true only if it has become another self. However, an actor is not able to assume a different role or to become another self. We need a person for that!

Next, we may wish to refine the behavior of an actor for certain roles, such as for example the student and employer roles, which are among the many roles a person can play.

    class student : public actor {
      public:
        void talk()  { cout << "OOP" << endl; }
        void think() { cout <<  "Z"  << endl; }
    };
    
    class employer : public actor {
      public:
        void talk() { cout << "$$" << endl; }
        void act()  { cout << "business" << endl; }
    };
  
Only a person has the ability to assume a different role or to assume a different identity. Apart from becoming a Student or Employer, a person may for example become an adult_person and in that capacity again assume a variety of roles.
    class person : public actor {
      public:
        person();                         // to create a person
    
        void become(Role r);              // to become a ...
        void become(actor* p);            // change identity
    
        int exists() { return role[Person] != this; }
        actor* self() { return exists()?role[Person]->self():role [role]; }
      private:
        int _role;
        actor* role[Final+1];             // the repertoire
      };
  
A person may check whether he exists as a Person, that is whether the Person role differs from the person's own identity. A person's self may be characterized as the actor belonging to the role the person is playing, taking a possible change of identity into account. When a person is created, his repertoire is still empy.
    person::person() {
       for (int i=Person; i <= Final; i++) role[i] = this;
       become(Person);
    }
  
Only when a person changes identity by becoming a different actor (or person) or by assuming one of his (fixed) roles, he is capable of displaying actual behavior.
    void person::become(actor* p) { role[Person] = p; } 
permanent

void person::become(Role r) { require( Person <= r && r <= Final ); if (exists()) self()->become(r); else { _role = r; if ( role [role] == this ) { switch (_role) { case Person: break;
nothing changes

case Student: role [role] = new student; break; case Employer: role [role] = new employer; break; case Final: role [role] = new actor; break; }; } } }
Assuming or 'becoming' a role results in creating a role instance if none exists and setting the _role instance variable to that particular role. When a person's identity has been changed, assuming a role takes effect for the actor that replaced the person's original identity. (However, only a person can change roles!) The ability to become an actor allows us to model the various phases of a person's lifetime by different classes, as illustrated by the adult_person class.
    class adult_person: public person {
      public:
        void talk() { cout << "interesting" << endl; }
    };
  
In the example code below we have a person talking while assuming different roles. Note that the person's identity may be restored by letting the person become its original self.
    person p; p.talk();                    
empty

p.become(Student); p.talk();
OOP

p.become(Employer); p.talk();
$$

p.become(new adult_person); p.talk();
interesting

p.become(Student); p.talk();
OOP (new student)

p.become(&p); p.talk();
$$ (old role)

p.become(Person); // initial state
The dynamic role switching idiom can be used in any situation where we wish to change the functionality of an object dynamically. It may for example be used to incorporate a variety of tools in a drawing editor, as illustrated in  [Eliens95b].
introduction framework patterns components conclusions references appendix experience