'Map binding of generic factories

Suppose we have a couple of Shape implementations. Each implementation is created by a ShapeFactory from a corresponding ShapeConfig.

interface Shape {}

class Circle implements Shape {}
class Square implements Shape {}
...

-----

abstract class ShapeConfig {
    abstract String getName();
}

class CircleConfig extends ShapeConfig {}
class SquareConfig extends ShapeConfig {}
...

-----

interface ShapeFactory<C extends ShapeConfig> {
    Shape create(C config);
}

class CircleConfig implements ShapeFactory<CircleConfig> {}
class SquareConfig implements ShapeFactory<SquareConfig> {}
...

Given a set of ShapeConfig objects, I want to construct a map binding of Shape objects by their configured name:

class ShapeModule extends AbstractModule {
    @Override
    protected void configure() {

        ShapeFactory<CircleConfig> circleFactory = new CircleFactory();
        ShapeFactory<SquareConfig> squareFactory = new SquareFactory();
        // ...

        MapBinder<String, Shape> shapesByName = newMapBinder(binder(), String.class, Shape.class);

        for (ShapeConfig config : getShapeConfigs()) {
            if (config instanceof CircleConfig) {
                Shape shape = circleFactory.create((CircleConfig) config);
                shapesByName.addBinding(config.getName()).toInstance(shape);
                continue;
            }
            if (config instanceof SquareConfig) {
                Shape shape = squareFactory.create((SquareConfig) config);
                shapesByName.addBinding(config.getName()).toInstance(shape);
                continue;
            }
            // ...
        }
    }
}

In the above example, the map binder is pretty much useless but I hope it clarifies my intent. What would a proper solution look like?



Solution 1:[1]

In your example it looks like you:

  • Have static Shape Configs (at load time)
  • Are attempting to use Shape circle = shapes.get("circle"); returning effectively a singleton Circle.

Which if you want, seems pretty spot on. But if you wanted to dynamically generate Shape(s) based on runtime ShapeConfig(s) then you might want to do something like:

MapBinder<ShapeConfig, ShapeFactory> shapeFactories = MapBinder.newMapBinder(binder(), ShapeConfig.class, ShapeFactory.class);
shapeFactories.addBinding(CircleConfig.class, CircleFactory.class);
shapeFactories.addBinding(SquareConfig.class, SquareFactory.class);
// etc.

Which would then be used as:

public class ShapeManager {
  @Inject Map<ShapeConfig, ShapeFactory> factories;

  public <T implements ShapeConfig, R implements Shape> R manage(T config) {
    Shape shape = factories.get(config.class).create(config);
    // do stuff?
    return shape;
  }
}

which you can then call how you like:

Shape shape = manager.manage(new CircleConfig(...));
// shape should be a Circle

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 kendavidson