Intermediate

C-Sharp Design Patterns State

At the end of this course, you will have the knowledge to elegantly manage state in your C# projects using the State Design Pattern.

Technology: C#, .NET, Visual Studio 2019, WPF


Table of Contents

  1. Course Overview
  2. Module introduction — What is State
  3. The demonstration project
  4. Naive approach to managing state
  1. Complete the naive approach
  1. Why use the State Design Pattern?
  2. The State Design Pattern — Fundamental Concepts
  1. Implementation — Abstract State
  2. Implementation — Concrete States
  3. Implementation — The Context and the Context State relationship
  4. Implementation — Code the behaviors of each state
  1. Conclusion and recap
  1. Pattern structure diagram
  2. State transition diagram
  3. Summary of created classes

1. Course Overview

Welcome to the C# Design Patterns: State training. The instructor, Marc Gilbert, is the owner and lead developer of Frivolous Twist, LLC, a small consulting company specializing in creating games and applications for mobile platforms.

Course objectives

This course answers a fundamental question that every C# developer ends up asking:

How ​​to properly manage state in an application without drowning its methods in complex conditional logic that is difficult to maintain?

Through this course, you will learn:

  • What state is and the different ways to manage it in an application
  • A conceptual overview of the State Design Pattern: its principles, components and benefits
  • How to concretely implement this pattern in a Visual Studio project
  • Why this pattern makes code cleaner, easier to maintain and extremely extensible

At the end of this course, you will have the knowledge to elegantly manage state in your C# projects using the State Design Pattern.


2. Module Introduction — What is State?

Module: The State Design Pattern — Duration: 42m 10s

Status definition

State is such a ubiquitous and intuitive concept that we rarely have to think about it consciously. At its most basic definition:

State can be defined as the condition of a variable thing.

The term variable is used here in its common linguistic sense — something that can change — and not in the computational or mathematical sense of the term.

Concrete examples of status

In nature: From school, we learn the states of matter: solid, liquid and gas. The same substance can have different properties and behaviors depending on its state — water flows, steam rises.

In Technology: The device on which you are taking this course must be connected to the Internet to access the content. If you attempt the same actions on a disconnected device, you will get an error. That is, your device can be in a connected or disconnected state, and it will behave differently depending on this state.

In software development: As developers, we are constantly faced with status questions. Let’s take the example of an order processing application:

  • Is an order new, processing, cancelled or completed?
  • Can we modify a canceled order?
  • Can a completed order be canceled?

The types of objects in an application and their behaviors are virtually limitless, but no matter what type of application you are developing, you will inevitably encounter state questions.

The solution: the State Design Pattern

The State Design Pattern is one of 23 design patterns documented by the Gang of Four (GoF). It specifically solves two design challenges:

  1. How can an object change its behavior when its internal state changes?
  2. How can state-specific behaviors be defined so that new states can be added without changing the behaviors of existing states?

These challenges seem abstract expressed like this, but the course makes them concrete through a practical demonstration project.


3. The demonstration project

Clip: The Demo Project

The demo project is a WPF application created with Visual Studio 2019, available in the exercise files under the name StateDesignPattern.zip.

Description of the reservation system

The project models a simplified reservation system for a single upcoming event. Here is the process flow:

  1. A user creates a new reservation (booking) for an event
  2. It submits information for processing
  3. If the processing is successful → the reservation is considered confirmed (booked)
  4. If processing fails → a new reservation is created and the process starts again
  5. When the event date passes → the reservation is closed (closed)
  • If this happens while the reservation is confirmed → it is assumed that the user attended the event
  • This can also happen before the reservation is submitted
  1. A reservation can be cancelled by the user at any time before it is closed, which also puts it in the closed state

Project structure

The WPF project contains:

  • A simple user interface with the necessary code-behind
  • Code to simulate external processing (asynchronous processing)
  • Business logic (to be implemented throughout the course)

The Booking class (starting point)

In Solution Explorer, the Logic folder contains the Booking.cs file. This class contains:

ElementTypeDescription
ViewMainWindowReference to the main window to interact with the UI
ExpectedstringParticipant Name
TicketCountintNumber of tickets
BookingIDintUnique booking identifier

The class also contains stub methods:

  • The first 3 serve as an interface to the UI (this is where the logic will be implemented)
  • ProcessingComplete — will be called by the external system once processing is complete
  • ShowState — utility method to display the reservation status in the UI

Key principle: The Booking class delegates all UI functions to the MainWindow, and the UI interacts with the Booking class through its public methods.


4. Naive approach to manage state

Clip: A Naive Approach to Managing State

The best way to understand the State Pattern is to understand the challenges it was designed to overcome. To do this, the course starts with a naive implementation of the reservation system — that is, without using the State Pattern.

