8

For example:

Only job applications that are not yet in review or approved, can be updated. In other words, a person can update his job appliance form until HR starts reviewing it, or it's already acepted.

So a job application can be in 4 states:

APPLIED(initial state), IN_REVIEW, APPROVED, DECLINED

How do I achieve such behavior?

Surely I can write an update() method in Application class, check Application state, and do nothing or throw exception if Application is not in required state

But this kind of code does not make it obvious such a rule exists, it allows anyone to call update() method, and only after failing a client knows such operation was not permitted. Therefore client needs to be aware that such an attempt might fail, therefore take caution. Client being aware of such things also means that logic is leaking outside.

I tried creating different classes for each state (ApprovedApplication etc.) and put allowed operations on only allowed classes, but this kind of approach feels wrong too.

Is there an official design pattern, or a simple piece of code, to implement such behavior?

  • 7
    These things are generally called StateMachines, and their implementation will vary a little depending on your requirements and the language(s) you're working with. – Telastyn Dec 25 '15 at 15:54
  • and, how do you ensure right methods are available on right states? – uylmz Dec 25 '15 at 16:10
  • 1
    It depends on language. Different classes is a common implementation for popular languages, though "throw if not in right state" is probably most common. – Telastyn Dec 25 '15 at 16:25
  • 1
    Where is problem in including "canUpdate" method and checking it before calling Update? – Euphoric Dec 25 '15 at 21:11
  • 1
    this kind of code does not make it obvious such a rule exists - This is why code has documentation. Writers of good code will take Euphoric's advice and provide a method to let the outside test the rule before trying it the hardware. – Blrfl Dec 26 '15 at 11:24
4

This type of situation pops up quite often. For example, files can only be manipulated while open, and if you try to do something with a file after it has been closed, you get a runtime exception.

Your desire (expressed in your previous question) to use the type system of the language so as to ensure that the wrong thing cannot even happen is noble, since compile-time errors are always preferable over runtime errors. However, there is no design pattern that I know of for this type of situation, probably because it would end up causing more problems than it would solve. (It would be impractical.)

The closest thing to your situation that I know of is to model different states of an object which correspond to different capabilities via extra interfaces, but this way you are only reducing the number of places in the code where a runtime error may occur, you are not eradicating the possibility of a runtime error.

So, in your situation, you would declare a number of interfaces describing what can be done with your object in its various states, and your object would return a reference to the right interface upon a state transition.

So, for example, the approve() method of your class would return an ApprovedApplication interface. The interface would be implemented privately, (via a nested class,) so code which only has a reference to an Application cannot invoke any of the ApprovedApplication methods. Then, code which manipulates an approved application explicitly states its intention to do so at compilation time by requiring an ApprovedApplication to work with. But of course, if you store this interface somewhere, and then you proceed to use this interface after the decline() method has been invoked, you will still get a runtime error. I do not think there is a perfect solution to your problem.

  • As a side note, should it be application.approve(someoneWhoCanApprove) or someoneWhoCanApprove.approve(application) ? I think it should be the first since "someone" may not have access to fields of application to make necessary adjustments – uylmz Dec 25 '15 at 16:07
  • I am not sure, but you should also examine the possibility that neither of the two might be correct. i.e. if( someone.hasApprovalPermission( application ) ) { application.approve(); } The principle of Separation of Concerns indicates that neither application, nor someone, should be concerned with making decisions regarding permissions and security. – Mike Nakis Dec 25 '15 at 17:58
3

I'm nodding my head on different bits of the various answers but the OP seems to still have the concern of flow control. There is too much to try to coalesce in words. I'm just going to right some code - The State Pattern.


State Names as Past Tense

"In_Review" is not a state perhaps but a transition, or process. Otherwise your state names should be consistent: "Applying", "Approving", "Declining", etc. OR have "Reviewed" too. Or not.

The Applied state does a review transition and sets state to Reviewed. Reviewed state does an approval transition and sets state to Approved (or Declined).


// Application class encapsulates state transition,
// the client is unable to directly set state.
public class Application {
    State currentState = null;

    State AppliedState    = new Applied(this);
    State DeclinedState   = new Declined(this);
    State ApprovedState   = new Approved(this);
    State ReviewedState   = new Reviewed(this);

    public class Application (ApplicationDocument myApplication) {
        if(myApplication != null && isComplete()) {
            currentState = AppliedState;
        } else {            
            throw new ArgumentNullException ("Your application is incomplete");
            // some kind of error communication would probably be better
        }
    }

    public apply()    { currentState.apply(); }
    public review()   { currentState.review(); }
    public approve()  { currentState.approve(); }
    public decline()  { currentState.decline(); }


    //These could be done via an enum. I like enums!
    protected void setSubmittingState() {}
    protected void setApproveState() {}
    // etc. ...
}

