用于管理任务组的设计模式

pkp*_*pnd 4 java design-patterns

我有一个处理Task对象的系统,现在我想进行一些基准测试。具体来说,我将创建许多(~100)个Task对象,每个对象都属于一组任务,并且我想在整个任务组上运行系统。我想要一种设计,可以轻松创建新的Task并将其与组相关联(轻松实现基准套件的多样化)。只有少数几个组,因此可以接受一些每组基础设施。

Tasks 可以包含任意Objects,所以我不能从像 JSON 这样的“数据”文件类型加载它们——只有 Java 代码足够通用来创建Tasks。此外,为了可维护性,我想Task在单独的 Java 文件中创建每个:

// SolveHaltingProblem.java
public class SolveHaltingProblem {
    static Task getTask() {
        Task task = new Task();
        task.setName("SolveHaltingProblem");
        ... // Create task
        return task;
    }

    static String getGroup() {
        return "impossible-tasks";
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,应该很容易收集Tasks组:

List<Task> tasks = gatherTasksInGroup("impossible-tasks");
Run Code Online (Sandbox Code Playgroud)

没有一些愚蠢和容易出错的东西,比如:

List<Task> gatherTasksInGroup(String groupName) {
    List<Task> list = new ArrayList<>();
    if (groupName.equals("impossible-tasks")) {
        list.add(SolveHaltingProblem.getTask());
        list.add(SomeOtherHardProblem.getTask());
        ...
    } else if (...) {
        ... // ~100 more lines
    }
    return list;
}
Run Code Online (Sandbox Code Playgroud)

我提供上面的代码只是为了帮助传达我的需求,设计细节并不是一成不变的。也许最好有SolveHaltingProblem扩展的ImpossibleTaskGroup扩展TaskGroup......

我知道类向其他类注册自己的模式(有这个名字吗?),但我认为这种模式不适用于这里,因为我没有创建 的实例SolveHaltingProblem,因此我不能强制SolveHaltingProblem要运行的任何静态初始值设定项。我也试过在 StackOverflow 上搜索类似的问题,但我发现这个问题很难描述;如果这是重复的,我深表歉意。

总而言之,我的问题是:如何管理Tasks组,以便轻松添加新的Task并将其与组关联?一个好的设计应该具有以下属性(从最重要到最不重要的顺序):

  1. 每个Task都在一个单独的文件中创建
  2. 添加新文件Task并将其与组关联只涉及添加/更改一个文件
  3. 我们只Task为请求的组构造对象
  4. 通过为现有组复制/粘贴基础结构,只需稍作修改即可“轻松”创建新组
  5. 我们不遍历类路径中的所有类,也不遍历文件系统中的文件

Jus*_*ano 5

一种解决方案是使用Composite Pattern,其中接口是Task,终端类是ExecutableTaskTask您的问题中的内容),复合类是TaskGroup。一个示例实现如下:

public interface Task {
    public void execute();
}

public class ExecutableTask implements Task {

    @Override
    public void execute() {
        // Do some work
    }
}

public class TaskGroup implements Task {

    private final String name;
    private final List<Task> tasks = new ArrayList<>();

    public TaskGroup(String name) {
        this.name = name;
    }

    @Override
    public void execute() {
        for (Task task: tasks) {
            task.execute();
        }
    }

    public void addTask(Task task) {
        tasks.add(task);
    }

    public String getName() {
        return name;
    }
}
Run Code Online (Sandbox Code Playgroud)

这将允许您以与执行单个任务完全相同的方式执行任务组。例如,假设我们有一些方法,benchmark,基准 a Task,我们可以提供一个ExecutableTask(单个任务)或TaskGroup

public void benchmarkTask(Task task) {

    // Record start time

    task.execute();

    // Record completion time
}

// Execute single task
Task oneOffTask = new ExecutableTask();
benchmarkTask(oneOffTask);

// Execute a group of tasks
TaskGroup taskGroup = new TaskGroup("someGroup");
taskGroup.addTask(new ExecutableTask());
taskGroup.addTask(new ExecutableTask());
benchmarkTask(taskGroup);
Run Code Online (Sandbox Code Playgroud)

这种模式也可以通过创建各种Task实现(或扩展ExecutableTask)来扩展,以匹配可以完成的不同任务。例如,假设我们有两种不同类型的任务:彩色任务和定时任务:

public class ColorTask implements Task {

    private final String color;

    public ColorTask(String color) {
        this.color = color;
    }

    @Override
    public void execute() {
        System.out.println("Executed task with color " + color);
    }
}

public class TimedTask implements Task {

    private final long seconds;

    public TimedTask(int seconds) {
        this.seconds = seconds * 1000;
    }

    @Override
    public void execute() {
        Thread.sleep(seconds * 1000);
        System.out.println("Executed timed task for " + seconds + " seconds");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以创建一个TaskGroup不同的Task实现并在没有任何特定于类型的逻辑的情况下运行它们:

TaskGroup group = new TaskGroup("differentTasksGroup");
group.addTask(new ColorTask("red"));
group.addTask(new TimedTask(2));
group.execute();
Run Code Online (Sandbox Code Playgroud)

这将导致以下输出:

Executed task with color red
Executed timed task for 2 seconds
Run Code Online (Sandbox Code Playgroud)

加载新任务

有了这个复合结构,您就可以使用Java 提供的服务提供者接口 (SPI)。虽然我不会在这里详细介绍(链接页面包含了关于如何设置和注册服务的丰富知识),但总体思路是您可以让Task接口充当服务接口,然后加载您需要的各种任务。注册为服务。例如:

public class TaskServiceMain {

    public static void main(String[] args) {
        TaskGroup rootGroup = new TaskGroup("root");
        ServiceLoader serviceLoader = ServiceLoader.load(Task.class);

        for (Task task: serviceLoader) {
            rootGroup.addTask(task);
        }

        // Execute all of the tasks
        rootGroup.execute();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用复合层次结构的根(Task接口)作为服务接口可能会很奇怪,因此创建一个新的服务接口可能是一个好主意。例如,可以创建一个新界面,该界面仅充当标记界面,不添加任何新功能:

public interface ServiceTask extends Task {}
Run Code Online (Sandbox Code Playgroud)

服务加载逻辑则变为:

public class TaskServiceMain {

    public static void main(String[] args) {
        TaskGroup rootGroup = new TaskGroup("root");
        ServiceLoader serviceLoader = ServiceLoader.load(ServiceTask.class);

        for (ServiceTask task: serviceLoader) {
            rootGroup.addTask(task);
        }

        // Execute all of the tasks
        rootGroup.execute();
    }
}
Run Code Online (Sandbox Code Playgroud)