Nati
Nati

Reputation: 1032

Factory Pattern with Static Factory method or Constructor

I have a hierarchy of classes RequestParams , all can be created with ExtArgs.

public interface RequestParams {
}

I also created a Factory RequestsFactory looking like this:

public class RequestFactory<P extends RequestParams> {

    private final Logger logger = LoggerFactory.getLogger(RequestFactory.class);
    final Class<P> paramsClass;

    public RequestFactory(Class<P> paramsClass) {
        this.paramsClass = paramsClass;
    }

    public Request<P> create(ExternalArgs args)  {
        P params = null;
        try {
            params = paramsClass.getDeclaredConstructor(ExternalArgs.getClass()).newInstance(args);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            logger.error("Failed to generate request", e);
        }
        return new Request<>("get", Collections.singletonList(params));
    }

    static <P extends RequestParams> RequestFactory<P> createFactory(
            final Class<P> paramsClass) {
        return new RequestFactory<P>(paramsClass);
    }

}

where my client code is s follows:

Request devInfoRequest = RequestFactory.createFactory(RequestType.SYSTEM_INFO.getRequestClass()).create(externalArgs);

I use this ENUM in order map request types:

public enum RequestType {
    SYSTEM_INFO(DeviceInfoParams.class),
    INTERFACES(InterfacesParams.class)
}

My question is - How can use this, since I can't add neither constructor nor static builder to the RequestParams interface (or abstract class) ?

EDIT: (After AdamSkywalker's answer): I want my client code to be:

Request<InterfacesParams> interfacesRequest =
                RequestFactory.<InterfacesParams>createFactory(RequestType.INTERFACES.create(externalArgs)).create("get");

But it requires the enum return function to match the generic type.

Upvotes: 2

Views: 441

Answers (2)

ruakh
ruakh

Reputation: 183361

As you've observed, the problem with the built-in enum feature is that every enum-value has to have the same type; they can't have different type arguments (in fact, the enum-class won't even accept a type parameter).

One option is to roll your own enumeration — just write a class RequestType<P extends RequestParam> with a private constructor and a normal static field for each instance.

But if the RequestType and RequestFactory<...> types really only have what you've shown here, then it probably makes more sense to just merge them, by writing:

public final class RequestFactory<P extends RequestParams> {
    public static final RequestFactory<DeviceInfoParams> SYSTEM_INFO =
        new RequestFactory<>(DeviceInfoParams::new);
    public static final RequestFactory<InterfacesParams> INTERFACES =
        new RequestFactory<>(InterfacesParams::new);

    private final Function<ExternalArgs, P> mRequestParamsCreator;

    private RequestFactory(final Function<ExternalArgs, P> requestParamsCreator) {
        mRequestParamsCreator = requestParamsCreator;
    }

    public Request<P> create(final ExternalArgs externalArgs) {
        final P requestParams = mRequestParamsCreator.apply(externalArgs);
        return new Request<P>("get", Collections.singletonList(requestParams));
    }
}

(Note: the above uses some Java 8 / JDK 1.8 features for conciseness. If you're still using an older version of Java, the above approach will still work, but you'll have to write a bit of boilerplate to fill in the gaps. Likewise if the relevant constructors of DeviceInfoParams and InterfacesParams declare any checked exceptions, since in that case they can't be implicitly converted to java.util.function.Functions.)

You can then create requests by writing (for example):

final Request<DeviceInfoParams> systemInfoRequest =
    RequestFactory.SYSTEM_INFO.create(externalArgs);
final Request<InterfacesParams> interfacesRequest =
    RequestFactory.INTERFACES.create(externalArgs);

As with AdamSkywalker's approach, this avoids reflection completely.

Upvotes: 1

AdamSkywalker
AdamSkywalker

Reputation: 11609

Maybe it's not exactly what you're looking for, but your code is so over engineered. Why not:

public enum RequestType {
    SYSTEM_INFO {
        public RequestParams create(ExternalArgs args) { return new DeviceInfoParams(args); }
    },
    INTERFACES {
        public RequestParams create(ExternalArgs args) { return new InterfacesParams(args); }
    };

    abstract RequestParams create(ExternalArgs args);
}

And then call it without reflection tricks:

Request req = new Request("get", singletonList(SYSTEM_INFO.create(args)));

You may still use your factory, but the point is to remove the reflection complexity.

Upvotes: 1

Related Questions