我有一个处理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
并将其与群组相关联?一个好的设计将具有以下属性(按从最重要到最不重要的顺序排列):
Task
都在单独的文件中创建Task
并将其与群组关联只涉及添加/更改一个文件Task
个对象答案 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.java
或PerformAddition.java
(目标1 )Task
并将其与群组相关联只涉及1个文件(目标2 )Task
(目标3 )中请求时创建TaskGroup.getTasks()
个对象
TaskGroup.java
中添加枚举值(目标4 )TaskCreator
创建一个新的TaskCreator
是非常不言自明的,很难弄清楚Task
都可以以明显的方式将Main.java
分配给任意数量的群组Task
)TaskCreator
(Main.java
中的示例)String
的句柄
TaskGroup
组名时不会发生这种情况)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();
}
都是一个单身人士,通常不喜欢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}}