Stack Overflow Asked by George Z. on February 24, 2021
I want to execute commands for each product view. Consider 10 products views, and each of them can execute the PrintProductViewCommand
. This command, takes in constructor a ProductView
and prints its name. Since it is @Inject
able, the container will create a new ProductView each time the command is created. The following example shows what I want to do:
public class InjectionTest {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(ProductView.class);
bind(CommandExecutor.class);
bind(PrintProductViewNameCommand.class);
install(new FactoryModuleBuilder().implement(ProductView.class, ProductView.class)
.build(ProductViewFactory.class));
}
});
List<ProductView> productViews = new ArrayList<>();
ProductViewFactory factory = injector.getInstance(ProductViewFactory.class);
for (int i = 0; i < 10; i++) {
productViews.add(factory.create("Name: " + String.valueOf(i)));
}
System.out.println("Done creating");
//Now sometime in future, each product view calls print method
productViews.forEach(ProductView::print);
}
private static interface ProductViewFactory {
ProductView create(String name);
}
private static class ProductView {
private String name; //simulate a property
private CommandExecutor executor;
public ProductView() {
//Guice throws exception when this is missing
//Probably because it is being asked in PrintProductViewCommand
}
@AssistedInject
public ProductView(@Assisted String name, CommandExecutor executor) {
this.name = name;
this.executor = executor;
}
public String getName() {
return name;
}
//assume some time product view it self calls this method
public void print() {
executor.execute(PrintProductViewNameCommand.class);
}
}
@Singleton
private static class CommandExecutor {
@Inject
private Injector injector;
public void execute(Class<? extends Command> cmdType) {
injector.getInstance(cmdType).execute();
}
}
private static class PrintProductViewNameCommand implements Command {
private ProductView view;
@Inject
public PrintProductViewNameCommand(ProductView view) {
this.view = view;
}
@Override
public void execute() {
//Want to print "Name: something" here
System.out.println(view.getName());
}
}
private static interface Command {
void execute();
}
}
This problem is solved if I add a parameter to Command interface, and make it Command<T>
. Then CommandExecutor
will have this method:
public <T> void execute(Class<? extends Command<T>> cmdType, T parameter) {
injector.getInstance(cmdType).execute(parameter);
}
So, my PrintProductViewNameCommand
is now class PrintProductViewNameCommand implements Command<ProductView>
, and in product view :
public void print() {
executor.execute(PrintProductViewNameCommand.class,this);
}
However, the Command Pattern has no parameter in execute()
. I have also seen somewhere that adding a parameter is an anti-pattern.
Of course, the command is simple. Assume that the command has other dependencies too, like Services etc.
Is there a way I can achieve it? Perhaps I am doing something wrong, probably with the whole DI situation.
When not using dependency injection, I would do something like this:
ProductView view = new ProductView();
Command command = new PrintProductViewNameCommand(view);
view.setPrintCommand(command);
But how to it while using DI?
So this works, although I'm not sure if it's 100% what you want to do.
public class InjectionTest {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(CommandExecutor.class);
bind(ProductView.class);
install(new FactoryModuleBuilder()
.implement(ProductView.class, ProductView.class)
.build(ProductViewFactory.class));
install(new FactoryModuleBuilder()
.implement(PrintProductViewNameCommand.class, PrintProductViewNameCommand.class)
.build(PrintProductViewNameCommand.Factory.class));
}
});
ProductViewFactory factory = injector.getInstance(ProductViewFactory.class);
List<ProductView> productViews = new ArrayList<>();
for (int i = 0; i < 10; i++) {
productViews.add(factory.create("Name: " + i));
}
System.out.println("Done creating");
//Now sometime in future, each product view calls print method
productViews.forEach(ProductView::print);
}
private interface ProductViewFactory {
ProductView create(String name);
}
private static class ProductView {
private String name;
private CommandExecutor executor;
private PrintProductViewNameCommand printProductViewNameCommand;
@AssistedInject
public ProductView(@Assisted String name, PrintProductViewNameCommand.Factory printProductViewNameCommandFactory, CommandExecutor executor) {
this.name = name;
this.executor = executor;
this.printProductViewNameCommand = printProductViewNameCommandFactory.create(this);
}
public ProductView() {}
public String getName() {
return name;
}
//assume some time product view it self calls this method
public void print() {
executor.execute(printProductViewNameCommand);
}
}
@Singleton
private static class CommandExecutor {
public void execute(Command command) {
command.execute();
}
}
private static class PrintProductViewNameCommand implements Command {
private final ProductView view;
@AssistedInject
public PrintProductViewNameCommand(@Assisted ProductView view) {
this.view = view;
}
static interface Factory {
PrintProductViewNameCommand create(ProductView productView);
}
@Override
public void execute() {
//Want to print "Name: something" here
System.out.println(view.getName());
}
}
private static interface Command {
void execute();
}
}
Basically what you're running into is a cyclic dependency problem (https://github.com/google/guice/wiki/CyclicDependencies#use-factory-methods-to-tie-two-objects-together) that's also exasperated by a bit by the fact that you have an additional AssistedInject
in the ProductView
.
By the way I'm using Guice 3 in this example.
Answered by William Hammond on February 24, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP