DeejUK
DeejUK

Reputation: 13501

Java Factory to create Generic parameterised on the type passed

I've got a hangover, and I'm being particularly slow today.

I want to pass a subclass of Intent to some factory, and get back an Handler<? extends Intent>, where the parameterised type matches that of the argument passed to the factory.

public interface HandlerFactory
{
    <I extends Intent> Handler<I> build(I intent);
}

I'm struggling with the implementation of this. I don't want a big nasty if/else checking for all the different types of Intent, and then creating the appropriate Handler<> for each one, and casting it based on which conditional block we end up in. I don't want to use some variation of the Visitor pattern, as then my factory becomes coupled to every subclass of Intent/Handler<? extends Intent>. Something more 'pluggable' seems desirable.

How can I implement this without some filthy casting? The simplified case below doesn't compile because whilst OrgHandler implements Handler<OrgIntent>, the compiler doesn't know that the Intent passed (I) was an OrgIntent and not any other subclass.

public class SimpleHandlerFactory implements HandlerFactory
{    
    @Override
    public <I extends Intent> Handler<I> build(I intent)
    {
        // Filthy conditional that I want to avoid
        if(intent instanceof OrgIntent)
        {
            // Cast seems wrong
            return (Handler<I>) new SimpleOrgHandler(intent);
        }
        else if(intent instanceof SomeOtherIntent)
        {
            ...
    }
}

Update

The client code has a List<Intent> and doesn't really care about the subtypes; it needs to build a List<Handler> (I figure I'll need to have a raw types suppression there) and then call a method on each of those.

List<Intent> intents = orderedIntentBuilder.getOrderedIntents(declaration);
for(Intent intent : intents)
{
    Handler<Intent> handler = handlerFactory.build(intent);
    handler.resolve(someUnimportantArg);
}

Upvotes: 2

Views: 72

Answers (3)

Jorn Vernee
Jorn Vernee

Reputation: 33885

As it happens, the Intent classes are just data, and the rationale for the Handler classes is that I want to provide users of the library different ways of implementing behaviour that acts upon those data.

So here's the other idea I had.

You could instantiate the functionality before ambiguating the intent type, here is a rough example:

  • In this, SomeInputType would be the type from which you derive an intent's data.

  • The type Intent is renamed to IntentModel (since it's just a data class).

  • The type Handler is renamed to Intent (since this is where the real functionality is).

class OrgIntentModel implements IntentModel {
    public OrgIntentModel(SomeInputType input) {...}
}

// Abstract factory pattern
interface IntentFactory {
    // Constructs an Intent with custom behaviour.
    // Using the data in the specific model.
    Intent getOrgIntent(OrgIntentModel model);

    // Getters for each model type
}

// This class is implemented by the user.
// You could provide a 'DefaultFactory' as a template to extend.
class UserProvidedFactory implements IntentFactory {...}

class IntentBuilder { // i.e. IntentDeserializer

    private Map<String, Function<? super SomeInputType, ? extends Intent>> map;

    public IntentBuilder(IntentFactory factory) {
        map.put("OrgIntent", makeFunc(OrgIntentModel::new, factory::getOrgIntent));
        // other mappings...
    }

    // Helper method, to help with casting the method references.
    // 'U' represents the specific IntentModel.
    // SomeInputType -> IntentModel -> Intent
    private static <T, U, R> Function<T, R> makeFunc(
        Function<T, ? extends U> f1,
        Function<? super U, R> f2) {
        return f1.andThen(f2);
    }

    public Intent getIntent(SomeInputType input) {
        String intentType = input.getIntentType();
        return map.get(intentType).apply(input);
    }
}

Upvotes: 1

Jorn Vernee
Jorn Vernee

Reputation: 33885

I see the potential for a circular dependency here. You want to go from an Intent to a Handler while Intent is not supposed to know about Handler, and Handler already knows about Intent.

One could also wonder why a class needs a dedicated handler, shouldn't the handler functionality by part of the class itself then?


Any ways, one easy way is to add a getHandler method to Intent:

interface Handler<T extends Intent> {}

interface Intent {
    Handler<?> getHandler();
}

class OrgIntent {    
    @Override
    public Handler<OrgIntent> getHandler() {
        return new SimpleOrgHandler(this);
     }
}

...
List<Intent> intents = orderedIntentBuilder.getOrderedIntents(declaration);
for(Intent intent : intents)
{
    Handler<?> handler = intent.getHandler();
    handler.resolve(someUnimportantArg);
}

Which does create a circular dependency between the Intent and the Handler. Which you could be okay with, but there is also a way to solve that.


By extracting the data 'model' the handler operates on, into a third class:

interface IntentModel {...}

The Handler now operates on this model instead of the Intent itself.

class OrgIntent {    
    @Override
    public Handler<OrgIntentModel> getHandler() { // <-- Handler<OrgIntentModel>
        return new SimpleOrgHandler(this.model); // passing only model
    }
}

Which creates the dependencies:

Intent -> IntentModel
Intent -> Handler
Handler -> IntentModel

In this case you might want to merge the Handler functionality with the IntentModel. There would be no circular dependencies there, because the model only operates on itself.

Upvotes: 2

H&#233;ctor
H&#233;ctor

Reputation: 26084

You can add the parameterized type to the factory class:

public interface HandlerFactory<I extends Intent> {
    Handler<I> build(I intent);
}

and your SimpleHandlerFactory:

public class SimpleHandlerFactory implements HandlerFactory<OrgIntent> {    
    @Override
    public Handler<OrgIntent> build(OrgIntent intent) {
        return new SimpleOrgHandler(intent);
    }
}

Upvotes: 1

Related Questions