Search code examples
javadesign-patternsserviceloader

How to create a composite that uses components from a provider where it should itself be added to?


The title maybe a bit complicated but I didn't see any other way of verbalizing my problem.

I'm working on a project where certain components are initialized by loading several files and transforming them into Java components, others are loaded by a ServiceLoader as they are alreaddy existing as Java classes. All of these objects are then mapped by a provider so that they can be retrieved from there.

public class Provider {

    private Map<String, Component> map;

    public Provider() {
        /*
         * Load files, transform, and add them to the map 
         * with keys being their names
         */
        /*
         * Load Java components and add them to the map 
         * with keys being their names
         */
    }

    public Component getComponent(String name) {
        return this.map.get(name);
    }
}

public interface Component {
    public Number execute();
}

I am now about to add some more components directly in Java but they are more of a composite meaning that they use components which were previously loaded into the provider. Hence, I must gather the required components from the provider, add them to the composite and add the composite itself to the provider.

public class Composite implements Component {
    private Component component1, component2;

    public Composite() {
        Provider provider = new Provider();
        this.component1 = provider.getComponent("Comp1");
        this.component2 = provider.getComponent("Comp2");
    }

    public Number execute() {
        return this.component1.execute() + this.component2.execute();
    }
}

The composite can be added to the Provider by simply adding it to a specific META-INF file. Then it will be handled by the ServiceLoader.

For me, this is somehow a cyclic dependency. I have to define, in a META-INF file, which Java classes should be gathered by the ServiceLoader. This would include the newly implemented composite as well. But that one cannot be created as long as all the other metrics have been loaded by the provider.

Is there any pattern or different approach I can use to solve that problem?


Solution

  • More likely you should have a chain. So instead of creating a new instance of Provider, in your Composite, you should create an interface let say called CompositeComponent, which will extend Component and will have one more method init(Provider provider). Or may be better to show in code:

    public interface CompositeComponent extends Component {
    
        void init(Provider provider);
    
    }
    

    So and then in your Provider:

    public Component getComponent(String name) {
        Component component = this.map.get(name);
        if (component instanceof CompositeComponent) {
            ((CompositeComponent)component).init(this);
        }
    }