3

In a Spring Boot application, running on Java 8, I want to implement a generic behavior to process different types of commands.

The code below illustrates the solution I had in mind.

A generic processor pattern

public interface Processor<C extends Command> {
    TypeCommand getCommandType();
    void processCommand(C command);
}

public abstract class AbstractProcessor<C extends Command> implements Processor<C> {
    @Override
    public void processCommand(C command) {
        // Some common stuff 
        // ...

        // Specific process
        Result result = executeCommand(command);

        // More common stuff
        // ...
    }

    protected abstract Result executeCommand(C command);
}

@Component
public AddCommandProcessor extends AbstractProcessor<AddCommand> {

    @Override
    protected Result executeCommand(AddCommand addCommand) {
        // Execute command
        // ...
        return result; 
    }

    @Override
    public TypeCommand getCommandType() {
        return TypeCommand.ADD_COMMAND;
    }
}

The command :

public abstract class Command {
    private final String uid;
    private final LocalDateTime creationDate;
    private final TypeCommand type;
    // Constructor and getters omited ...
}

public class AddCommand extends Command {
    private final Double amount;
    // Constructor and getters omited ...
}

The service with the chain of responsibility :

@Service
public class MyService {

    private final List<Processor<? extends Command>> processors;

    @Autowired
    public MyService(final List<Processor<? extends Command>> processors) {
        this.processors = processors;
    }

    public void processCommand(final Command command) {
        processors.stream()
            .filter(p -> command.getType() == p.getCommandType())
            .findFirst()
            .ifPresent(processor -> processor.processCommand(command));
    }
}

Unfortunately, this code does not compile. The line :

.ifPresent(processor -> processor.processCommand(command));

failed to compiles with the message :

processCommand(capture <? extends Command>) in Processor cannot be applied to (Command)

I don't see any other way to do it as intended. Where am I wrong ?

Thanks a lot.

CrakBoom
  • 33
  • 5
  • 3
    Change the `private final List> processors;` to `private final List> processors;` if you can https://stackoverflow.com/questions/5857561/java-generics-fancy-capture-collision – André Jul 31 '17 at 09:36
  • 5
    [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/q/2723397/2711488) – Holger Jul 31 '17 at 10:19
  • Changing `private final List> processors;` to `private final List> processors;` compiles, but then Spring will not inject the Processor's implementations into the collection :'( – CrakBoom Jul 31 '17 at 14:42
  • 1
    @CrakBoom, you can configure the bean injection, then you wouldn't need the unsafe casting. https://stackoverflow.com/a/21067356/1018903 – André Aug 01 '17 at 01:38

1 Answers1

1

You should be able to drop the generics on Processor<? extends Command> processor into just Processor processor which will transform the error into a warning about raw types which you can mute with @SuppressWarnings("rawtypes")

.ifPresent(processor -> ((Processor) processor).processCommand(command));

Another method is to make processCommand generic and upcast Processor<? extends Command> processor to the generic type, like this:

public <C extends Command> void processCommand(final C command) {
    processors.stream()
        .filter(p -> command.getType() == p.getCommandType())
        .findFirst()
        .map(processor -> (Processor<C>) processor)
        .ifPresent(processor -> processor.processCommand(command));
}

Which will give you a warning about an unchecked cast which you can mute with @SuppressWarnings("unchecked").

Both of these methods will throw a ClassCastException if a Command that returns a specific TypeCommand isn't a subclass of the type that the (first) Processor that returns the same TypeCommand expects (e.g. if SubCommand returns TypeCommand.ADD_COMMAND but isn't a subclass of AddCommand).

Raniz
  • 10,882
  • 1
  • 32
  • 64
  • Both methods work. The `ClassCastException` is not a problem here, I have only one `Command` implementation by `TypeCommand` value. Thanks ! – CrakBoom Jul 31 '17 at 14:35