使用状态模式获取具有特定状态的对象

get objects with a certain state using state pattern

我被分配设计一个软件(在 java 中),其中包含项目和工作,其中每个工作都有一个状态。当我们了解 GoF-patterns 时,State 模式对我来说似乎是一个显而易见的选择,但现在我在实施时遇到了一些麻烦。

在我当前的设计中,我有一个 Job-class 来表示具有特定状态的作业和一个 Project-class 具有作业列表.为了表示四种可能的状态(AVAILABLE - UNAVAILABLE - FINISHED - FAILED),我用在枚举。

我遇到的状态模式问题如下:

  1. Project 中,我想要一种方法 return 项目的所有 AVAILABLE 作业(甚至可能是 return 所有作业的方法即 FINISHEDFAILED),但状态模式未提及如何执行此操作。我们还听说在使用状态模式时不应使用 public boolean isAvailable() in JobState 的方法(因为这正是使用此模式时想要避免的) .
  2. 需要一种将状态从 AVAILABLE 作业更改为 FINISHEDFAILED 的方法。这个问题是我不太确定在使用状态模式时如何实现它。

我有一些解决方案,但我想知道是否有other/better解决方案可以很好地实现状态模式。

  1. 对于第一个问题,我将在 State 中实施一个 public Collection<Job> filter(Collection<Job>) 方法,该方法将 return 给定集的子集仅包含那些处于当前状态的作业。 Project 中的 getAvailableJobs() 方法如下所示。这种方法对我来说的缺点是,当 Project 也需要了解 State 时耦合会增加。
  2. 对于第二个问题,我可以编写一个 update(State newState) 方法来检查 newState 是完成还是失败,或者我可以编写一个方法 fail() 和一个方法 finish()(当然都在 State 中)。第二个问题似乎是一些状态逻辑在 Job 中返回的事实,但我不确定 update 方法是否也是最好的方法。

我希望我说得足够清楚,希望有人能帮助我理解为什么这些解决方案足够好或不好,以及可能的 替代方案

提前致谢。


一些额外的代码使更多的事情变得清晰:

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;
    }
}

为了避免 Job 和 State 之间的耦合,您可以将 "filter" 方法移动到一个单独的 class,并且您可以通过让 Job 实现一个只有一个方法的接口来进一步减少耦合用于返回状态。

至此,您已经创建了一个功能界面。如果您使用 Java 8,您可以轻松地创建一个 Lambda 表达式来做同样的事情。当然,这可能有点超前,但希望您能理解。

您在问题中引用了状态模式,但围绕您问题的大部分细节与状态模式无关。

状态模式将仅限于 Job 的界面设计,以及处理可在 Job 上执行的操作。 Job 所在的每个状态都必须提供此接口的某种实现。即:运行()、退出()、中止()。这些方法将在 Job 上调用,并且实现将根据 Job 处于哪个状态(即:可用、完成或失败)而改变。

为了实现这一点,你声明了一个 class Job 然后是一个抽象的 class JobState,带有(具体的)subclasses JobStateAvailJobStateFailed 等。每个子class 将实现 Job 接口。客户端对 Job 的任何调用都会被委托给当前的 JobState subclass 以进行适当处理。

JobProject 之间的关系我有点不清楚。然而,这种关系与状态模式无关,所以这就是您可能会感到困惑的原因。我需要有关项目 class 的目的以及如何创建工作的更多信息。要根据状态获得作业列表,您可以做的是让作业将自己添加到 Project 中定义的列表中。也许这就是您的意图?

好吧,我有一些时间,而且我确实喜欢实现模式,所以我尝试在这里实现您的概念。似乎挑战在于当状态隐藏在作业中时如何维护按作业状态排序的作业列表。下面是我会怎么做,如果你不能在 Job 中只使用 public 的字段。

Job 将持有对 Project 对象的引用,对其状态对象的引用,并定义接口 IJob(这是用 C# 编写的)。然后,每个状态通过调用 Project 中的方法来通知它更改,从而确保状态与其环境的一致性。您必须在 Project 上创建一个有意义的界面。我在这里放置了一个结构,但您必须定义自己的结构,只要有意义即可。

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: 只要项目必须过滤其作业,过滤方法就应该是 Project class 的一部分。您可以提供一种通用方法,按给定状态和您的具体状态进行过滤(如果经常使用,为什么不将其作为您的一部分 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);
  }

}

两者都工作正常并且对用户透明:

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

@问题2:还在想"how much state"在里面。根据描述,只有 AVAILABLE (2) 的状态转换,并且只有一种方法与之相关。根据我的经验和理解,如果有必要,应该应用设计模式,而不是从头开始。你的解决方案就这样很好。但是考虑到这里描述的几个要求,如果没有状态模式,它可能会简单得多。 (=YAGNI)