4.1 Initial implementation — creating a reservation

When a reservation is created, three things should happen:

  1. Assign a booking ID (BookingID)
  2. View booking status
  3. Provide a way for the user to enter their information
public Booking(MainWindow view)
{
    View = view;
    // Attribuer un ID de réservation
    BookingID = new Random().Next();
    // Afficher le statut
    ShowState("New");
    // Activer la saisie des données
    View.ShowEntryPage();
}

After this implementation, when a user clicks the New Booking button, a new reservation is created, its ID and New status are displayed, and the user sees a form to enter their information.

4.2 Handling cancellation and expiration with a boolean

At this stage of the reservation (New state), three things can happen:

  1. Event date passes → reservation is closed
  2. User cancels → reservation is closed
  3. User submits information → processing in progress

For the first two cases, the logic in the Cancel and DatePassed methods is simple:

public void Cancel()
{
    ShowState("Closed");
    View.ShowStatusPage("Canceled by user");
}

public void DatePassed()
{
    ShowState("Closed");
    View.ShowStatusPage("Booking expired");
}

Issue: A closed reservation can be re-cancelled or re-expired

Without state management, an already closed reservation may be canceled or expired again — which makes no sense. A common approach is to use a boolean field:

public class Booking
{
    // ...
    private bool isNew;

    public Booking(MainWindow view)
    {
        View = view;
        BookingID = new Random().Next();
        isNew = true;  // La réservation commence dans l'état "New"
        ShowState("New");
        View.ShowEntryPage();
    }

    public void Cancel()
    {
        if (isNew == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Canceled by user");
            isNew = false;  // La réservation n'est plus "New"
        }
        else
        {
            View.ShowError("Cannot cancel a closed booking", "Error");
        }
    }

    public void DatePassed()
    {
        if (isNew == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Booking expired");
            isNew = false;  // La réservation n'est plus "New"
        }
        // Si la réservation est déjà fermée, on ignore
    }
}

Result: A reservation in the New state can be canceled or expired, moving to the Closed state, where it cannot be re-cancelled or re-expired. Using a boolean to handle state this way is fairly common practice — and if those are the only requirements, we might stop there. But the rest of the course shows the limits of this approach.


5. Complete the naive approach

Clip: Completing the Naive Approach

5.1 Submission and Pending status

Four things must be implemented for submission:

  1. Update reservation data
  2. Show status in UI
  3. Submit reservation for processing
  4. Manage processing response

A new state is introduced: Pending (waiting for processing). This requires a second Boolean field isPending:

public class Booking
{
    // ...
    private bool isNew;
    private bool isPending;
    private CancellationTokenSource cancelToken;

    public void SubmitDetails(string attendee, int ticketCount)
    {
        if (isNew == true)
        {
            // Mettre à jour les données
            Attendee = attendee;
            TicketCount = ticketCount;

            // Préparer l'annulation
            cancelToken = new CancellationTokenSource();

            // Soumettre au système externe (simulé de façon asynchrone)
            StaticFunctions.ProcessBooking(this, ProcessingComplete, cancelToken);

            // Mettre à jour l'UI
            ShowState("Pending");
            View.ShowStatusPage("Processing Booking");

            // Changer l'état
            isNew = false;
            isPending = true;
        }
    }

Manage treatment response

The ProcessingComplete method is called by the external system after processing is complete. It receives a ProcessingResult which can be a success, a failure or a cancellation:

    public void ProcessingComplete(ProcessingResult result)
    {
        isPending = false;

        if (result == ProcessingResult.Success)
        {
            // Afficher que la réservation est confirmée
            ShowState("Booked");
            View.ShowStatusPage("Enjoy the event!");
            isBooked = true;
        }
        else if (result == ProcessingResult.Fail)
        {
            // Afficher une erreur et réinitialiser
            View.ShowError("Processing failed", "Error");
            BookingID = new Random().Next();
            isNew = true;  // Remettre à true pour pouvoir soumettre à nouveau
            ShowState("New");
            View.ShowEntryPage();
        }
        // Si annulé par l'utilisateur, la méthode Cancel s'en est chargée
    }

Cancel a pending reservation

The Cancel method must now handle the cancellation of a reservation in Pending state in addition to New state:

    public void Cancel()
    {
        if (isNew == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Canceled by user");
            isNew = false;
        }
        else if (isPending == true)
        {
            // Informer le système externe que l'utilisateur a annulé
            cancelToken.Cancel();
            // isPending sera remis à false dans ProcessingComplete
        }
    }

5.2 Booked status management

The reservation is confirmed (Booked) after successful processing. So that the Booking class can know that it is in this state, we add a third Boolean field isBooked.

The DatePassed method must now handle the New and Booked cases:

