Neglected Sanity Neglected Sanity - 3 months ago 10
Java Question

return and use different classes in java

Not sure if the title does this justice. I am kind of new to Java and trying to figure out how to have a single class use different "services". Let say I have an APIRequest class, this class needs to be able to use different APIs depending on what is needed. Example. I need to ship a package, if the package is below 32OZ I need to use Endicia, else I need to use FedEx. I have 2 "service" classes FedexRequest and EndiciaRequest. I am trying to allow the APIRequest class use either one depending on what the weight of the package. I created a class called APIService that has a static method called getService. it just creates a map of string name -> request class like so...

public class APIService {

private static Map<String, Object> services = new HashMap<>();

private static final Map<String, String> availableServices = new HashMap() {{
put("fedex", "FedexRequest");
put("endicia", "EndiciaRequest");
}};

public static Object getService(String type) {
if(services.containsKey(type)) {
return services.get(type);
}
return null;
}

static {
for(Map.Entry<String, String> serv : availableServices.entrySet()) {
try {
Class<?> cls = Class.forName(serv.getValue());
services.put(serv.getKey(), cls.newInstance());
} catch(Exception e) {
services.put(serv.getKey(), new Class[1]);
}
}
}


}

So now I can call APIService.getService("fedex"); however I am having a really hard time trying to figure out how to use that in my APIRequest class, because I would need to do something like...

this.service = (FedexRequest) APIService.getService("fedex");
//or
this.service = (EndiciaRequest) APIService.getService("endicia);


but that breaks the whole dynamic part of the equation, what if I need to add another service later?
I tried having both FedexRequest and EndiciaRequest implement a Request interface, then use

this.service = (Request) APIService.getService("fedex");


but that gives me a Java.lang.Class error saying it cannot be cast to Request. I am assuming it is because Request is an interface so you cannot use cls.newInstance() on an implementing class then cast to the interface.

I am really lost on how to allow my APIRequest class to use either FedexRequest or EndiciaRequest, without specifically using the type casting, so that it can be dynamic and we could add a service later without recoding the whole thing. I come from PHP where this would be extremely simple, since you do not have to explicitly define a type. Any help would be greatly appreciated. Thank you.

Answer

If I were you I would do the following:

This is the implementation of Service interface:

public interface Service {
    public void performAction();
    //other common functions...
}

A small modification to your APIService class:

public class APIService {

private static Map<String, Service> services = new HashMap<>();

private static final Map<String, String> availableServices = new HashMap() {{
    put("fedex", "com.finity.shipping.api.fedexapi.FedexRequest");
    put("endicia", "com.finity.shipping.api.endiciaapi.EndiciaRequest");
}};

public static Service getService(String type) {
      return services.get(type);
}

static {
    for(Map.Entry<String, String> serv : availableServices.entrySet()) {
        try {
            Class<?> cls = Class.forName(serv.getValue());
            services.put(serv.getKey(), cls.newInstance());
        } catch(Exception e) {
            services.put(serv.getKey(), new Class[1]);
        }
    }
}

}

Every time you need a service to be added to your application just implement the Service interface:

public class FedexRequest implements Service {
    public void performAction() {
        //do something
    }
}

And finally in your class where you use this.service:

Service service;
...
this.service = APIService.getService("fedex");
this.service.performAction();
Comments