Get objects with a specific state using the state pattern

I was tasked with developing a piece of software (in java) with projects and jobs where each job has a status. Since we learned about the GoF samples , the staff model seemed like an obvious choice to me, but now I'm having some implementation issues.

In my current project, I have a class Job

to represent an assignment with a specific state and a class Project

that has a list of assignments. To represent the four possible states ( AVAILABLE

- UNAVAILABLE

- FINISHED

- FAILED

), I made an enumeration State

with state-dependent job methods implemented in the enumeration.

The problems with the state pattern I have are as follows:

  • In Project

    , I would like to have a method that returns all the AVAILABLE

    jobs in the project (and maybe even those that return all jobs that are either FINISHED

    or FAILED

    ), but the status template does not mention how to do this. We've also heard that using methods as public boolean isAvailable()

    in Job

    or State

    shouldn't be used when using the state template (because that's exactly what to avoid when using this template).
  • A method is required that changes the state from the job AVAILABLE

    to FINISHED

    or FAILED

    . The problem is that I'm not sure how to implement this when using the State pattern.

I have some solutions in mind, but I would like to know if there are other / better solutions to implement the state pattern correctly.

  • For the first problem, I would use a method public Collection<Job> filter(Collection<Job>)

    in State

    , which will return a subset of the given set of only those jobs that are relevant to the current state. The method getAvailableJobs()

    in Project

    will look like this. The disadvantage of this approach for me is that the connection grows when you Project

    also need to know about State

    .
  • For the second problem, I could either write a method update(State newState)

    where I could check if t224> has completed or not, or I could write a method fail()

    and a method finish()

    (both of State

    course). The problem with the second seems to be related to the fact that some of the state logic is returned in Job

    , but I'm not sure if the method is the update

    best method.

Hope I was clear enough and that someone can help me understand why these solutions are going to be good or bad enough and what alternatives there might be .

Thanks in advance.


Some additional codes to do a few more things:

public class Project {

    private final Set<Job> jobs;
    // constructors, getters, setters, ...
    // for updating, I need to be able to present only the available tasks
    public Collection<Job> getAvailableJobs() {
        return Status.AVAILABLE.filter(getJobs());
    }

}

public class Job {
    private final State state;
    // Constructors, getters, setters, ...
    // update either to finished or failed 
    // (Timespan is nothing more than a pair of times)
    public void update(Timespan span, State newState) {
        getState().update(this, span, newState);
    }

}

public enum State {
    AVAILABLE {
        @Override
        public void update(Job job, Timespan span, State newState) {
            if(newState == AVAILABLE || newState == UNAVAILABLE)
                throw new IllegalArgumentException();
            job.setSpan(span);
            job.setState(newState);
        }
    },
    UNAVAILABLE, FINISHED, FAILED;

    public void update(Job job, State newState) {
        throw new IllegalStateException();
    }

    public Collection<Job> filter(Collection<Job> jobs) {
        Collection<Job> result = new HashSet<>();

        for(Job j : jobs)
            if(j.getStatus() == this)
                result.add(j);

        return result;
    }
}

      

+3


source to share


4 answers


@Question 1: While only the project needs to filter its jobs, the filter method needs to be part of the class Project

. You can provide a generic method that filters on a specific state as well as your specific one (if this is used a lot, why not take it as part of your API):

class Project {
  private Set<Job> jobs;

  public Set<Job> filterJobs(JobState state) {
    return jobs.stream().filter(j -> j.getState() == state).collect(Collectors.toSet());
  }

  public Set<Job> availableJobs() {
    return filterJobs(JobState.AVAILABLE);
  }

}

      

Both work fine and are transparent to the user:



project.availableJobs();
project.filterJobs(JobState.<your state>);

      

@Question 2: Still thinking "how much state" is in it. Taking the description, there is only a state transition from AVAILABLE (2) and only one associated method. Based on my experience and understanding, design samples should be applied as needed, not from scratch. Your solution is fine, and it is. But given a few requirements outlined here, it could be much easier without a state pattern. (= YAGNI )

+1


source


Well, I've had a while and I really enjoy injecting templates, so I tried to implement your concept here. It looks like the challenge is how to maintain lists of jobs sorted by Job status when the state is hidden in Job. Below is an example of how I would go about doing this if you can't just use a field in a Job that should use public.

The assignment will contain a reference to the Project object, a reference to the state object, and define an interface IJob

(this is written in C #). Each state then ensures that the state is consistent with the environment by calling methods in Project to notify the changes. You will need to create an interface for the project that makes sense. I've introduced a structure here, but you will need to define your own, whatever is reasonable.



public class Project {

    private List<Job> listOfJobs;
    private List<Job> listOfAvailJobs;
    private List<Job> listOfNotAvailJobs;

    // Factory Method
    public Job CreateJob() {}

    public void JobIsAvailable(Job job) {
        listOfAvailJobs.Add(job);   
    }
    public void JobIsNotAvailable(Job job) {
        listOfNotAvailJobs.Add(job);
    }

}


public interface IJob {
    void Run();
    void Abort();
    void Delete();
}


public class Job : IJob {

    public Project project;
    protected JobState currentState;

    public Job(Project project) {
        this.project = project;
        currentState = new JobStateInit();
        project.JobIsAvailable(this);
    }

    // IJob Interface
    public void Run();
    public void Abort();
    public void Delete();

}

public abstract class JobState : IJob {

    protected Job job;

    public JobState(Job job) {
        this.Job = job;
    }

    // IJob Interface
    public void Run();
    public void Abort();
    public void Delete();

}

public class JobStateInit : JobState {
    // IJob Interface
    public void Run();
    public void Abort();
    public void Delete();
}

public class JobStateAvail : JobState {

    // IJob Interface
    public void Run() {
        this.job.project.JobIsNotAvailable(this.job);
    }
    public void Abort();
    public void Delete();
}

public class JobStateFailed : JobState {
    // IJob Interface
    public void Run() {
        throw new InvalidOperationException;
    }

    public void Abort() {}
    public void Delete() {}
}

      

+1


source


To avoid the coupling between job and state, you can move your filter method to a separate class, and you could further reduce coupling by setting an interface that only has a way to return state.

At this point, you have created a functional interface. If you are using Java 8, you can just as easily create a Lambda expression to do the same. Sure, it might be a little ahead of where you are, but hopefully you get the idea.

0


source


You reference the state pattern in your question, but most of the details around your question have nothing to do with the state pattern.

The state template will be limited only by the design of the interface for Job

and handling of the actions that can be performed on Job

. Each state it Job

is in must provide some implementation of that interface. i.e .: run (), quit (), abort (). These methods will be called on Job

, and the implementation will change depending on what state it is in Job

(i.e.: AVAILABLE, DONE, or FAILED).

To do this, you declare a class Job

and then an abstract class JobState

, with (concrete) subclasses JobStateAvail

, JobStateFailed

etc. Each subclass implements the Job interface. Any calls to Job from the client will then be delegated to the current JobState subclass for proper handling.

The relationship between Job

and Project

is a little unclear to me. However, this relationship is not related to the state pattern, so you might be confused. I need more information about the purpose of the Project class and how jobs are created. To get lists of jobs according to state, what you can do is indicate that Job adds itself to a specific list in Project

. Perhaps this is what you intend?

0


source







All Articles