views:

57

answers:

5

I have an entity, equivalent to a task, that can have multiple states. The "task" can either be in a pending, passed, or failed state. Each one of these states will also have some unique data. For instance, in a failed state the entity should have a reason for the failure, in the pending state it should have a deadline for evaluation, ect.

While the above leads me to think I should have a separate object to represent each state, the underlying ID for the entity should remain the same, pushing me back towards thinking of this as a single object.

Also, transitioning from state to state requires some logic. A "pending" task transitioning to a "passed" state will be treated differently than a "failed" task making the same transition.

If the representations for each state were exactly the same I'd just use a primitive property and be done with it. However, since the different states have slightly different representations, I've been struggling to figure out the best way to model this. The logic for governing the internal state is getting a little messy so I figured I'd step back and reconsider. Any thoughts?

I'm using c# though I'd consider this language agnostic.

A: 

Have a look at the state pattern.

Daniel Brückner
I'm familiar with that pattern. From my understanding, and from how I've used it, it's most helpful in changing the behavior of an object at runtime. With this problem, I think, it's more about managing the differences in the object's representation. I may be barking up the wrong tree in general here. Just explaining the problem has helped me identify a few incompatible goals.
TheDeeno
A: 

This sounds like an ideal application of object inheritance and polymorphism.

abstract class Task
{
      public int TaskId { get; private set; }
      abstract PassedTask TransitionToPassed();
      ...
}

class PendingTask : Task
{
      PassedTask TransitionToPassed()
      {
            PassedTask passed = new PassedTask();
            passed.TaskId = TaskId;
            ...
            return passed;
      }
      ...
}

class PassedTask : Task
{
      PassedTask TransitionToPassed()
      {
            return this;
      }
      ...
}

class FailedTask : Task
{
      public string ReasonForFailure { get; private set; }
      PassedTask TransitionToPassed()
      {
            ...
      }
      ...

}
Jeffrey L Whitledge
A: 

When I first read this question, my answer was to use an enumeration to define the state. After rereading it, though, I would suggest one of the following:

  1. Implement each task as a separate class (PendingTask, PassedTask,...) with the same parent, and a constructor that accepts a task of any type that comes before that state.
  2. Implement one Task, and create a new class TaskStateData with child classes for the data needed for each state.
  3. Implement one Task, but have a separate method to change the state for each state type with parameters for the extra attributes needed for that state

I suggest these solutions to ensure data integrity.

dj_segfault
A: 

Going with a pure Object-Oriented approach has downsides. Unless you plan on having to do a lot of polymophic code managing, avoid representing your domain class's state using polymorphism directly. I've settled on a more hybrid approach. The different states need to be modeled as a separate parent/child inheritance tree. Start with an abstract base class MyState which have as children MyState1, MyState2, and MyState3. ( see Jeffrey's answer for an example.

The entity which needs its state tracked has an attribute of 'current-state' which is of type MyState. when the entity changes state it is a simple assignment or setter() call to change it. You can construct singleton instances of each state if so desired, or construct new instances for each state change. It depends on how often the state changes and how many objects are having their state tracked. If the numbers get too big you can consider the singleton approach.

Kelly French
A: 

The "task" can either be in a pending, passed, or failed state. A "pending" task transitioning to a "passed" state will be treated differently than a "failed" task making the same transition.

That seems a rather odd collection of states. I'd expect a task to take some time to execute, so transition from pending to executing then either pass or fail, and tasks which don't execute before their time runs out to expire. If there is also a transition from failed to failed-and-expired then that might add another state.

Draw a state machine to find the states.

Firstly, do you need to model the states structurally? Would a pending/expired flag, a scheduled time and a result do (with failure and success as the two sub-types of result)? What do clients of the task need from it?

Secondly, are you interacting with a task or a scheduler? It is not uncommon to give a task description to a scheduler and to get back a future, which can be queried about the result of the task. But the task itself is not exposed, only whether it is complete and the result. If you require progress, you might want to have a scheduler which can be queried by task ID to get progress, rather than a task object you hold a reference to - having a task object which changes state concurrently makes it hard to get a consistent set of state data from it, until it reaches a final state. If the 'passed' state does not have failure information, then querying 'are you failed', followed by 'get failure status' easily leads to a race unless you externalise locking (ewww), so returning an immutable task state information object atomically becomes desirable, by which time your task object becomes more or less equivalent to the ID passed to a scheduler.

Pete Kirkham