    public void DatePassed()
    {
        if (isNew == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Booking expired");
            isNew = false;
        }
        else if (isBooked == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("We hope you enjoyed the event");
            isBooked = false;
        }
    }

The Cancel method must also handle the Booked case:

    // Dans la méthode Cancel
    else if (isBooked == true)
    {
        ShowState("Closed");
        View.ShowStatusPage("Booking canceled: Expect a refund");
        isBooked = false;
    }

5.3 Full code of naive approach

Here is the complete implementation of the Booking class with the naive approach:

public class Booking
{
    public MainWindow View { get; set; }
    public string Attendee { get; set; }
    public int TicketCount { get; set; }
    public int BookingID { get; set; }

    // Champs booléens pour suivre l'état courant
    private bool isNew;
    private bool isPending;
    private bool isBooked;
    private CancellationTokenSource cancelToken;

    public Booking(MainWindow view)
    {
        View = view;
        BookingID = new Random().Next();
        isNew = true;
        ShowState("New");
        View.ShowEntryPage();
    }

    public void SubmitDetails(string attendee, int ticketCount)
    {
        if (isNew == true)
        {
            Attendee = attendee;
            TicketCount = ticketCount;
            cancelToken = new CancellationTokenSource();
            StaticFunctions.ProcessBooking(this, ProcessingComplete, cancelToken);
            ShowState("Pending");
            View.ShowStatusPage("Processing Booking");
            isNew = false;
            isPending = true;
        }
    }

    public void Cancel()
    {
        if (isNew == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Canceled by user");
            isNew = false;
        }
        else if (isPending == true)
        {
            cancelToken.Cancel();
        }
        else if (isBooked == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Booking canceled: Expect a refund");
            isBooked = false;
        }
    }

    public void DatePassed()
    {
        if (isNew == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("Booking expired");
            isNew = false;
        }
        else if (isBooked == true)
        {
            ShowState("Closed");
            View.ShowStatusPage("We hope you enjoyed the event");
            isBooked = false;
        }
    }

    public void ProcessingComplete(ProcessingResult result)
    {
        isPending = false;

        if (result == ProcessingResult.Success)
        {
            ShowState("Booked");
            View.ShowStatusPage("Enjoy the event!");
            isBooked = true;
        }
        else if (result == ProcessingResult.Fail)
        {
            View.ShowError("Processing failed", "Error");
            BookingID = new Random().Next();
            isNew = true;
            ShowState("New");
            View.ShowEntryPage();
        }
        // Si annulé (ProcessingResult.Canceled), rien à faire ici
    }

    public void ShowState(string state)
    {
        // Affiche l'état dans l'UI via MainWindow
    }
}

6. Why use State Design Pattern?

Clip: Why Use the State Design Pattern?

The two design challenges

The State Design Pattern was developed to solve two main challenges:

  1. How can an object change its behavior when its internal state changes?
  2. How can state-specific behaviors be defined so that new states can be added without changing the behaviors of existing states?

Naive approach analysis

Let’s take a single behavior from our naive implementation: cancel a reservation.

This behavior actually addresses first challenge: when a reservation is in New state, canceling updates the UI and notifies the user. When in Pending state, the Cancel method of the cancelToken is called. If it is Booked, the behavior changes again. So the reservation changes behavior depending on its internal state.

But here’s the problem: to accommodate each state, we had to modify the behavior itself. We had to:

  • Create Boolean fields (isNew, isPending, isBooked) to track different states
  • Query these fields with a string of conditions (if/else if) which becomes more complex with each new state added
  • Manage the values of these fields, digging through the code to find exactly where to set them to true or false, which affects not only the current method but potentially several other methods in the code

The fundamental problem

The more states we have, the more their behaviors become interdependent. We end up spending more time managing Boolean field values ​​than coding the behaviors themselves.

This becomes more and more problematic with each added state, resulting in code:

  • Hard to debug
  • Difficult to read and maintain
  • Very fragile — each change risks breaking something else

If you work in a team, one member may change one behavior and break another without realizing it.

The promise of the State Pattern

By responding to the second challenge, the State Pattern:

