Behavioral

State

Purpose

Allow an object to change its behavior when its internal state changes.

UML

Behavior

The Context class, which is the primary interface for clients, delegates state-specific requests to the current Concrete State object. The Context object can pass itself to the Concrete State object if it  needs to access the Context object.

Note that either the context or the state object can decide which state succeeds another.

Example

In Microsoft Transaction Server (MTS/COM+) roles are used to logically group users in order to determine which users can access components and/or interface methods. The following example uses the State pattern to change the dynamic behavior based on the user's role. Users in the role of Employee can invoke OpenAccount() and TrnasferMoney(). Users in the role of Manager can invoke all operations. The State pattern changes the dynamic behavior by throwing an AccessDenied exception when an Employee attempts to invoke methods for which he/she has no access.

Usage

/* Exception class */
class AccessDenied : public std::exception
{
private:
    const std::string strWhat;
public:
    AccessDenied( const std::string& str = "") : strWhat(str) {}

    const char *what() const throw()
    {
        return strWhat.c_str();
    }
};

/* State Classes */

/* State: Defines an interface for encapsulating state-dependent behavior. In this example, MTSRole declares an interface for all role-based methods. Assume the MTS application contains two security roles, Teller and Manager */
class MTSRole
{
// Constructors/Destructor
public:
    virtual ~MTSRole(){}

// Public Interface
public:
    virtual void OpenAccount() = 0;             // Manager or Teller
    virtual void CloseAccount() = 0;            // Manager
    virtual void TransferMoney() = 0;           // Manager or Teller
    virtual void IncreaseCredit() = 0;          // Manager
    virtual void SubmitLoanApplication() = 0;   // Manager
};

/* ConcreteState subclasses: Each subclass (RoleEmployee, RoleManager) implements state-specific behavior. A user in the role of Employee can only invoke OpenAccount and TransferMoney. A user in the role of Manager can invoke any operation */
class RoleEmployee : public MTSRole
{
// Public Interface
public:
    virtual void OpenAccount() // Manager or Teller
    {
        std::cout << "RoleEmployee::OpenAccount()" << std::endl;

        // ...
   
}
    virtual void TransferMoney()
    {
        std::cout << "RoleEmployee::TransferMoney()" << std::endl;

        // ...
   
}
    virtual void CloseAccount()
    {
        throw AccessDenied("CloseAccount can only be called by a Manager");
    }
    virtual void IncreaseCredit()
    {
        throw AccessDenied("IncreaseCredit can only be called by a Manager");
    }
    virtual void SubmitLoanApplication()
    {
        throw AccessDenied("SubmitLoanApplication can only be called by a Manager");
    }
};

/* A user in the role of Manager can invoke all role-based operations */
class RoleManager : public MTSRole
{
// Public Interface
public:
    virtual void OpenAccount()
    {
        std::cout << "RoleManager::OpenAccount()" << std::endl;

        // ...
   
}
    virtual void CloseAccount()
    {
        std::cout << "RoleManager::CloseAccount()" << std::endl;

        // ...
   
}
    virtual void TransferMoney()
    {
        std::cout << "RoleManager::TransferMoney()" << std::endl;

        // ...
   
}
    virtual void IncreaseCredit()
    {
        std::cout << "RoleManager::IncreaseCredit()" << std::endl;

        // ...
   
}
    virtual void SubmitLoanApplication()
    {
        std::cout << "RoleManager::SubmitLoanApplication()" << std::endl;

        // ...
   
}
};

class RoleController
{
// Constructors/Destructor
public:
    // On construction, initial state is Employee
    RoleController() : m_spState(new RoleEmployee) {}
    ~RoleController(){}

// Public Interface
public:
    void OpenAccount()             { m_spState->OpenAccount(); }
    void CloseAccount()            { m_spState->CloseAccount(); }
    void TransferMoney()           { m_spState->TransferMoney(); }
    void IncreaseCredit()          { m_spState->IncreaseCredit(); }
    void SubmitLoanApplication()   { m_spState->SubmitLoanApplication(); }

    void ChangeState( MTSRole* pRole) 
    { 
        // Always validate incoming pointers ...

        m_spState = std::auto_ptr<MTSRole>(pRole); // assign new one (old is deleted by auto_ptr)

        // We can potentially return old role if required ...
   
}

// Data members
private:
    std::auto_ptr<MTSRole> m_spState;
};

/* Main */

enum Role { EMPLOYEE, MANAGER };
bool IsCallerInRole( Role eRole ) 
{
    // Simulate IsCallerInRole MTS SDK method. Assume user is in Employee role
   
return (eRole == EMPLOYEE);
}

void f();
void g();
int main( int argc, char* argv[] )
{
    // Invoke operations assuming user is in EMPLOYEE role   
    f();

    // Invoke operations assuming user is in MANAGER role   
    g();

    return 0;
}

void f()
{
    try
    {
        /* Assume an MTS (COM+) object defines obRC as a data member. */
        RoleController obRC;         // default state is Employee

        /* Assume the following code is inside an MTS object method. The MTS object is 
        configured with Role security. Use IsCallerInRole MTS SDK method to determine
        in what role did a user invoke the COM method */
        if ( IsCallerInRole( EMPLOYEE ) )
        {
            obRC.OpenAccount();     // OK
            obRC.TransferMoney();   // OK
            obRC.IncreaseCredit();  // Access denied!

        }
    } // try
    catch ( const AccessDenied& e)
    {
        std::cout << e.what() << std::endl;
    }
    catch ( const std::exception &e)
    {
        std::cout << e.what() << std::endl;
    }
}

void g()
{
    try
    {
        /* Assume an MTS (COM+) object defines obRC as a data member. */
 
       RoleController obRC;         // default state is Employee
        obRC.ChangeState( new RoleManager );

        /* Assume the following code is inside an MTS object method. The MTS object is 
        configured with Role security. Use IsCallerInRole MTS SDK method to determine
        in what role did a user invoke the COM method */
        if ( !IsCallerInRole( MANAGER ) )
        {
            obRC.OpenAccount();     // OK
            obRC.TransferMoney();   // OK
            obRC.IncreaseCredit();  // OK
        }
    } // try
    catch ( const AccessDenied& e)
    {
        std::cout << e.what() << std::endl;
    }
    catch ( const std::exception &e)
    {
        std::cout << e.what() << std::endl;
    }
}

Notes