1

In my app there are objects representing tasks (stored in the DB). There will also be objects that have the actual implementation of a task (some business logic) - let's call them runners. So runners could i.e. implement the following interface:

public interface Runner<T> {
    void run(T task);
}

Given a task object, I need some nice way to create/get a runner so that I can run it.

I'd prefer to avoid something like

if(task instance of DoSomethingTask) {
    return new DoSomethingTaskRunner();
} else if(task instance of DoSomethingElseTask) {
    return new DoSomethingElseRunner();
}

because then whenever I create a new runner I also need to remember to add another if/else to the above block. It would be nice to just automatically get the runner that implements

Runner<myTask.getClass()> // pseudocode ;)

A nice way of doing this would be to make the Tasks have a "getRunner" method (adding a new Task I need to implement the method so I cannot forget it) but unfortunately due to project dependencies my Task objects cannot know anything about Runners.

Any ideas regarding how to do it in a nice way?

Btw.: I'm using Spring so it would be even nicer to get a Runner bean based on the passed task.

2
  • how about creating a runner dynamically with Class.forName() ? that way you only need to store the runner class name in the task Commented Feb 26, 2013 at 13:43
  • Erm, that's the entire point of the interface. You don't have to know what the concrete class is. Commented Feb 26, 2013 at 13:43

3 Answers 3

4

You need a RunnerFactory. If you don't want to test the class of the task, you could use the visitor pattern, which allows basically doing what you want (myTask.createRunner()), but without any coupling between the task and the runner:

public interface TaskVisitor<R> {
    R visitTask1(Task1 task1);
    R visitTask2(Task2 task1);
}

public interface Task {
    // ...
    <R> R accept(TaskVisitor<R> visitor);
}

public class Task1 implements Task {
    // ...
    @Override
    public <R> R accept(TaskVisitor<R> visitor) {
        return visitor.visitTask1(this);
    }
}

public class Task2 implements Task {
    // ...
    @Override
    public <R> R accept(TaskVisitor<R> visitor) {
        return visitor.visitTask2(this);
    }
}

public class RunnerFactory implements TaskVisitor<Runner> {
    @Override
    public Runner visitTask1(Task1 task1) {
        return new Task1Runner(task1);
    }

    @Override
    public Runner visitTask2(Task1 task2) {
        return new Task2Runner(task2);
    }
}

Now, to get a runner from a task, you just need to call

RunnerFactory factory = new RunnerFactory();
Runner runner = someTask.accept(factory);

And if you add a new Task type, you'll be foced to implement its accept() method, which will force you to add a new method to the visitor, and a new Runner implementation.

Sign up to request clarification or add additional context in comments.

1 Comment

thank you, this is an interesting pattern and this answer was quite educational to me. Perhaps I'll use this in the future, but for my current case I think Jose's solution fits my problem better.
1

If you follow a bean naming convention, for example runnerBeanName = Task class name + 'Runner' you could use the ApplicationContext as Runner factory.

for example:

public class RunnerFactory implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Runner getRunner(Task task) {
        return (Runner) applicationContext.getBean(task.getClass().getSimpleName() + "Runner");
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;

    }
}

You can use alias to bind one runner to many tasks.

If you don't like the factory coupled to Spring, you can also autowire a Map<String, Runner> that will be injected with all Runners in context by bean name.

1 Comment

thank you, I'm accepting this answer, because it's simple and whenever I'm adding a new Runner I don't need to change any other classes. I just use something like this: @Component(MyTask.class + RUNNER_BEAN_NAME_SUFFIX) public class MyTaskRunner implements TaskRunner<MyTask> ... This way when I change the name of the task class, the runner bean name will also change automatically :)
0

Hm... make map like:

Map<Class<? extends MyTask>, MyRunner> runners;

then in xml or @Configuration add mappings. In method getRunner(Class<? extends MyTask> myTask) just:

 MyRunner getRunner(Class<? extends MyTask> myTask){
    return runners.get(myTask);
 }

that's all. Whole 'ifology' now is wrapped by standard map#get

1 Comment

thanks for your answer, I decided to accept Jose's answer because that way I don't have to update any other classes - all changes in the Runner class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.