  • Minimizes conditional complexity, eliminating the need for boolean fields and countless if/else
  • Product code more modular
  • Produces code easier to read and maintain
  • Produces code less difficult to debug
  • Produces considerably more extensible code — new states can be added without changing existing states

7. The State Design Pattern — Fundamental Concepts

Clip: The State Design Pattern

Central principle

The State Design Pattern solves state-related issues by encapsulating state-specific behaviors in separate state objects. A class then delegates the execution of its behaviors to one of these state objects at a time, rather than implementing the state-specific behaviors within its own.

7.1 The three main components

┌─────────────────────────────────────────────────────────────┐
│                    STATE DESIGN PATTERN                      │
│                                                             │
│   ┌──────────────┐         ┌──────────────────────────┐    │
│   │   Context    │────────▶│    Abstract State         │    │
│   │              │         │  (classe abstraite)       │    │
│   │ currentState │         │ + EnterState()            │    │
│   │              │         │ + Cancel()                │    │
│   └──────────────┘         │ + DatePassed()            │    │
│                            │ + EnterDetails()          │    │
│                            └──────────────────────────┘    │
│                                        △                    │
│                                        │ hérite             │
│                    ┌───────────────────┼──────────────┐    │
│                    │                   │              │    │
│             ┌──────────┐      ┌──────────────┐  ...  │    │
│             │Concrete  │      │  Concrete    │       │    │
│             │State A   │      │  State B     │       │    │
│             └──────────┘      └──────────────┘       │    │
└─────────────────────────────────────────────────────────────┘
ComponentDescription
BackgroundClass that maintains an instance of a concrete state as its current state. Provides the way to set/change its state.
Abstract StateAbstract class that defines an interface encapsulating all state-specific behavior.
Concrete State(s)Abstract state subclasses that implement behaviors specific to a particular state of the context.

Interaction

  • Concrete states derive from the abstract state by implementing the interfaces defined therein
  • The context maintains a reference to one of the concrete states as its current state, via the abstract state base class
  • The context interacts with concrete states through the interface defined in the abstract base class

7.2 Identification of states and transitions for reservation

Before coding, we identify:

1. Possible states:

StateDescription
NewNew reservation, awaiting submission
PendingSubmitted, awaiting processing response
BookedProcessing successful, booking confirmed
ClosedReservation closed (cancelled or expired)

2. Transitions between states:

Starting stateTriggerArrival status
NewPast event dateClosed
NewCancellation by userClosed
NewSubmission of informationPending
PendingSuccessful treatmentBooked
PendingFailed treatmentNew
PendingCancellation by userClosed
BookedPast event dateClosed
BookedCancellation by userClosed

3. The initial state:

The initial status of any reservation is New.

Key advantage of the State Pattern

Since an object can only be in one state at a time, it is never necessary to check that it is not in another state — it is simply impossible under this design. This built-in restriction is what makes the State Pattern so powerful: it allows you to focus only on what needs to happen within a state, without worrying about how it might affect other states.


8. Implementation — Abstract State

Clip: The Abstract State

Setting up the context in the UI

The BookingContext class (in the Logic folder) will serve as context in our implementation. Minor modifications are necessary in the code-behind of the MainWindow (MainWindow.xaml.cs):

// Dans MainWindow.xaml.cs
// Avant :
private Booking booking;

// Après :
private BookingContext booking;

// Dans btnCreate_Click :
// Avant :
booking = new Booking(this);

// Après :
booking = new BookingContext(this);

Creation of the abstract class BookingState

In Solution Explorer, right-click on the Logic folder → Add ClassBookingState.

The class must be public and abstract. Abstract classes cannot be instantiated directly — they serve as a base (prototype or template) for other classes.

public abstract class BookingState
{
    // Méthode appelée lorsqu'on entre dans cet état
    // Le paramètre BookingContext donne une référence au context
    public abstract void EnterState(BookingContext booking);

    // Méthode appelée lorsque l'utilisateur annule
    public abstract void Cancel(BookingContext booking);

    // Méthode appelée lorsque la date de l'événement passe
    public abstract void DatePassed(BookingContext booking);

    // Méthode appelée lorsque l'utilisateur soumet ses informations
    public abstract void EnterDetails(BookingContext booking, string attendee, int ticketCount);
}

Note on abstract methods: By declaring these methods abstract, we indicate that they must be redefined by the classes which derive from BookingState (the concrete states). They have no implementation in the base class itself.

These four methods constitute the interface of our abstract state. It is through this interface that the context will interact with the concrete states.


9. Implementation — Concrete States

Clip: Concrete States

Creation of the four concrete state classes

In Visual Studio, create four new classes in the Logic folder:

  1. NewState
  2. ClosedState
  3. PendingState
  4. BookedState

Each derives from BookingState:

// Structure de base pour chaque état concret
public class NewState : BookingState
{
    public override void EnterState(BookingContext booking)
    {
        throw new NotImplementedException(); // À supprimer
    }

    public override void Cancel(BookingContext booking)
    {
        throw new NotImplementedException(); // À supprimer
    }

    public override void DatePassed(BookingContext booking)
    {
        throw new NotImplementedException(); // À supprimer
    }

