1

I'm having trouble implementing factory with generics for a specific use case

I have model classes:

class BaseModel { }

class ModelA extends BaseModel { }

class ModelB extends BaseModel { }

And corresponding services:

class Service<T extends BaseModel> { }

class ServiceA extends Service<ModelA> {}

class ServiceB extends Service<ModelB> {}

My Factory class, to create service according to the model it services:

class Factory {
    private Map<Class<? extends BaseModel>, Class<? extends Service>> registry;

    Factory(){
        registry = new HashMap<>();
        registry.put(ModelA.class, ServiceA.class);
        registry.put(ModelB.class, ServiceB.class);
    }

    Service getService(Class<? extends BaseModel> clazz) throws IllegalAccessException, InstantiationException {
        return registry.get(clazz).newInstance();
    }
}

And a class that uses the factory

class Handler<T extends BaseModel>{
    private Service<T> myService;

    Handler(Class<T> modelClass) {
        Factory fact = new Factory();
        try {
            myService = fact.getService(modelClass);
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
}

I get an warning for the line that uses the factory to get the service:

"Unchecked assignment 'Service' to 'Service<T>'"

I understand why I get the message, since the getService method returns Service, but I need Service<T> and puzzled about how I can change the code to get Service<T>

Any suggestions?

Thanks!

bizz
  • 163
  • 2
  • 15
  • And what *return type* does the method `getService()` have? is it `Service` or `Service` ? – Nir Alfasi Nov 24 '17 at 00:07
  • You could use ` Service getService(Class clazz)` and cast your returned value to `Service`. You will still get an unchecked warning in the method itself, but that's one of the weaknesses of runtime erasure of Java generics. – bcsb1001 Nov 24 '17 at 00:45
  • There are two problems: First, there is no way to make a Map where different keys correspond to different generic types. It simply can’t be done. Second, there is no way to safely use reflection to create a generically typed object (like `ArrayList` or `Service`). You would be better off eliminating generic typing from Service. Can you show some examples of Service methods that use the model subtype? – VGR Nov 24 '17 at 03:37
  • @alfasin I would like it to be Service but I'm I don't know how to make it happen. – bizz Nov 24 '17 at 12:19
  • @bcsb1001 If I need to cast then I can just use casting for it all instead no? If I'm staying with a warning then I can leave this implementation as it, don't you think? – bizz Nov 24 '17 at 12:20
  • @VGR basically I would like to create a service for the type the handler uses. My handler is generic but I have a service for each type it might receive. I wanted to keep the model separate from the business logic so I created a service for each type. I'm starting to think it's a wrong design for this case – bizz Nov 24 '17 at 12:25
  • Why does Service need to be generic? – VGR Nov 24 '17 at 14:26
  • @VGR The code sample doesn't reflect that. Since the service is exposing methods that should be in the context of the same model in the return value, I cannot assign it back to a generic like so (in the handler) "T myModel = myService.doSomethingAndReturnAModel()" – bizz Nov 26 '17 at 08:01
  • And that is why I asked you to provide an example of a Service method. So, a Service essentially acts as a factory for model instances of a specific type? – VGR Nov 26 '17 at 15:42

2 Answers2

2

Here, you can capture the class as T and return a instance of it (with cast):

<T extends BaseModel> Service<T> getService(Class<T> clazz) throws IllegalAccessException, InstantiationException {
    return (Service<T>) registry.get(clazz).newInstance();
}
Marcos Vasconcelos
  • 18,136
  • 30
  • 106
  • 167
1

Java generics and inheritance sometimes interact counter-intuitively, i.e. Service<ModelA> is not related (in terms of inheritance) to Service<BaseModel>. However, you can write two implementations of getService() and call the service constructors explicitly (not through reflection). Your Handler will need an instance of your Model, such that Java overriding will select the correct service-constructor for you.

What I haven't thought about in my original answer: you need to catch for all BaseModel implementations in your factory (overriding will pick the most specific method) and also your Service has to be implemented against an interface to pull the strings together.

class BaseModel { }

class ModelA extends BaseModel { }

class ModelB extends BaseModel { }

interface Service { }

class ServiceImplementation<T extends BaseModel> implements Service { }

class ServiceFactory {
    public static Service getService(ModelA model) { return new ServiceImplementation<ModelA>(); }
    public static Service getService(ModelB model) { return new ServiceImplementation<ModelB>(); }
    public static Service getService(BaseModel model) { 
        throw new UnsupportedOperationException("Unknown Service Model");
    }
}

class Handler<T extends BaseModel> {
    private Service service;

    Handler(T model) {
        service = ServiceFactory.getService(model);
    }
}