I’m going to try to make this as concise and concrete as possible, but apologies since I can think of multiple ways to make it work. This question might also relate to handling production workflows or manufacturing, but I have no experience in these areas.
I have a problem where I need to move an Object, O, from different states to other states (like a Nondeterministic finite automata). The problem is the Object can be in different states at the same time, and the interaction sometimes matters.
For example say there are 10 states, O can be in up to 3 of those states at the same time. I’m a bit lost as to what type of code design I should use to model this. The two ideas I am thinking about are
-
Pass O from handler to handler, and manage the transitions between states, and keep some sort of info in O saying which states they are in, so that each state can figure out what to do based on what other states O is in.
-
Have a larger Superclass that handles which states O is in, and handles the interactions.
I’d normally say 1. but handling all the interactions between handlers in each handler sounds messy.
What would be the better option between these 2? Or is there another method that is preferred?
** Update and Clarification **
As a concrete example. The Object can be in one of many physical locations, while it can also be in one of several stages of processing (The object might move back and forth between locations, or back and forth between the stages of processing). As additional complexity each physical location might have sublocations, but I think I can ignore that for now. So if it’s clearer, it can be considered to be two independent automata, but choosing the edge to move to the next state in either automata depends on it’s state from both automata.
18
Your different states sound more like activities. If they were states you would use Nouns
to describe them (example; “full”, “empty”, “box”, “container”). If they were activities then you would use Adjectives
to describe them (example; “opening”, “processing”, “making”).
Your object can be at a given location (i.e. a state), but it’s doing many things at once (i.e. activities). Therefore, you would have an internal collection of activities that the object contains, but it may not be responsible for the logic of those activities. Each activity could implement that logic internally.
I’d create a new object to represent the factory, which contains the object at different locations (i.e. their state). The factory object would add/remove activities to objects when those activities report they are finished or not.
Here is a mockup
class ActivityContext
{
// data that is needed by
// other activities when
// doing work.
}
interface Activity
{
public void DoWork(ActivityContext);
public bool DoneWork(ActivityContext);
}
class MyObject
{
public string Location;
public List<Activity> CurrentActivities;
public void WorkOnActivites() {...}
public void Add(Activity) {...}
public void Remove(Activity) {...}
}
class Factory
{
public List<MyObject> objects;
public void ChangeLocation(MyObject) {...}
public void Add(MyObject) {...}
public void Remove(MyObject) {...}
// .. and other methods
}
You would create new objects that implement the Activity
interface, add them to MyObject
when that object needs to perform that activity, or change it’s location. To find objects and manage them you use the Factory
object.
EDIT:
I added the class ActivityContext
as a data object. These objects are created and passed to an Activity
when it does work. Each Activity
can update the context data so that other activites can receive that data when they do work. This allows activities to be aware of what other activities are doing without directly referencing those activities.
2
2. Have a larger Superclass that handles which states O is in, and handles the interactions.
That sounds like an awful lot of logic from what you are describing. Do you want the same class defining that 30% complete parts in Atlanta have to be shipped to Dallas as the class that handles 99% complete parts in the East part of Dallas can be moved to finished in the same area?
- Pass O from handler to handler, and manage the transitions between states, and keep some sort of info in O saying which states they are in, so that each state can figure out what to do based on what other states O is in.
I think this would be manageable if you provide a way to ignore the states you don’t care about. If step 2 of assembly is always followed by step 3, regardless of location, you want to be able to return “go to step 3” without worrying about what factory you are in.
For instance you could make a wrapper class that allows partial transitions. If you are going the immutable route, the functions could return the result of the partial transition function, i.e.
if state.Production = Step.Two then
return state.AlterProduction(Step.Three)
Or you could go the mutable route and just transform the state with any updates.
2
If I understand your question correctly, you could design your code similar to the outline below:
- A StateObjectContainer class that holds references to the current state, the object, and to the state manager. This class is responsible to the metadata (the references) associated with the contained object.
- A State class with a collection of StateObjectContainers. Also has the ability to add objects to the current state.
- A StateManager class that has references to all state instances and exposes functionality to query them for information.
- A StateDecider class that determines the next logical states given an object and a snap shot of all states.
- An event or callback that is called when an object is ready to change states and is given that object as an argument.
When an object is ready to change states:
- Get a snapshot of all current states
- Calculate the next logical states
- Add the object to each of the calculated states.
What I understand from your example is that your object can be represented by two parallel state-machines (location and stage), and you need to synchronize those automata.
You could take inspiration from Statecharts, for example, which allow hierarchical state-machines with communication across states, among other things, but this is likely to be overkill for your usage.
Personally, I would try to use something like Petri-nets. Basically, you can model fork/join (“rendez-vous”) transitions easily between separate subgraphs. Read for example The application of petri-nets to workflow management or this paper (which is about another topic but has nice explanations).
Then, your would fork your initial state into different parallel paths (location, stage, ..) where each “token” would represent the current state of the object for the different variables. Some or all of those tokens will then be used jointly to trigger specific transitions.
1
Sounds like your object needs a location field (and maybe a location class or interface to help ensure valid values) and a second field for processing stage which could probably be represented by an enum of the possible stages.
enum ProcessingStage { STAGE_1, STAGE_2,... STAGE_N; }
class MyObject {
String location;
ProcessingStage stage;
...
}
Surely there is some limit to the number of processing-stage combinations. Even if there are 10 possible processing-stages, and it can be in 3 of them at once (though not the same 3 across the board), that’s 10x9x8 = 720 possible processing-stage combinations. I’m guessing the number of valid combinations is much smaller, like maybe 30, in which case you could represent the combinations in an enum:
enum ProcessingStage {
CHASSIS_ASSEMBLY,
CHASSIS_VERIFICATION_AND_DASHBOARD_ASSEMBLY,
DASHBOARD_ASSEMBLY,
PAINTING,...
}
Otherwise instead of having a single processing-stage, you could store them in a list. Don’t let the client modify the list directly. Instead, provide methods that check the validity of the processing-stage combinations.
private final List<ProcessingStage> procStages = new ArrayList<>();
private static final int MAX_STAGES = 3;
public synchronized void addStage(ProcessingStage ps) throws Exception {
if (procStages.size() >= 3) {
throw new Exception("Can't have more than 3 simultaneous stages");
}
if (procStages.size() < 1) {
procStages.add(ps);
} else {
for (ProcessingStage testPs : procStages) {
// Maybe you could start painting while still assembling,
// but can't start assembling while you are painting.
if ( !ps.isCompatibleWith(testPs) ) {
throw new Exception("New stage " + ps +
" is incompatible with an existing stage: " +
testPs);
}
}
}
}
public synchronized void removeStage(ProcessingStage ps) throws Exception {
if (procStages.size() < 1) {
throw new Exception("Can't remove a stage when there are none");
}
procStages.remove(ps);
}
/**
In Java, the client cannot modify this list, but it still tracks
the list used internally by this object (shares the underlying
list) so that every time the client looks at an old list that they
get from this method, it could be different. Weird, but that's
Java.
*/
public List<ProcessingStage> stageListView() {
return Collections.unmodifiableList(procStages);
}
I synchronized the methods so that stage transitions would be atomic and made them throw exceptions so that the caller has to handle what happens if another caller changes the state underneath them.
It occurs to me that you might want to look at Josh Bloch’s Item 33: “Use EnumMap instead of ordinal indexing” (from Effective Java p. 161). Regardless of the title and the language, he has a nice example of modeling states: SOLID, LIQUID, GAS and valid transitions: FREEZE(LIQUID, SOLID), MELT(SOLID, LIQUID)… Then he creates a map of valid transitions.
1
The Object can be in one of many physical locations, while it can also be in one of several stages of processing …
This got me thinking about the Bridge Pattern. For structure.
(The object might move back and forth between locations, or back and forth between the stages of processing).
This got me thinking about the Visitor Pattern. For behavior.
State
In general an object’s state is the sum-total of the current values of all its properties. So I’m assuming location
, stage
, and object
can all have their own self-contained state. Further, the combination of location
and stage
state for a given object
gives us the 10 possible states you refer to.
Code Sketch
- Look at the overall class, interface definitions. Don’t get hung up on method names and details.
-
abstract class
vis-a-visinterface
– use either or both as needed. This is not a discussion of “code to interfaces not implementation” -
If these 10 states can be described succinctly perhaps an
enum
defining them is in order. - If
state
is as I describe above thenobject
holdinglocation
andstage
references is notionally the same as anenum
, but very fungable. - If concrete
location
andstage
vary then each should have abase
. - If and
object
‘slocation
&stage
can both vary independently, andobject
state should change when these do, thus we utilizebridge pattern
. -
If a
location
(orstage
) changes that new location can/must calculate its own state, and possibly directly affectobject
‘s other properties (and/or vice versa!), thus we have thevisitor pattern
. i.e. concretelocation
(orstage
) driven state change. -
Not shown well here, but I’m thinking.. in general return new copies of objects rather than directly changing properties of the current
object
,location
,stage
. then you can utilize a “functional” programming style; and can keep the obsolete objects as state history.
public class Object { public Location currentLocation; public Stage currentProcess; // other properties } public abstract class Location { public DoYourThing(); public Object myObject; // this could be handy // other properties } public abstract class Stage { public Object myObject; // this could be handy //other properties public DoA(); public DoB(); } // Smart enough to calculate Location state relative to concrete Object & Object.currentStage public abstract class LocationVisitor { public CalcNewState(Object obj) { obj.Location.DoYourThing(); . . . }; } // Smart enough to calculate Stage state relative to concrete Object & Object.currentLocation public abstract class StageVisitor { // base implementation stuff // if certain things must be in a specific order, let's say: public DoStageing(Object obj) { obj.currentStage.DoB(); DoExtraStuffDefinedinVisitor(); obj.currentStage.DoA(); // and so on } } public abstract ObjectStateMachine { protected Object theObject; protected LocationVisitor locVisitor; protected StageVisitor stageVisitor; // constructors to fix properties above, and/or // public properties to set each individually at will . . . public Object CalcNewState() { var locationState = locVisitor.CalcNewState(theObject); var stageState = stageVisitor.DoStaging(theObject); var allEncompassingState = CalcNewState (locationState, stageState); } public CalcNewState (locationThing, stageThing) { // magic happens... } }