// could be an interface if we don't have any default or base behavior.
public abstract class State {   
    protected Application theApp;
    // maybe these return an object communicating errors / error state.
    public abstract void apply();
    public abstract void review();
    public abstract void accept();
    public abstract void decline();
}

public class Applied implements State {
    public Applied (Application newApp) {
        if(newApp != null)
            theApp = newApp;
        else
            throw new ArgumentNullException ("null application argument");
     }

    public override void apply() {
        // whatever is appropriate when already in "applied" state
        // do not do any work on behalf of other states!
        // throwing exceptions here is not appropriate, as others
        // have said.
      }

    public override void review() {
        if(recursiveBureaucracyBuckPassing())
            theApp.setReviewedState();
    }

    public override void decline() { // ditto  }
}

public class Reviewed implements State {}
public class Approved implements State {}
public class Declined implements State {}

Edit - Error Handling Comments

A recent comment:

... if you are attempting to loan a book which is already issued to somebody else the Book model will contain the logic to prevent it's state from changing. This might be via a return value (e.g. a boolean successful yay/nay, or status code) or an exception (e.g. IllegalStateChangeException) or some other means. Irrespective of the means chosen, this aspect is not covered as part of this (or any) answer.

And from the original question:

But this kind of code does not make it obvious such a rule exists, it allows anyone to call update() method, and only after failing a client knows such operation was not permitted.

There is more design work to do. There is no Unified Field Theory Pattern. The confusion comes from assuming the state transition framework will do general application functions and error handling. That feels wrong because it is. The answer shown is designed for controlling state change.


Surely I can write an update() method in Application class, check Application state, and do nothing or throw exception if Application is not in required state

This suggests there are three functionalities working here: The State, Updating, and interaction of the two. In this case Application is not the code I've written. It may use it to determine current state. Application is not the applicationPaperwork either. Application is not the interaction of the two, but could be a general StateContextEvaluator class. Now Application will orchestrate these component interactions and then act accordingly, like emitting an error message.

End Edit

  • Am I missing something? This seems to permit calling all four methods, regardless of state, without a hint of how this setup is to be used for communicating to the calling methods that the apply() call was unsuccessful due to having already applied for example. – kwah Dec 26 '15 at 13:37
  • 1
    permit calling all four methods, regardless of state Yes. It must. without a hint of how this setup is to be used for communicating to the calling methods See the comment in the Application constructor where the exception is thrown. Maybe calling AppliedState.Approve() might result in a user message "Application must be reviewed before it can be approved." – radarbob Dec 26 '15 at 16:35
  • 1
    ...the apply() call was unsuccessful due to having already applied for example. That is wrong thinking. The call is successful. But there is different behavior for different states. That, is the State Pattern...... However, the programmer must decide what behavior is appropriate. But it is wrong thinking that "OMG an error!!! We need to go apoplectic and abort the program!" I expect AppliedState.apply() would gently remind the user that the application has already been submitted and is waiting for review. And the program keeps going. – radarbob Dec 26 '15 at 16:55
  • Presuming the state pattern is being used as a model, the "failure" must be communicated to the user interface. For example, if you are attempting to loan a book which is already issued to somebody else the Book model will contain the logic to prevent it's state from changing. This might be via a return value (e.g. a boolean successful yay/nay, or status code) or an exception (e.g. IllegalStateChangeException) or some other means. Irrespective of the means chosen, this aspect is not covered as part of this (or any) answer. – kwah Dec 27 '15 at 1:41
  • Thank god somebody said it. "I need different behavior based on state of an object. ... Yes, yes. You want the state pattern." ++ old bean. – RubberDuck May 19 '16 at 23:05
1

In general, what you are describing is a workflow. More specifically, business functions that are embodied by states such as REVIEWED APPROVED or DECLINED fall under the heading of "business rules" or "business logic."

But to be clear, business rules should not be encoded into exceptions. To do so would be to use exceptions for program flow control, and there are many good reasons why you should not do that. Exceptions should be used for exceptional conditions, and the INVALID state of an application is entirely unexceptional from a business standpoint.

Use exceptions in cases where the program cannot recover from an error condition without user intervention ("file not found," for example).

There is no specific pattern for writing business logic, other than the usual techniques for arranging business data processing systems and writing code to implement your processes. If the business rules and workflow are elaborate, consider using some sort of workflow server or business rules engine.

In any case, the states REVIEW, APPROVED, DECLINED, etc. can be represented by an enum type private variable in your class. If you use getter/setter methods, you can control whether or not the setters will allow changes by first examining the value of the enum variable. If someone tries to write to a setter when the enum value is in the wrong state, then you can throw an exception.