    public override void EnterDetails(BookingContext booking, string attendee, int ticketCount)
    {
        throw new NotImplementedException(); // À supprimer
    }
}

Visual Studio Tip: When the NewState: BookingState class is defined, Visual Studio underlines the class name in red if abstract methods are not implemented. Right click → Quick Actions and RefactoringImplement abstract interface to automatically generate the stubs of all required methods.

Summary of components in place

At this point, all necessary components are in place:

ComponentRole in the patternClass
BackgroundMaintains current stateBookingContext
Abstract StateBehavior interfaceBookingState
Concrete StateNew StateNewState
Concrete StateStatus ClosedClosedState
Concrete StatePending StatusPendingState
Concrete StateBooked ConditionBookedState

10. Implementation — The Context and the Context State relationship

Clip: Context and State

Full implementation of BookingContext

For a class to act as context, it must:

  1. Maintain an instance of a concrete state as its current state
  2. Provide a way to set/change this current state
  3. Delegate the execution of behaviors to the current state
public class BookingContext
{
    // Propriétés de données
    public MainWindow View { get; set; }
    public string Attendee { get; set; }
    public int TicketCount { get; set; }
    public int BookingID { get; set; }

    // Champ privé maintenant l'état courant
    private BookingState currentState;

    // Constructeur : définit l'état initial
    public BookingContext(MainWindow view)
    {
        View = view;
        // L'état initial est "New"
        TransitionToState(new NewState());
    }

    // Méthode pour changer d'état
    public void TransitionToState(BookingState state)
    {
        // 1. Définir l'état courant
        currentState = state;
        // 2. Appeler la méthode EnterState du nouvel état
        //    "this" = référence au context courant
        currentState.EnterState(this);
    }

    // Délégation des comportements à l'état courant
    public void SubmitDetails(string attendee, int ticketCount)
    {
        currentState.EnterDetails(this, attendee, ticketCount);
    }

    public void Cancel()
    {
        currentState.Cancel(this);
    }

    public void DatePassed()
    {
        currentState.DatePassed(this);
    }

    // Méthode utilitaire pour afficher l'état dans l'UI
    public void ShowState(string state)
    {
        // Affiche l'état dans l'interface utilisateur
    }
}

Analysis of TransitionToState

public void TransitionToState(BookingState state)
{
    currentState = state;           // Ligne 1
    currentState.EnterState(this);  // Ligne 2
}
  • Line 1: Simply defines the currentState field to the instance of a concrete state passed as a parameter
  • Line 2: Calls the EnterState method of this concrete state. Each concrete state uses EnterState for any code to execute when the state is first entered
  • this represents the current instance of BookingContext, giving the current state a reference to the context

Principle of delegation

When DatePassed() is called on the context, instead of implementing behaviors right there, the call is passed to a method in currentState. That is, the context (BookingContext) delegates responsibility for managing DatePassed and all state-specific behaviors associated with an instance of a concrete state — its currentState.

This delegation principle applies to both SubmitDetails and Cancel: no behavioral code is executed in the context itself. It is the current state that determines what happens.

Summary of fully implemented BookingContext

The BookingContext is a fully implemented context for our State Pattern because it:

  • Maintains a reference to a concrete instance of BookingState in the currentState field
  • Provides a way to set currentState in its TransitionToState method
  • Sets the initial state of the process in its constructor (call to TransitionToState(new NewState()))
  • Interacts with currentState via methods defined in the abstract base class BookingState

11. Implementation — Code the behaviors of each state

Clip: Implementing the Pattern

11.1 NewState

The New state is the initial state of any reservation.

Behaviors when entering this state (EnterState):

  • Assign Reservation ID
  • Show reservation status
  • Enable data entry

Behaviors available in this state:

  • EnterDetails → transition to PendingState
  • Cancel → transition to ClosedState (reason: “Booking Canceled”)
  • DatePassed → transition to ClosedState (reason: “Booking Expired”)
public class NewState : BookingState
{
    public override void EnterState(BookingContext booking)
    {
        // Attribuer un ID de réservation
        booking.BookingID = new Random().Next();
        // Afficher l'état
        booking.ShowState("New");
        // Activer la saisie des données
        booking.View.ShowEntryPage();
    }

    public override void EnterDetails(BookingContext booking, string attendee, int ticketCount)
    {
        // Mettre à jour les données de la réservation
        booking.Attendee = attendee;
        booking.TicketCount = ticketCount;
        // Transitionner vers l'état Pending
        booking.TransitionToState(new PendingState());
    }

    public override void Cancel(BookingContext booking)
    {
        // Transitionner vers Closed avec la raison "Booking Canceled"
        booking.TransitionToState(new ClosedState("Booking Canceled"));
    }

    public override void DatePassed(BookingContext booking)
    {
        // Transitionner vers Closed avec la raison "Booking Expired"
        booking.TransitionToState(new ClosedState("Booking Expired"));
    }
}

Note: This code is identical to that of the naive approach, except that we now call the methods via a reference to the context (booking.) rather than directly in the class.

11.2 ClosedState

The Closed status represents a completed reservation. A reservation can be closed for several reasons, hence the constructor parameter.

Behaviors when entering this state (EnterState):

