Technology: C#, .NET, Visual Studio 2019, WPF
Table of Contents
- Course Overview
- Module introduction — What is State
- The demonstration project
- Naive approach to managing state
- 4.1 Initial implementation — creating a reservation
- 4.2 Handling cancellation and expiration with a boolean
- Implementation — Abstract State
- Implementation — Concrete States
- Implementation — The Context and the Context State relationship
- Implementation — Code the behaviors of each state
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:
- How can an object change its behavior when its internal state changes?
- 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:
- A user creates a new reservation (booking) for an event
- It submits information for processing
- If the processing is successful → the reservation is considered confirmed (booked)
- If processing fails → a new reservation is created and the process starts again
- 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
- 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:
| Element | Type | Description |
|---|---|---|
View | MainWindow | Reference to the main window to interact with the UI |
Expected | string | Participant Name |
TicketCount | int | Number of tickets |
BookingID | int | Unique 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 completeShowState— utility method to display the reservation status in the UI
Key principle: The
Bookingclass delegates all UI functions to theMainWindow, and the UI interacts with theBookingclass 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:
- Assign a booking ID (
BookingID) - View booking status
- 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:
- Event date passes → reservation is closed
- User cancels → reservation is closed
- 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:
- Update reservation data
- Show status in UI
- Submit reservation for processing
- 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:
- How can an object change its behavior when its internal state changes?
- 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
trueorfalse, 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 │ │ │
│ └──────────┘ └──────────────┘ │ │
└─────────────────────────────────────────────────────────────┘
| Component | Description |
|---|---|
| Background | Class that maintains an instance of a concrete state as its current state. Provides the way to set/change its state. |
| Abstract State | Abstract 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:
| State | Description |
|---|---|
New | New reservation, awaiting submission |
Pending | Submitted, awaiting processing response |
Booked | Processing successful, booking confirmed |
Closed | Reservation closed (cancelled or expired) |
2. Transitions between states:
| Starting state | Trigger | Arrival status |
|---|---|---|
New | Past event date | Closed |
New | Cancellation by user | Closed |
New | Submission of information | Pending |
Pending | Successful treatment | Booked |
Pending | Failed treatment | New |
Pending | Cancellation by user | Closed |
Booked | Past event date | Closed |
Booked | Cancellation by user | Closed |
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 Class → BookingState.
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 fromBookingState(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:
NewStateClosedStatePendingStateBookedState
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: BookingStateclass is defined, Visual Studio underlines the class name in red if abstract methods are not implemented. Right click → Quick Actions and Refactoring → Implement 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:
| Component | Role in the pattern | Class |
|---|---|---|
| Background | Maintains current state | BookingContext |
| Abstract State | Behavior interface | BookingState |
| Concrete State | New State | NewState |
| Concrete State | Status Closed | ClosedState |
| Concrete State | Pending Status | PendingState |
| Concrete State | Booked Condition | BookedState |
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:
- Maintain an instance of a concrete state as its current state
- Provide a way to set/change this current state
- 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
currentStatefield to the instance of a concrete state passed as a parameter - Line 2: Calls the
EnterStatemethod of this concrete state. Each concrete state usesEnterStatefor any code to execute when the state is first entered thisrepresents the current instance ofBookingContext, 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
BookingStatein thecurrentStatefield - Provides a way to set
currentStatein itsTransitionToStatemethod - Sets the initial state of the process in its constructor (call to
TransitionToState(new NewState())) - Interacts with
currentStatevia methods defined in the abstract base classBookingState
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 toPendingStateCancel→ transition toClosedState(reason: “Booking Canceled”)DatePassed→ transition toClosedState(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,DatePassedandEnterDetailsmethods 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
CancellationTokenSourceto enable cancellation - Show “Pending” status
- Inform user that processing is in progress
- Submit reservation to external system
Available behaviors:
Cancel→ callscancelToken.Cancel()to inform the external systemDatePassed→ 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 toClosedState(reason: “Booking canceled: Expect a refund”)DatePassed→ transition toClosedState(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
| Advantage | Description |
|---|---|
| Modular code | The logic of each state is maintained in its own class |
| Ease of reading | Each state class is short and focused on a single state |
| Ease of maintenance | Changes to one state do not affect others |
| Simplified debugging | Bugs are isolated to the affected status class |
| Maximum expandability | New states can be added without refactoring existing code |
| Elimination of conditional complexity | No more cascading if/else if, no more Boolean fields to manage |
| State independence | Changing 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
| Class | Type | Primary Responsibility |
|---|---|---|
BookingContext | Context | Maintains currentState, delegates calls, exposes TransitionToState |
BookingState | Abstract State | Defines the contract (interface) of all states |
NewState | Concrete State | Manages behaviors when reservation is new |
ClosedState | Concrete State | Handles behaviors when reservation is closed (+ reason) |
PendingState | Concrete State | Manages asynchronous processing and its responses |
BookedState | Concrete State | Manages behaviors when reservation is confirmed |
Comparison: Naive approach vs State Pattern
| Criterion | Naive approach | StatePattern |
|---|---|---|
| Number of classes | 1 (Booking) | 6 classes |
| State management | Boolean fields (isNew, isPending, isBooked) | Dedicated classes |
| Conditional logic | if/else if cascading | None — polymorphism |
| Added a state | Edit multiple existing methods | Add a new class |
| Bug isolation | Difficult — interleaved logic | Easy — dedicated class |
| Readability | Decreases with each added state | Constant regardless of the number of states |
| Extensibility | Low | High |
| Respect Open/Closed Principle | No | Yes |
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