  • There's an object, called "Application", it's properties can only be changed if its "State" is "INITIAL". This is not a big workflow, like documents flowing from one department to another. What I fail to do is, to reflect this behavior in an object oriented sense. – uylmz Dec 25 '15 at 13:14
  • @Reek Application should expose read/write interface, and iteraction logic should take place in higher level. Both applicant and HR uses the same object, but have different privileges - application object shouldn't be concerned about it. Inner exceptions might be used to protect system integration, but I woudn't go to defensive (editing contact information might be neccessary even for approved applications - just need higher access level). – shudder Dec 28 '15 at 20:54
1

Application could be an interface, and you could have an implementation for each of the states. The interface could have a moveToNextState() method, and this would hide all the workflow logic.

For the client's needs, there could be also a method returning directly what you can do and not (i.e. a set of booleans), instead of just the state, so that you don't need a "checklist" in the client (I assume the client to be an MVC controller or UI anyway).

However, instead of throwing an exception you could just do nothing and log the attempt. This is safe at runtime, rules were enforced and the client had ways to hide the "update" controls.

1

One approach to this problem which has been extremely successful in the wild is hypermedia - the representation of the state of the entity is accompanied by hypermedia controls that describe the kinds of transitions that are currently allowed. The consumer queries the controls to discover what can be done.

It's a state machine, with a query in its interface that allows you to discover what events you are allowed to fire.

In other words: we're describing the web (REST).

Another approach is to take your idea of different interfaces for different states, and provide a query that allows you to detect which interfaces are currently available. Think IUnknown::QueryInterface, or down casting. The client code plays Mother May I with the state to find out what's allowed.

It's essentially the same pattern - just using an interface to represent the hypermedia controls.

  • I like this. It could be combined with the State pattern to return a collection of valid States that could be transitioned to. Chain of Command comes to mind in a way. – RubberDuck May 19 '16 at 23:07
  • 1
    My guess is that you don't want "collection of valid states" but "collection of valid actions". Think graph: you want the current node (state) and the list of edges (actions). You'll find out the next state when you choose your action. – VoiceOfUnreason May 19 '16 at 23:14
  • Yes. You're correct. A collection of valid actions where that action is actually a state transition (or something that triggers one). – RubberDuck May 19 '16 at 23:22
1

Here's an example of how you might approach this from a functional perspective, and how it helps avoid the potential pitfalls. I'm working in Haskell, which I'll assume you don't know, so I'll explain it in detail as I go along.

data Application = Applied ApplicationDetails |
                   InReview ApplicationDetails |
                   Approved ApplicationDetails |
                   Declined ApplicationDetails

This defines a data type that can be in one of four states that correspond to your application states. ApplicationDetails is assumed to be an existing type that contains the detailed information.

newtype UpdatableApplication = UpdatableApplication Application

A type alias that needs explicit conversion to and from Application. This means that if we define the following function which accepts and unwraps an UpdatableApplication and does something useful with it,

updateApplication :: UpdatableApplication -> ApplicationDetails -> Application
updateApplication (UpdatableApplication app) details = ...

then we have to explicitly convert the Application to an UpdatableApplication before we can use it. This is done using this function:

findUpdatableApplication :: Application -> Maybe UpdatableApplication
findUpdatableApplication app@(Applied _) = Just (UpdatableApplication app)
findUpdatableApplication _               = Nothing

Here we do three interesting things:

  • We check the state of the application (using pattern matching, which is really handy for this kind of code), and
  • if it can be updated, we wrap it in an UpdatableApplication (which only involves a compile type note of the change of type being added, as Haskell has a specific feature to do this kind of type-level-trickery, it costs nothing at runtime), and
  • we return the result in a "Maybe" (similar to Option in C# or Optional in Java -- it's an object that wraps a result that could be missing).

Now, to actually put this together, we need to call this function and, if the result is successful, pass it on to the update function...

case findUpdatableApplication application of
    Just updatableApplication -> do
        storeApplicationInDatabase (updateApplication updatableApplication)
        showConfirmationPage
    Nothing -> do
        showErrorPage

Because the updateApplication function needs the wrapped object, we can't forget to check the preconditions. And because the precondition check function returns the wrapped object inside a Maybe object we can't forget to check the result and respond accordingly if it failed, either.

Now... you could do this in an object-oriented language. But it's less convenient:

  • None of the OO languages I've tried have a simple syntax for making a type-safe wrapper type, so that's boilerplate.
  • It'll also be less efficient, because at least for most languages they won't be able to eliminate the wrapper type, as it will be required to exist and be detectable at runtime (Haskell has no runtime type checking, all type checks are performed at compile time).
  • While some OO languages have types equivalent to Maybe they don't usually have as convenient a way of extracting the data and choosing the path to take at the same time. Pattern matching is really useful here, too.
1

You could use the «command» pattern, and then ask to the Invoker to provide a list of valid functions according to the state of the receiver class.

I used the same in order to provide functionality to different interfaces that were supposed to call my code, some of the options were not available depending of the current state of the record, so my invoker updated the list and that way every GUI asked the Invoker which options were available and they painted themselves accordingly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.