  • Show status “Closed”
  • Show reason for closing

Behaviors in this state:

  • Any action (Cancel, DatePassed, EnterDetails) shows an error because these actions are invalid for a closed reservation
public class ClosedState : BookingState
{
    // Champ pour stocker la raison de la fermeture
    private string reasonClosed;

    // Constructeur avec la raison de la fermeture
    public ClosedState(string reason)
    {
        reasonClosed = reason;
    }

    public override void EnterState(BookingContext booking)
    {
        // Afficher que la réservation est fermée
        booking.ShowState("Closed");
        // Afficher la raison
        booking.View.ShowStatusPage(reasonClosed);
    }

    public override void Cancel(BookingContext booking)
    {
        // Action invalide pour une réservation fermée
        booking.View.ShowError("Invalid action for the state", "Closed Booking Error");
    }

    public override void DatePassed(BookingContext booking)
    {
        // Action invalide pour une réservation fermée
        booking.View.ShowError("Invalid action for the state", "Closed Booking Error");
    }

    public override void EnterDetails(BookingContext booking, string attendee, int ticketCount)
    {
        // Action invalide pour une réservation fermée
        booking.View.ShowError("Invalid action for the state", "Closed Booking Error");
    }
}

Design note: Technically, the Cancel, DatePassed and EnterDetails methods could be left empty for the Closed state — the closed reservation would simply ignore them. But it is better to display an error message to inform the user.

11.3 PendingState

The Pending state manages submission to the external system and response management.

Behaviors when entering this state (EnterState):

  • Create a CancellationTokenSource to enable cancellation
  • Show “Pending” status
  • Inform user that processing is in progress
  • Submit reservation to external system

Available behaviors:

  • Cancel → calls cancelToken.Cancel() to inform the external system
  • DatePassed → ignored (pending reservations do not respond to this event)
  • ProcessingComplete → handles response (success → BookedState, failure → NewState, canceled → ClosedState)
using System.Threading;

public class PendingState : BookingState
{
    // Champ pour gérer l'annulation du traitement asynchrone
    private CancellationTokenSource cancelToken;

    public override void EnterState(BookingContext booking)
    {
        // Initialiser le token d'annulation
        cancelToken = new CancellationTokenSource();

        // Mettre à jour l'UI
        booking.ShowState("Pending");
        booking.View.ShowStatusPage("Processing Booking");

        // Soumettre au système externe (simulé)
        // ProcessingComplete sera appelé par le système externe une fois terminé
        StaticFunctions.ProcessBooking(booking, ProcessingComplete, cancelToken);
    }

    public override void Cancel(BookingContext booking)
    {
        // Informer le système externe que l'utilisateur a annulé
        cancelToken.Cancel();
    }

    public override void DatePassed(BookingContext booking)
    {
        // Les réservations en attente ne répondent pas à la date qui passe
        // (comportement intentionnellement vide)
    }

    public override void EnterDetails(BookingContext booking, string attendee, int ticketCount)
    {
        // Impossible de soumettre des informations pendant le traitement
        // (comportement intentionnellement vide)
    }

    // Appelé par le système externe lorsque le traitement est terminé
    public void ProcessingComplete(BookingContext booking, ProcessingResult result)
    {
        switch (result)
        {
            case ProcessingResult.Success:
                // Traitement réussi → transitionner vers Booked
                booking.TransitionToState(new BookedState());
                break;

            case ProcessingResult.Fail:
                // Traitement échoué → afficher une erreur et retourner à New
                booking.View.ShowError("Processing failed", "Error");
                booking.TransitionToState(new NewState());
                break;

            case ProcessingResult.Canceled:
                // Annulé par l'utilisateur → fermer la réservation
                booking.TransitionToState(new ClosedState("Processing Canceled"));
                break;
        }
    }
}

11.4 BookedState

The Booked status represents a confirmed reservation after successful processing.

Behaviors when entering this state (EnterState):

  • Show status “Booked”
  • Inform user to take advantage of event

Available behaviors:

  • Cancel → transition to ClosedState (reason: “Booking canceled: Expect a refund”)
  • DatePassed → transition to ClosedState (reason: “We hope you enjoyed the event”)
public class BookedState : BookingState
{
    public override void EnterState(BookingContext booking)
    {
        // Afficher que la réservation est confirmée
        booking.ShowState("Booked");
        // Informer l'utilisateur
        booking.View.ShowStatusPage("Enjoy the Event");
    }

