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

时间:2018-04-03 08:18:37

标签: java design-patterns

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

Task可以包含任意Objects,因此我无法从像JSON这样的“数据”文件类型加载它们 - 只有Java代码足以创建Task s 。此外,为了可维护性,我想在单独的Java文件中创建每个Task

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

然后,应该很容易收集Task s:

List<Task> tasks = gatherTasksInGroup("impossible-tasks");

没有愚蠢和容易出错的事情,如:

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

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

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

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

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

2 个答案:

答案 0 :(得分:3)

一种解决方案是使用Composite Pattern,其中接口为Task,终端类为ExecutableTask(问题中为Task),以及复合class是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;
    }
}

这将允许您以与执行单个任务完全相同的方式执行任务组。例如,假设我们有一些benchmark方法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);

也可以通过创建匹配可以完成的不同任务的各种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");
    }
}

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

TaskGroup group = new TaskGroup("differentTasksGroup");
group.addTask(new ColorTask("red"));
group.addTask(new TimedTask(2));
group.execute();

这将导致以下输出:

Executed task with color red
Executed timed task for 2 seconds

加载新任务

使用此复合结构,您可以使用Java提供的Service Provider Interface (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();
    }
}

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

public interface ServiceTask extends Task {}

服务加载逻辑变为:

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

答案 1 :(得分:1)

OP,回答他们自己的问题。

我最终使用了一种方法,其中每个Task - 创建类(TaskCreator)将自己注册到类的静态初始化程序中的一个组,并且反射用于显式初始化这些类。

我在TaskGroup.java中使用Google Guava Reflection库迭代类,因为我已经在其他地方依赖了Guava。 this question中介绍了其他方法。

这种方法成功实现了我所述目标的前4个,但不是第5个。 (可能无法同时满足所有5个目标。)

这种方法的优点是:

  • 每个Task都是在单独的文件中创建的,例如HaltingProblem.javaPerformAddition.java目标1
  • 创建Task并将其与群组相关联只涉及1个文件目标2
  • 仅在Task目标3 )中请求时创建
  • TaskGroup.getTasks()个对象
  • 创建新组是单行更改,即在TaskGroup.java中添加枚举值(目标4
  • 通过复制/粘贴现有​​的TaskCreator创建一个新的TaskCreator是非常不言自明的,很难弄清楚
  • 每个Task都可以以明显的方式将Main.java分配给任意数量的群组
  • 我们可以遍历所有群组(例如Task
  • 我们通过其TaskCreatorMain.java中的示例)
  • 了解每个String的句柄
  • 编译器确保任务只能注册到存在的组(使用TaskGroup组名时不会发生这种情况)

有些缺点是:

  • 我们不满足目标5 - Task的静态初始化程序在类路径中的类上循环
  • 在不依赖某些反射库的情况下实现此方法很棘手
  • 如果TaskCreator位于多个组中,则可以多次创建package my.packagename; public class Task { private String name; // and other data public void setName(String name) { this.name = name; } public String getName() { return name; } // and other getters/setters } (尽管可以实现缓存机制来解决此问题)
  • 这种方法很大程度上依赖于静态初始化器,这通常是不受欢迎的
  • 每个package my.packagename; import my.packagename.Task; public interface TaskCreator { public abstract Task createTask(); } 都是一个单身人士,通常不喜欢

代码示例

部件为inspired by this answer

Task.java

package my.packagename;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.*;

import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;

public enum TaskGroup {
    IMPOSSIBLE,
    EASY,
    COOL;

    static {
        Class<?> creatorBase = TaskCreator.class;
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            Set<ClassInfo> infoSet = ClassPath.from(loader).getTopLevelClassesRecursive("my.packagename");
            for (ClassInfo info : infoSet) {
                Class<?> cls = info.load();
                if (creatorBase.isAssignableFrom(cls) && !Modifier.isAbstract(cls.getModifiers()))
                    Class.forName(cls.getName()); // runs static initializer for cls
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private final List<TaskCreator> creators = new ArrayList<>();

    public void register(TaskCreator task) {
        creators.add(task);
    }

    public List<Task> getTasks() {
        List<Task> tasks = new ArrayList<>();
        for (TaskCreator creator : creators)
            tasks.add(creator.createTask());
        return tasks;
    }
}

TaskCreator.java

package my.packagename;

public enum HaltingProblem implements TaskCreator {
    INSTANCE;
    static {
        TaskGroup.IMPOSSIBLE.register(INSTANCE);
        TaskGroup.COOL.register(INSTANCE);
    }

    public Task createTask() {
        Task task = new Task();
        task.setName("Halting problem");
        // and other halting-problem-specific Task setup
        return task;
    }
}

TaskGroup.java

package my.packagename;

public enum PerformAddition implements TaskCreator {
    INSTANCE;
    static {
        TaskGroup.EASY.register(INSTANCE);
        TaskGroup.COOL.register(INSTANCE);
    }

    public Task createTask() {
        Task task = new Task();
        task.setName("Perform addition");
        // and other addition-specific Task setup
        return task;
    }
}

HaltingProblem.java

package my.packagename;

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Task> impossible = TaskGroup.IMPOSSIBLE.getTasks();
        for (Task task : impossible)
            System.out.println("Impossible task: " + task.getName());
        System.out.println();

        List<Task> easy = TaskGroup.EASY.getTasks();
        for (Task task : easy)
            System.out.println("Easy task: " + task.getName());
        System.out.println();

        List<Task> cool = TaskGroup.valueOf("COOL").getTasks();
        for (Task task : cool)
            System.out.println("Cool task: " + task.getName());
        System.out.println();

        // Can iterate through groups
        for (TaskGroup group : TaskGroup.values())
            System.out.println("Group " + group + " has " + group.getTasks().size() + " task(s)");

        // Can easily get a specific Task through its creator
        Task haltingProblemTask = HaltingProblem.INSTANCE.createTask();
    }
}

PerformAddition.java

{{1}}

Main.java

{{1}}