'. '

OpenClass

From APIDesign

Jump to: navigation, search

When dealing with objects that produce and consume generic type of unknown bounds, it is necessary to open the type before its use. One can either use an OpenMethod or, in case the open needs to last longer than just during method execution, an OpenClass.

Imagine a generified API to access project configuration:

Code from ProjectConfigurationCorrect.java:
See the whole file.

interface ProjectConfigurationProvider
    <Configuration extends ProjectConfiguration> {
    public Configuration[] getConfigurations();
    public Configuration getActive();
    public void setActive(Configuration c);
}
interface ProjectConfiguration {
    public String getDisplayName();
}
 

The getActive' and setActive methods could take ProjectConfiguration class as parameter, but that would not be correct - it would allow a user of the API to take configuration from one project and try to activate it on different one. By use of <Configuration extends ProjectConfiguration> bound, this is prevented. Yet, this very complicates life of clients of this API, as following code:

Code from ProjectConfigurationCorrect.java:
See the whole file.

ProjectConfigurationProvider<?> provider = null; // obtain elsewhere;
provider.setActive(provider.getConfigurations()[0]);
 

no longer compiles. The problem is that provider is of unknown bound <?>. Whenever it is used, the compiler uses new unknown bound, as such the call to getter returns one unknown type and the setter needs another unknown type. They are not the same. To work around that, one needs to create new OpenMethod:

Code from ProjectConfigurationCorrect.java:
See the whole file.

private static <C extends ProjectConfiguration> void resetToZero(
    ProjectConfigurationProvider<C> provider
) {
    provider.setActive(provider.getConfigurations()[0]);
}
 

which opens the provider parameter and binds it to <C> bound. All references to that variable then represent the same type during the method execution or during existence of OpenClass instance:

Code from ProjectConfigurationCorrect.java:
See the whole file.

static void workWithProjectConfigurationProvider(
    ProjectConfigurationProvider<?> p
) {
    ResetToZero<?> rtz = ResetToZero.create(p);
    rtz.obtainFirst();
    // after a while
    rtz.apply();
}
 
static class ResetToZero<C extends ProjectConfiguration> {
    C active;
    final ProjectConfigurationProvider<C> provider;
 
    ResetToZero(ProjectConfigurationProvider<C> provider) {
        this.provider = provider;
    }
 
    static <C extends ProjectConfiguration> ResetToZero<C> create(
        ProjectConfigurationProvider<C> p
    ) {
        return new ResetToZero<C>(p);
    }
 
    public void obtainFirst() {
        active = provider.getConfigurations()[0];
    }
 
    public void apply() {
        provider.setActive(active);
    }
}
 

This is very annoying and definitely not clueless! As such exposing patterns that require OpenClass or OpenMethod to ClientAPI users is not really recommended.

On the other hand, as Singletonizer pattern shows, using this style in ProviderAPI, where the open is done once by the API infrastructure is quite OK.

Personal tools
buy