Tuesday 24 February 2009

Implementing the State Software Design Pattern in C#

An article about implementing state in a C# application, using the State Software Design Pattern.

Level: Intermediate to Advanced

About The State Software Design Pattern

Extract from Refactoring to Patterns by Joshua Kerievsky

The primary reason for refactoring to the State pattern is to tame overly complex state-altering conditional logic. Such logic, which tends to spread itself throughout a class, controls an object's state, including how states transition to other states. When you implement the State pattern, you create classes that represent specific states of an object and the transitions between those states. The object that has its state changed is known is Design Patterns as the context. A context delegates state-dependent behavior to a state object. State objects make state transitions at runtime by making the context point to a different state object.

The extract above describes the primary reason for applying the State Design Pattern - to retract overly-complex conditional logic. I have a secondary reason for using this pattern, which is....applications get scattered with code representing state values.

A conventional approach of applying a state process model to a software application is to create a lookup table in a database and add state values(ID's) and descriptions to said table. Application code would then use integer values to represent the state lookup values(ID's) of the table. Applications using these hard-coded integers is susceptible to typos and lots of editing and replacing when somebody decides to change the values during development of the model.

An alternative to scattering integers everywhere is to implement an enumerated type that models the states and then to use the enumerated types in place of the integers. This is an improvement but still presents additional maintenance in keeping the enumeration and the lookup table in sync.

Both of these techniques present problems and neither satisfy the primary and secondary problems I have just described. Let's take a look at an example and see how we can apply a more effective design using the State Software Design Pattern. I've created a mock-up; a fictional business problem we can apply this pattern to.

My Fictional Problem

Users are gathering content from third-parties and creating internal documents with it. The content usually has spelling mistakes and grammatical errors and requires some kind of validation and formal sign-off process.

Bring in the UML Diagrams

A UML State Model Diagram

Below is a simple document state process model I am going to use to solve my problem.

Implementing the State Design Pattern in C#: UML State Diagram

Note. This State Process Model would require more states if it was to be applied to a real-world solution. However, I am keeping this example simple for the purpose of this article.

Applying the State Design Pattern

I'm starting by presenting the kind of State Pattern UML Class Diagram you're already familiar with. I've no doubt you have seen this if you have read any Gang Of Four (GoF) publications, or anything by Martin Fowler about the State Design Pattern.

The UML Class Diagram

State Design Pattern Class Diagram

Looks good doesn't it? It looks "Designed"; Object-Oriented! We're on to a winner. However, I'm sure you've seen many class diagrams like this, understood the diagram at a abstract level, but then wondered "how the hell do I implement this?". Well, hopefully I can help you out here. I'm going to take these diagrams one step further and show you an implementation of how the context interacts with the State Pattern. Before I do this I would like to take a step backwards....

An Unconventional Approach: Dropping the State Lookup Table

I'm not going to create anything in a database to model our state process. I'm not creating a lookup table. My state process model is representing a solution to a business problem; it's a solution created for managing the quality of content going in to internal documents. This is business logic (also known as domain logic), so I don't want the database to be responsible for maintaining it.

Ok, so you've picked yourself up off the floor....let's continue, back to the class diagram and applying the pattern.

Creating the C# Classes for State Pattern

Here is the first pass implementation of the classes in the UML class diagram. Note. I have added a prefix of Base to the DocState and a suffix of State to the concrete states.

    // The Base State Class in the Design Pattern
    

    public abstract class BaseDocState
    {
        public abstract void SignOff( );
    }
    
    // The Concrete State Classes in the Design Pattern
    

    public sealed class NewDocState : BaseDocState
    {
        public override void SignOff( )
        {
        }
    }

    public sealed class PendingReviewState : BaseDocState
    {
        public override void SignOff( )
        {
        }
    }

    public sealed class InReviewState : BaseDocState
    {
        public override void SignOff( )
        {
        }
    }

    public sealed class LiveState : BaseDocState
    {
        public override void SignOff( )
        {
        }
    }
    
    // The Context Class in the Design Pattern
    

    public sealed class DocBll
    {
        public void CreateDoc( )
        {
        }
        public void SubmitForReview( )
        {
        }
        public void SignOffReview( )
        {
        }
        public void GoLive( )
        {
        }
    }

