State Design Pattern with Polymorphic Objects

1.1k Views Asked by At

I have a hierarchy of objects that will all have similar behavior. I want to separate the behavior from the POCO definitions. Since the behaviors represent moving the objects to various states, this seems to me like a job for a State Pattern. But, it isn't as simple as there being a single definition for each function since each object may have slightly different behavior.

For example, let's say I have the following classes, based on an abstract base class:

public abstract BaseClass
{
    public int Property1 { get; set; }
    public int Property2 { get; set; }
}

public Class1 : BaseClass
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
}

public Class2 : BaseClass
{
    public string PropertyC { get; set; }
    public string PropertyD { get; set; }
}

The hierarchy represents the different types of objects. Let's also say that the objects all follow the same basic workflow: Submitted, Approved, Executed, Closed.

Now, the behavior of each function is also hierarchical, meaning that calling the function Approve() on Class1 should be the same as calling the inherited behavior from the BaseClass, but Class2 would override the Approve() function since that type follows a different approval process.

I'm lost trying to apply the State Pattern to these objects. I could choose to put the functions on the objects themselves and inherit them that way, and that works fine, but it breaks the POCO design. I could also implement the Approve() function with a switch statement for each object type, but that breaks my polymorphic design.

How can I apply a State Pattern to a multi-layer polymorphic Object definition and stay consistent with design principles.

Update: Let me clarify, I think that functions that do other things besides act on the object do not belong on the POCO. Ex: the Approve function is going to send emails and and trigger events in other systems, not just modify the state of the object. Maybe that's just me.

3

There are 3 best solutions below

0
On BEST ANSWER

I could choose to put the functions on the objects themselves and inherit them that way, and that works fine, but it breaks the POCO design

So I agree with everyone here that it wont break your POCO design. For example, it could looks something like this:

  public class BaseClass
  {
     public int Property1 { get; set; }
     public int Property2 { get; set; }
     public virtual bool Submitted() { return (Property1 != 0); }
     public virtual bool Approved() { return (Property2 != 0); }
     // ...
  }

  public class Class1 : BaseClass
  {
     public string PropertyA { get; set; }
     public string PropertyB { get; set; }
     public override bool Submitted() 
     { return !String.IsNullOrEmpty(PropertyA); }
     public override bool Approved() 
     // Or do specific Class1 Approval actions...
     { return !String.IsNullOrEmpty(PropertyB); }
     // ...
  }

  public class Class2 : BaseClass
  {
     public string PropertyC { get; set; }
     public string PropertyD { get; set; }
     public override bool Submitted()
     { return !String.IsNullOrEmpty(PropertyC); }
     public override bool Approved()
     { return !String.IsNullOrEmpty(PropertyD); }
     // ...
  }

  public abstract class WorkflowState<PocoType> 
     where PocoType : BaseClass
  {
     private WorkflowManager<PocoType> _workflowManger;
     private PocoType _pocoObject;

     public WorkflowManager<PocoType> Workflow
     {
        get { return _workflowManger; }
        set { _workflowManger = value; }
     }

     public PocoType PocoObject
     {
        get { return _pocoObject; }
        set { _pocoObject = value; }
     }

     public abstract void Submitted();
     public abstract void Approved();
     // ...
  }

  public class InitialState<PocoType> : 
     WorkflowState<PocoType> where PocoType : BaseClass
  {
     public InitialState(PocoType pocoObject)
     {
        base.PocoObject = pocoObject;
     }

     public override void Submitted()
     {
        if (PocoObject.Submitted())
        {
           // move to approved state if submitted is ok for the poco
           // definition
           Workflow.State = new ApprovedState<PocoType>(this);
        }
     }

     public override void Approved()
     {
        // Not supported state
        throw new InvalidOperationException();
     }

     // ...
  }

  public class ApprovedState<PocoType> :
     WorkflowState<PocoType> where PocoType : BaseClass
  {
     public ApprovedState(WorkflowState<PocoType> state)
     {
        base.PocoObject = state.PocoObject;
        base.Workflow = state.Workflow;
     }

     public override void Submitted()
     {
        // Not supported state
        throw new InvalidOperationException();
     }

     public override void Approved()
     {
        if (PocoObject.Approved())
        {
           // next state ...
           //Workflow.State = ...
           //Send emails
           //Do approval items
        }
     }
  }

  public class WorkflowManager<PocoType> where PocoType : BaseClass
  {
     WorkflowState<PocoType> _state;

     public WorkflowManager(PocoType pocoObject)
     {
        this._state = new InitialState<PocoType>(pocoObject);
        this._state.Workflow = this;
     }

     public WorkflowState<PocoType> State
     {
        get { return _state; }
        set { _state = value; }
     }

     public void RunWorkflow()
     {
        State.Submitted();
        State.Approved();
     }
  }

And a few different examples of running might look like this:

Class1 test = new Class1();
test.PropertyA = "hello";
WorkflowManager<Class1> work_flow_man = new WorkflowManager<Class1>(test);
work_flow_man.RunWorkflow();

Class2 test2 = new Class2();
test2.PropertyC = "cool";
test2.PropertyD = "dude";
WorkflowManager<Class2> work_flow_man2 = new WorkflowManager<Class2>(test2);
work_flow_man2.RunWorkflow();
0
On

Just to give you a perspective on how you could go about centralizing the code that defines the transitions between your states, I cracked my brain a long time ago on the state pattern. At that time generics were pretty new, so it's a bit heavy on that. Either way, it may give you another perspective on how to implement the state pattern.

2
On

Putting methods on an object does not break "POCO design" (there is no standard "POCO design"), as POCO is simply a term used to contrast simple objects like you have with more complicated or heavyweight objects that would make use of larger frameworks. The term is ordinarily used with ORM's to differentiate between objects that are simple CLR types (hence the "Plain Old CLR Objects" acronym) and those that inherit from a common implementation-specific base type. Similarly, POCO can also be used to indicate that the type itself has no direct connection to the ORM or library that's using it, so it could easily be used with others.

So, in short, do it just as you described with methods on the types.