    public override void Cancel(BookingContext booking)
    {
        // L'utilisateur annule une réservation confirmée → remboursement prévu
        booking.TransitionToState(new ClosedState("Booking canceled: Expect a refund"));
    }

    public override void DatePassed(BookingContext booking)
    {
        // La date de l'événement est passée → on suppose que l'utilisateur a assisté
        booking.TransitionToState(new ClosedState("We hope you enjoyed the event"));
    }

    public override void EnterDetails(BookingContext booking, string attendee, int ticketCount)
    {
        // Impossible de soumettre des informations quand la réservation est confirmée
        // (comportement intentionnellement vide ou erreur)
    }
}

12. Conclusion and recap

Clip: Conclusion Module

12.1 Complete behavior test

Here is a complete summary of the application behavior with the implemented State Pattern:

Status New

  • Can be canceled → transition to Closed (message: “Canceled by user”)
  • Date may pass → transition to Closed (message: “Booking Expired”)
  • Submission of information → transition to Pending

Status Closed

  • Cannot be submitted → displays an error
  • Cannot be undone → displays an error
  • Does not respond to passing date → displays an error

Pending Status

  • Processing successful → transition to Booked
  • Processing failed → shows an error, then returns to New
  • Does not respond to passing date
  • Can be canceled → transition to Closed (message: “Processing Canceled”)

Status Booked (Reservation confirmed)

  • Can be canceled → transition to Closed (message: “Expect a refund”)
  • Date may pass → transition to Closed (message: “We hope you enjoyed the event”)

Everything works perfectly, and you have managed the behavior of each of these states without resorting to Boolean fields or the conditional complexity of the naive approach.

12.2 Advantages of the State Pattern

AdvantageDescription
Modular codeThe logic of each state is maintained in its own class
Ease of readingEach state class is short and focused on a single state
Ease of maintenanceChanges to one state do not affect others
Simplified debuggingBugs are isolated to the affected status class
Maximum expandabilityNew states can be added without refactoring existing code
Elimination of conditional complexityNo more cascading if/else if, no more Boolean fields to manage
State independenceChanging the behavior of one state does not affect other states

12.3 Potential disadvantages

For completeness, here are the potential disadvantages of the State Pattern:

1. Longer setup time The pattern takes a little time to configure. However, this time is largely recovered (and beyond) during future developments of the code. It is therefore less of an inconvenience than an investment.

2. No more moving parts The naive implementation was done in a single class, while the implementation with the State Pattern required six (BookingContext, BookingState, NewState, ClosedState, PendingState, BookedState). This is not bad in itself, but it can potentially make the code more resource intensive and, in extreme cases, can negatively affect performance. However, it is rare to encounter performance issues due to using this pattern.

Conclusion: Despite these minor drawbacks, the State Design Pattern is a great addition to the developer’s toolbox. If you have objects in your applications with easily identifiable states and the code in your methods is starting to look like spaghetti, the State Design Pattern may be just the tool you need.


13. Pattern structure diagram

                    ┌──────────────────────────┐
                    │       BookingContext      │
                    │        (Context)          │
                    ├──────────────────────────┤
                    │ + View: MainWindow        │
                    │ + Attendee: string        │
                    │ + TicketCount: int        │
                    │ + BookingID: int          │
                    │ - currentState: BookingState│
                    ├──────────────────────────┤
                    │ + BookingContext(view)    │
                    │ + TransitionToState(state)│
                    │ + SubmitDetails(...)      │
                    │ + Cancel()               │
                    │ + DatePassed()           │
                    │ + ShowState(state)       │
                    └──────────┬───────────────┘
                               │ utilise (via currentState)
                               ▼
                    ┌──────────────────────────┐
                    │      BookingState         │
                    │    (Abstract State)       │
                    ├──────────────────────────┤
                    │ + EnterState(booking)*    │
                    │ + Cancel(booking)*        │
                    │ + DatePassed(booking)*    │
                    │ + EnterDetails(booking,   │
                    │     attendee, count)*     │
                    └──────────────────────────┘
                               △
                  ┌────────────┼────────────┐
                  │            │            │
      ┌───────────┴──┐  ┌──────┴────┐  ┌───┴──────────┐
      │   NewState   │  │ClosedState│  │ PendingState  │
      ├──────────────┤  ├───────────┤  ├───────────────┤
      │+EnterState() │  │-reasonClosed│ │-cancelToken   │
      │+Cancel()     │  ├───────────┤  ├───────────────┤
      │+DatePassed() │  │+ClosedState│ │+EnterState()  │
      │+EnterDetails()│  │  (reason) │  │+Cancel()      │
      └──────────────┘  │+EnterState│  │+DatePassed()  │
                        │+Cancel()  │  │+EnterDetails()│
                        │+DatePassed│  │+ProcessingComp│
                        │+EnterDeta.│  └───────────────┘
                        └───────────┘
                                              +
                              ┌───────────────────────┐
                              │      BookedState       │
                              ├───────────────────────┤
                              │ + EnterState()         │
                              │ + Cancel()             │
                              │ + DatePassed()         │
                              │ + EnterDetails()       │
                              └───────────────────────┘

14. State transition diagram