What we have now is a code skeleton that maps on to our class diagram. Time to flesh out the classes and show you how they interact.

The Context Class

I'm beginning with the context class, DocBll, because the design will be easier to digest starting with the problem domain.

The first step is to add a state variable to the context, which represents the current state of a document. At the same time I'll throw in a couple of variables to store the data of the document. The variables won't be used in my examples but it will give you a better feel for the class design.

    public sealed class DocBll
    {
        private string _title;
        private string _xmlContent;        
        
        // The Current State
        
        private BaseDocState _currentState;
        
        //
        
        public void CreateDoc( )
        {
            // code here            

        }

        public void SubmitForReview( )
        {
            // code here            

        }

        public void SignOffReview( )
        {
            // code here            

        }

        public void GoLive( )
        {
            // code here
        }
    } 

Now I'll flesh out one of the public methods. I'm going to start with SubmitForReview method in the context class, and you're probably wondering why. Why don't I start with CreateDoc as it's the first method in our process? I will explain shortly. Here is a fleshed-out SubmitForReview method:

    public void SubmitForReview( )
    {   
        _currentState.SignOff( );
    }  

"Is this it? What is this going to achieve?", I hear you ask. Well, not a lot to be honest. Let's expand it a little....

What we really need is to be able to pass information about the context object, our DocBll, through to our current state pattern object. This wasn't included in our class diagram, but I wanted to keep the diagram as simple as I possibly could. I am going to introduce a new class here, and the sole purpose of this class is to provide a communication link between the context, and the current state.

    public sealed class DocStateArgs
    {
        private DocBll _context;

        public DocStateArgs( DocBll context )
        {
            _context = context;
        }
        
        // expose fields of the _context that the
        // state pattern needs to operate, here....

        // .
        // .
        // .
        // .
        
        
        // fields state pattern fill in here....
        
        private bool _hasSignature = false;

        public bool HasSignature
        {
            get { return _hasSignature; }
            set { _hasSignature = value; }
        }

        private BaseDocState _nextState;

        public BaseDocState NextState
        {
            get { return _nextState; }
            set { _nextState = value; }
        }
    }

Our class takes a context object in the constructor so it can expose only the fields that should be exposed to the state pattern, as opposed to every public member of the context object. Also, our class contains two properties which the state will fill in: The NextState, which is the state the context will be moving to (our transition), and HasSignature, which is a boolean indication of whether or not the context is in a valid state to proceed with the request that was made on it.

By design, I have decided that I don't want our state pattern to have direct database access - I don't want it to persist anything. It should simply validate and provide us with authorisation through the HasSignature property.

Here is the DocState Class with its new signature and the DocBll with an implementation of SubmitForReview.

    // The new Base State Class
    
    public abstract class BaseDocState
    {
        public abstract void SignOff( DocStateArgs args );
    }
    
    // The new Context Class
    
    public sealed class DocBll
    {
        private string _title;
        private string _xmlContent;        
        
        // The Current State
        
        private BaseDocState _currentState;
        
        //
        
        public void CreateDoc( )
        {
            // code here            

        }

        public void SubmitForReview( )
        {
            DocStateArgs args = new DocStateArgs( this );

            _currentState.SignOff( args );

            if ( args.HasSignature )
            {
                _currentState = args.NextState;

                // _sqlDocProvider.Commit( .. );
            }
        }

        public void SignOffReview( )
        {
            // code here            

        }

        public void GoLive( )
        {
            // code here
        }
    }      

Let's get back to the CreateDoc method....

By default, when a class is instanciated all its object members will be initialized to null, which means that when the DocBll context class is instanciated the state variable _currentState will be null, and will still be null CreateDoc is called. This means that the implementation of CreateDoc will have to do something extra. It will have to test for a null value and create a state if the test returns true. Here's an implementation of the CreateDoc method:

    public void CreateDoc( )
    {
        if ( _currentState == null )
        {
            _currentState = new NewDocState( );

            
            // hang on a minute, I don't like this!!!
            // I want to look the same as the others!!!
            // Why don't you implement the Null Object Pattern for me!!! 
        }
    }

