Neglected Sanity
Neglected Sanity

Reputation: 1934

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.

Upvotes: 1

Views: 106

Answers (2)

davidxxx
davidxxx

Reputation: 131466

Pooya solution is good.

I will add something. You use some strings to represent things which are typable : constants and classes. Using reflection to initialize a factory where you handle only classes written in hard in some strings(for example com.finity.shipping.api.fedexapi.FedexRequest) and belonging to your own project seems to be a overhead.
In case where your factory don't know which classes it will instantiate, using reflection is meaningful. But it seems not be the case.

Besides, FEDEX and ENDICIA could be constant by using enum. It allows to type them and to avoid bad suprpises.

We would expect that your factory be more simple. Here an example :

public class APIService {

public static enum TypeRequest{
  FEDEX, ENDICIA;
}

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

  static {
      services.put(FEDEX, new FedexRequest());
      services.put(ENDICIA, new EndiciaRequest());       
  }

  public static Service getService(TypeRequest typeRequest) {
        return services.get(typeRequest);
  }

}

Upvotes: 0

Pooya
Pooya

Reputation: 6136

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();

Upvotes: 3

Related Questions