State - Design Pattern

State - Design Pattern

Software Architecture Simplified

ยท

4 min read

Objective ๐ŸŽฏ

Allow an object alter its own behavior when its state changes.

Type โœ…

โœ”๏ธBehavioral: Describes how objects interact/communicate between themselves.

โŒCreational: Describes how to instantiate an object without large and complex.

โŒStructural: Describes how objects/classes are composed to form larger structures.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Context:

  • Represents the class that will be used by the Client
  • Maintains an instance of a ConcreteState class that represents the current state

โ€ข State:

  • Defines an interface for encapsulating the behavior associated with a particular state of the Context.

โ€ข Concrete State:

  • Represents a specific behavior for a particular Context

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class StateStructural
    {
        public static void Execute()
        {
            Context lContext = new Context(new ConcreteStateA());
            lContext.Request(); // State A > State B
            lContext.Request(); // State B > State C
            lContext.Request(); // State C > State A
        }
    }
    public abstract class State
    {
        public abstract void Handle(Context prContext);
    }

    public class ConcreteStateA : State
    {
        public override void Handle(Context prContext)
        {
            Console.WriteLine("Concrete State A - Executing method \"Handle\" and changing state to \"B\"");
            prContext._State = new ConcreteStateB();
        }
    }

    public class ConcreteStateB : State
    {
        public override void Handle(Context prContext)
        {
            Console.WriteLine("Concrete State B - Executing method \"Handle\" and changing state to \"C\"");
            prContext._State = new ConcreteStateC();
        }
    }

    public class ConcreteStateC : State
    {
        public override void Handle(Context prContext)
        {
            Console.WriteLine("Concrete State C - Executing method \"Handle\" and changing state back to \"A\"");
            prContext._State = new ConcreteStateA();
        }
    }

    public class Context
    {
        public State _State;

        public Context(State prState)
        {
            _State = prState;
        }

        public void Request()
        {
            _State.Handle(this);
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class StatePractical
    {
        public static void Execute()
        {
            DocumentContext lDocumentContext = new DocumentContext(new Draft());

            Console.WriteLine("\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext._DocumentContent = "Lorem ipsum";
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext._DocumentContent = "Lorem ipsum dolor sit amet";
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext.GoToNextStatus();
        }
    }

    public abstract class DocumentState
    {
        public abstract void GoToNextStatus(DocumentContext prDocumentContext);
        public abstract void Close(DocumentContext prDocumentContext);
        public abstract void Publish(DocumentContext prDocumentContext);
    }

    public class Draft : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            if (string.IsNullOrWhiteSpace(prDocumentContext._DocumentContent))
                Console.WriteLine("Is not possible to send a document with empty content for review");
            else
            {
                Console.WriteLine("Document submitted to review");
                prDocumentContext.DocumentState = new InReview();
            }
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to close a document in the status \"Draft\"");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a document in the status \"Draft\"");
        }
    }

    public class InReview : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            if (prDocumentContext._DocumentContent.Length < 15)
            {
                Console.WriteLine("Document rejected as the content has less than 15 characters");
                prDocumentContext.DocumentState = new Rejected();
            }
            else
            {
                Console.WriteLine("Review approved");
                prDocumentContext.DocumentState = new Approved();
            }
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document closed");
            prDocumentContext.DocumentState = new Closed();
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a document in the status \"In Review\"");
        }
    }

    public class Approved : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Publish(prDocumentContext);
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document closed");
            prDocumentContext.DocumentState = new Closed();
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document published");
            prDocumentContext.DocumentState = new Published();
        }
    }

    public class Rejected : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already in the last status (Rejected)");
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to close a rejected document");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a document in the status \"Rejected\"");
        }
    }

    public class Published : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already in the last status (Published)");
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to close a published document");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already published");
        }
    }

    public class Closed : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already in the last status (Closed)");
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already closed");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a closed document");
        }
    }

    public class DocumentContext
    {
        private DocumentState _DocumentState;
        public string _DocumentContent;

        public DocumentContext(DocumentState prDocumentState)
        {
            _DocumentState = prDocumentState;
        }

        public DocumentState DocumentState
        {
            get { return _DocumentState; }
            set
            {
                if (value != _DocumentState)
                {
                    Console.WriteLine($"<< Document state changed from {_DocumentState.GetType().Name} to {value.GetType().Name} >>");
                    _DocumentState = value;
                }
            }
        }

        public void GoToNextStatus() { _DocumentState.GoToNextStatus(this); }
        public void Close() { _DocumentState.Close(this); }
        public void Publish() { _DocumentState.Publish(this); }
    }

Output

image.png

Source Code ๐ŸŽฒ

github.com/VictorLins/DesignPatterns

Did you find this article valuable?

Support Victor Lins by becoming a sponsor. Any amount is appreciated!

ย