There's actually no reason why the CreateDoc method should be implemented differently to the other methods on DocBll, and with the addition of one small class consistency will be brought back in line - there will be no need for the null checks we can see in the previous code example.

The Null Object Software Design Pattern

Extract from Refactoring to Patterns by Joshua Kerievsky

If a client calls a method on a field or variable that is null, an exception may be raised, a system may crash, or similar problems may occur. To protect our systems from such unwanted behavior, we write checks to prevent null fields or variables from being called and, if neccessary, specify alternative behavior to execute when nulls are encountered.

Repeating such null logic in one or two places in a system isn't a problem, but repeating it in multiple places bloats a system with unnecessary code. Compared with other code that is free of null logic, code that is full of it generally takes longer to comprehend and requires more thinking about how to extend.

Here is NullDocState Null Object Pattern implementation:

    public sealed class NullDocState : BaseDocState
    {
        public override void SignOff( DocStateArgs args )
        {
            // validate the context here....


            // if (all is well) then  // set the next state and sign it off....
            {
                args.NextState = new NewDocState( );

                args.HasSignature = true;
            }        
        }
    }

As well as providing you with a Null Object definition, here I have given you a full implementation of the SignOff method.

Here is a complete implementation of the DocBll Context Class

    public sealed class DocBll
    {
        private string _title;
        private string _xmlContent;        
        
        // The Current State initialized to NullDocState
        
        private BaseDocState _currentState = new NullDocState( );
        
        //
        
        public void CreateDoc( )
        {
            DocStateArgs args = new DocStateArgs( this );

            _currentState.SignOff( args );

            if ( args.HasSignature )
            {
                _currentState = args.NextState;

                // _sqlDocProvider.Commit( .. );
            }
        }

        public void SubmitForReview( )
        {
            DocStateArgs args = new DocStateArgs( this );

            _currentState.SignOff( args );

            if ( args.HasSignature )
            {
                _currentState = args.NextState;

                // _sqlDocProvider.Commit( .. );
            }
        }

        public void SignOffReview( )
        {
            // code here            

        }

        public void GoLive( )
        {
            // code here
        }
    }

The Concrete State Classes

Here is the implementation of the modelled conrete states:

    public sealed class NewDocState : BaseDocState
    {
        public override void SignOff( DocStateArgs args )
        {
            // validate the context here....


            // if (all is well) then  // set the next state and sign it off....
            {
                args.NextState = new PendingReviewState( );

                args.HasSignature = true;
            }
        }
    }
    
    
    public sealed class PendingReviewState : BaseDocState
    {
        public override void SignOff( DocStateArgs args )
        {
            // validate the context here....


            // if (all is well) then  // set the next state and sign it off....
            {
                args.NextState = new InReviewState( );

                args.HasSignature = true;
            }
        }
    }
    
    
    public sealed class InReviewState : BaseDocState
    {
        public override void SignOff( DocStateArgs args )
        {
            // validate the context here....


            // if (all is well) then  // set the next state and sign it off....
            {
                args.NextState = new LiveState( );

                args.HasSignature = true;
            }
        }
    }
    
    
    public sealed class LiveState : BaseDocState
    {
        public override void SignOff( DocStateArgs args )
        {
            args.HasSignature = false; // end of state model
            
            // the above is just a cheap way of indicating the
            // end of the state model for our example.  In a
            // real application you'd probably want to be more
            // informative about why it can't be signed off.
        }
    }

To Conclude Implementing the State Design Pattern in C#

I have given you a very simplistic solution, demonstrating the use of the State Design Pattern, and the transformation of a business problem through analysis, class design, through to the implementation. I hope my writing is clear and you feel you now have the understanding to apply these techniques in your own solutions.

Missing from this Article

What this article didn't cover is how to persist a context's state to a database and then load the context object with state. I will be following this article up with a part 2 where I will add a little more functionality to these classes and also show you how to persist and load.

Useful Links

No comments:

Post a Comment