16

I am new to the State design pattern and I can't find a proper example of saving different states of an object to the database (SQL Server in my case). The scenario is quite similar [almost identical] to the example described in the following article, however I have not found an applicable solution for persisting the states to the database. Can you guys recommend a link or possiblly give an example?

State Pattern Usage and Sample in C#

In addition: How do you enumerate all different ConcreteState types at run time? For instance, if you have 10 different states, do you declare an EnumStates with 10 different members and give every single ConcreteState member an associated EnumStates member, or you do get all the distinct states by getting the subclasses of ConcreteState?

For you information, I need to be able to search for entities based on their different states.

jaco0646
  • 15,303
  • 7
  • 59
  • 83
MHOOS
  • 5,146
  • 11
  • 39
  • 74

5 Answers5

8

Instances of States don't have state themselves, so all you need to save is each State's identity. It's not a good idea to save the State class name in the database, because the database would have to change if you changed the State class name. Instead,

  • give each State class a member with an Enum value that is unique to the state.
  • When you persist the object that has the State, persist the Enum.

To get the state back when you load the object, either

  • instantiate the object's State member immediately and assign it to the object's State member, or
  • if it's expensive to instantiate a State, change the object to access the State member through a method and lazily instantiate the State in that method based on the value of the State identity Enum member.

Either way, you need to be able to go from an Enum value to a State. Do that by looping through all of the relevant State classes until you find the one whose identity value matches.

So, what are the relevant States? It depends on who's writing State classes.

  • In the simple case, where you control the entire program and all of the State classes in the program are potentially members of the State-having object, you can just loop over all the subclasses or implementers of the State superclass or interface, like this: Getting all types that implement an interface.

  • If for some reason there are State classes that you don't want to loop over, just define a list of the ones that you do want to loop over in a constant or (if you want to change it without changing the code) in configuration.

If making your list of State classes is slow, just do it once at program startup or first use. If you hardcode the list, don't do that in the State-having class (it should be independent of specific States) or in the State superclass (that would introduce a circular dependency); put the list higher up (dependency-wise) in your program or (as Farhad suggested) in its own class.

There are a lot of examples how how to persist objects with State out there; this one is relatively straightforward.

Community
  • 1
  • 1
Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
5

Don't try to translate the states into columns in a table, that wont work.

Instead serialize the states using JSON.NET as it supports inheritance. Then store it in a table like:

create table OrderStates
(
    OrderId int not null,
    Data nvarchar(MAX) not null
);

Include more columns if you want but only columns which is required to identify what the states are used for.

To activate the inheritance support in JSON.NET you have to use:

