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 theAVAILABLE
jobs in the project (and maybe even those that return all jobs that are eitherFINISHED
orFAILED
), but the status template does not mention how to do this. We've also heard that using methods aspublic boolean isAvailable()
inJob
orState
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
toFINISHED
orFAILED
. 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>)
inState
, which will return a subset of the given set of only those jobs that are relevant to the current state. The methodgetAvailableJobs()
inProject
will look like this. The disadvantage of this approach for me is that the connection grows when youProject
also need to know aboutState
. - 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 methodfail()
and a methodfinish()
(both ofState
course). The problem with the second seems to be related to the fact that some of the state logic is returned inJob
, but I'm not sure if the method is theupdate
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;
}
}
source to share
@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 )
source to share
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() {}
}
source to share
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.
source to share
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?
source to share