                         ┌─────────────────┐
                         │  [START]        │
                         └────────┬────────┘
                                  │ new BookingContext()
                                  ▼
                         ┌────────────────┐
                    ┌───▶│      NEW       │◀───────────────┐
                    │    └───┬───┬────────┘                │
                    │        │   │                         │
                    │  Cancel│   │DatePassed               │
                    │        │   │                         │
                    │        ▼   ▼          EnterDetails() │
                    │    ┌──────────────┐                  │
                    │    │    CLOSED    │   ┌──────────────┴──┐
                    │    │              │   │    PENDING       │
                    │    │ (error on    │◀──│                  │
                    │    │  any action) │   │ DatePassed():    │
                    │    └──────────────┘   │   (ignoré)      │
                    │           ▲           └──────┬──────────┘
                    │           │                  │
                    │           │ Cancel()         │ ProcessingResult.Success
                    │           │                  ▼
                    │    ┌──────┴─────────────────────┐
                    │    │          BOOKED             │
                    │    └─────────────────────────────┘
                    │                   │
                    │    DatePassed()   │
                    └───────────────────┘
                       (ProcessingResult.Fail)

Details of transitions by state

NEW ──────────────────────────────────────────────────────────
  Cancel()       ──▶  CLOSED ("Booking Canceled")
  DatePassed()   ──▶  CLOSED ("Booking Expired")
  EnterDetails() ──▶  PENDING

PENDING ──────────────────────────────────────────────────────
  ProcessingComplete(Success)   ──▶  BOOKED
  ProcessingComplete(Fail)      ──▶  NEW  (+ affiche erreur)
  ProcessingComplete(Canceled)  ──▶  CLOSED ("Processing Canceled")
  Cancel()                      ──▶  [cancelToken.Cancel()]
  DatePassed()                  ──▶  (ignoré)
  EnterDetails()                ──▶  (ignoré)

BOOKED ───────────────────────────────────────────────────────
  Cancel()     ──▶  CLOSED ("Booking canceled: Expect a refund")
  DatePassed() ──▶  CLOSED ("We hope you enjoyed the event")
  EnterDetails() ──▶  (ignoré)

CLOSED ───────────────────────────────────────────────────────
  Cancel()       ──▶  ShowError("Invalid action for the state")
  DatePassed()   ──▶  ShowError("Invalid action for the state")
  EnterDetails() ──▶  ShowError("Invalid action for the state")

15. Summary of classes created

Complete project structure (with State Pattern)

StateDesignPattern/
├── UI/
│   └── MainWindow.xaml.cs          ← Utilise BookingContext (et non Booking)
└── Logic/
    ├── Booking.cs                  ← Implémentation naïve (exercice de départ)
    ├── BookingContext.cs           ← CONTEXT du State Pattern
    ├── BookingState.cs             ← ABSTRACT STATE
    ├── NewState.cs                 ← CONCRETE STATE : état New
    ├── ClosedState.cs              ← CONCRETE STATE : état Closed
    ├── PendingState.cs             ← CONCRETE STATE : état Pending
    └── BookedState.cs              ← CONCRETE STATE : état Booked

Summary of Responsibilities

ClassTypePrimary Responsibility
BookingContextContextMaintains currentState, delegates calls, exposes TransitionToState
BookingStateAbstract StateDefines the contract (interface) of all states
NewStateConcrete StateManages behaviors when reservation is new
ClosedStateConcrete StateHandles behaviors when reservation is closed (+ reason)
PendingStateConcrete StateManages asynchronous processing and its responses
BookedStateConcrete StateManages behaviors when reservation is confirmed

Comparison: Naive approach vs State Pattern

CriterionNaive approachStatePattern
Number of classes1 (Booking)6 classes
State managementBoolean fields (isNew, isPending, isBooked)Dedicated classes
Conditional logicif/else if cascadingNone — polymorphism
Added a stateEdit multiple existing methodsAdd a new class
Bug isolationDifficult — interleaved logicEasy — dedicated class
ReadabilityDecreases with each added stateConstant regardless of the number of states
ExtensibilityLowHigh
Respect Open/Closed PrincipleNoYes


Search Terms

c-sharp · design · patterns · state · testing · architecture · c# · .net · development · pattern · status · reservation · approach · naive · concrete · pending · abstract · analysis · booked · bookingcontext · class · classes · closed · components

Interested in this course?

Contact us to book it or get a custom training plan for your team.