var json = JsonConvert.SerializeObject(yourState, typeof(StateBaseClass), JsonConvert.DefaultSettings)`. 
using (var cmd = sqlConnection.CreateCommand())
{
    cmd.CommandText = "INSERT INTO OrderStates (OrderId, Data) VALUES(@OrderId, @Data)";
    cmd.Parameters.AddWithValue("OrderId", orderId);
    cmd.Parameters.AddWithValue("Data", json);
    cmd.ExecuteNonQuery();
}

Same goes when deserializing, specify the base class when using JsonConvert.DeserializeObject().

How do you enumerate all different ConcreteState types at run time? For instance, if you have 10 different states, do you declare an EnumStates with 10 different members and give every single ConcreteState member an associated EnumStates member, or you do get all the distinct states by getting the subclasses of ConcreteState?

Subclasses. It's the only way to be able to introduce new states or remove old ones without having to modify the other classes. Each modification of existing classes can introduce bugs.

jgauffin
  • 99,844
  • 45
  • 235
  • 372
5

I didn't like the example you linked, below I listed the reasons:

  1. I agree that the persisting architecture will be a mess - way to messy in my opinion.
  2. Creating new instance per state seems to me like efficiency suicidal pattern.
  3. Testing will be hell... finding errors will be hell.. debugging will be hell.
  4. In over 30 years of experience I never seen this pattern being used even once in a data centric application - I did see it and used it in cases where I do not need to persist the information, in example when building a network layer - per port could be treated with that kind of state pattern.


I would go for this pattern instead:

pattern infrastructure

public interface IStateObject<T>
{
    T State { get; set; }
    void Process();
}

Example implementation for some pseudo Order object

public enum OrderState
{
    Taken,
    Approved,
    Payed,
    Emailed,
    BeforeShipment
    //etc.. etc..
 }

 public class Order : IStateObject<OrderStates>
 {
     //some linear fields of order..
     //: name, description, etc.. etc..

     public OrderStates State { get; set; }

     public void Process()
     {
         switch (State)
         {
             case OrderState.Taken:
                 // code to handle this state
                 break;
             case OrderState.Approved:
                 // etc..
                 break;
          }
         //persist myself to db.
     }
 }

It is very simple as you can save object per type per context of the object in one row.
Also an object is created once as intuitively it should have if we had no computer nears us..
But mostly because it is very straight forward and very flexible.

You might notice you actually may not need the IStateObject<T> at all - but I would argue you will need it later on when you want to process on vertical decisions. keep in mind that T doesn't have to be an enum. it could serve as common ground to evolve according to your app needs.

To further point out the mess I mentioned in the beginning of this answer,
Let's say we want to have history to previous states of the order:

Using the pattern offered in this answer - you add a PreviousOrderState property and now you have history per row.. And there are other ways I'm sure you can think of..

But using the "State Pattern" - you will be in serious problem... it will actually going to be complicated by a full "scale level" to do that.. you will have to be able to link from each type of table to each type of other table - or try to force Object Oriented on your database...

See my point? the States pattern is simply not designed for data centric apps.

Good Luck.

G.Y
  • 6,042
  • 2
  • 37
  • 54
  • 3
    This is exactly NOT the State design pattern. While a switch-case in some cases might be the right answer, I don't think you should fall back to it just because of persistence issues. Usually, the state-pattern can save you a metric ton of bugs in complex cases. – Gilthans May 29 '14 at 22:28
  • @Gilthans I agree with your comment. but order state is not a different context that justify object per case. order-state is a property of the order. just like a green person is not a different type of person - is just a different type of property belong to a person.. it is crucial to distinguish what are properties and what are two different objects. `Order` is an object type cause it does not need anything else to materialize itself - `order-state` is a property cause it cannot materialize without an order. – G.Y May 30 '14 at 11:58
  • Either option is a valid way to program; in either case the order-state is a property of the order, but it can be an enum which the order manipulates, or it can be an object which manipulates itself. The latter is more OOP-ish, and generally less prone to bugs due to unexpected combinations of enums. I like the example given here: http://gameprogrammingpatterns.com/state.html – Gilthans May 30 '14 at 13:09
  • Yup it is a good example - I agree again. But even in the example he does not use that pattern for persisting information which I believe adds strength to my statement that states-pattern is simply not designed for data centric apps. – G.Y May 30 '14 at 14:09
1

I agree with Dave's answer up to the point where you need to get the state when you load the object. I don't think always iterting through all state classes or even a list of classes is a good idea when you have many different objects that have states.

I think in that scenario there should be a StateManager class which may also contain the states Enum definition and also a mapping between each Enum value and its State object(Dictionary<StateEnum, State>). This mapping should either be hard-coded or read from a configuration file. This class can handle the lazy loading of the states when they are being accessed for the first time. It could also create them as Singleton objects if the state don't really have fields but rather functionalities(as in the example link in OP's submission).

Farhad Alizadeh Noori
  • 2,276
  • 17
  • 22
0

State pattern can be used in data centric system, like web applications dealing with workflow order and approval business. State manipulation and persistent store occurs in different periods of state switching logic. For example, a delegate object will take charge of state switch, while database operations should occurs when a state switch event occurs. You can also predefine all the business states flow in a state machine object. When state change event comes in, trigger the state machine to find whether it is in the predefined flow. A small demo can be found in https://github.com/elimisteve/fsm

burx
  • 21
  • 4