简体   繁体   中英

Bounded wildcard in return type of static factory pattern

I've read in Effective Java that you should not use bounded wildcards as return types, but I don't know how should I do this then. The only way my code compiles is by using RequestCloner<? extends HttpUriRequest> RequestCloner<? extends HttpUriRequest> as return type in the static factory. Am I doing something wrong or is there a workaround?

NOTE: One thing to be noted is that HttpUriRequest has the method setHeader , but only HttpPost has the method setEntity .

abstract class RequestCloner<T extends HttpUriRequest> {

  protected T clonedRequest;

  private enum RequestType {
    GET, POST, DELETE
  }

  static RequestCloner<? extends HttpUriRequest> newInstance(
      String type, String url) {
    RequestType requestType = RequestType.valueOf(type);
    switch (requestType) {
    case GET:
      return new GetRequestCloner(url);
    case POST:
      return new PostRequestCloner(url);
    case DELETE:
      return new DeleteRequestCloner(url);
    default:
      throw new IllegalArgumentException(String.format(
          "Method '%s' not supported",
          type));
    }
  }

  public abstract HttpUriRequest clone(HttpServletRequest servletRequest) throws IOException;

  protected void cloneHeaders(HttpServletRequest servletRequest) {
    @SuppressWarnings("unchecked")
    Enumeration<String> e = servletRequest.getHeaderNames();
    while (e.hasMoreElements()) {
        String header = e.nextElement();
        if (!header.equalsIgnoreCase("Content-Length")
                && !header.equalsIgnoreCase("Authorization")
                && !header.equalsIgnoreCase("Host")) {
            clonedRequest.setHeader(new BasicHeader(header, servletRequest.getHeader(header)));
        }
    }
  }
}

...

class GetRequestCloner extends RequestCloner<HttpGet> {

  GetRequestCloner(String url) {
    this.clonedRequest = new HttpGet(url);
  }

  @Override
  public HttpUriRequest clone(HttpServletRequest servletRequest) {
    cloneHeaders(servletRequest);
    return clonedRequest;
  }
}

...

class PostRequestCloner extends RequestCloner<HttpPost> {

  private static final int MAX_STR_LEN = 1024;

  PostRequestCloner(String url) {
    this.clonedRequest = new HttpPost(url);
  }

  @Override
  public HttpUriRequest clone(HttpServletRequest servletRequest) throws IOException {
    cloneHeaders(servletRequest);
    cloneBody(servletRequest);
    return clonedRequest;
  }

  private void cloneBody(HttpServletRequest servletRequest) throws IOException {
    StringBuilder sb = new StringBuilder("");
    BufferedReader br = new BufferedReader(new InputStreamReader(
            servletRequest.getInputStream(),
            "UTF-8"));
    String line = "";
    while ((line = br.readLine()) != null && sb.length() < MAX_STR_LEN) {
        sb.append(line);
    }
    br.close();
    clonedRequest.setEntity(new StringEntity(sb.toString(), "UTF-8"));
  }
}

...

class DeleteRequestCloner extends RequestCloner<HttpDelete> {

  DeleteRequestCloner(String url) {
    this.clonedRequest = new HttpDelete(url);
  }

  @Override
  public HttpUriRequest clone(HttpServletRequest servletRequest) {
    cloneHeaders(servletRequest);
    return clonedRequest;
  }
}

Looking at your code, your class does not need to be generic. Looking further, there is the odd issue where the caller passes in a URL to create a cloner, then passes a HttpServletRequest (which could in theory be a different type of request) into the clone method.

I can see two kinds of solutions, depending on whether you really need RequestCloner to be generic.

If RequestCloner Does Not Need To Be Generic

Change the base class as follows:

abstract class RequestCloner {

  private enum RequestType {
    GET, POST, DELETE
  }

  public static HttpUriRequest cloneRequest(HttpServletRequest servletRequest)
        throws IOException {
    RequestCloner cloner = createCloner(servletRequest);
    String uri = servletRequest.getRequestURI();
    return cloner.clone(uri, servletRequest);
  }

  private static RequestCloner createCloner(HttpServletRequest servletRequest) {
    RequestType requestType = RequestType.valueOf(servletRequest. getMethod());
    switch (requestType) {
    case GET:
      return new GetRequestCloner();
    case POST:
      return new PostRequestCloner();
    case DELETE:
      return new DeleteRequestCloner();
    default:
      throw new AssertionFailedError(String.format(
          "RequestType '%s' not supported", requestType));
    }
  }

  protected abstract HttpUriRequest clone(
      String uri, HttpServletRequest servletRequest)
      throws IOException;

  protected final void cloneHeaders(
      HttpServletRequest servletRequest,
      HttpUriRequest clonedRequest) { // note addition of parameter
    // same code as before, but modify the passed-in clonedRequest
  }
}

Subclasses of RequestCloner would override clone() , optionally changing the return value to return a subclass of HttpUriRequest :

class PostRequestCloner extends RequestCloner {
  private static final int MAX_STR_LEN = 1024;

  @Override
  protected HttpPost clone(
      String uri, HttpServletRequest servletRequest)
      throws IOException {
    HttpPost clonedRequest = new HttpPost(uri);
    cloneHeaders(servletRequest, clonedRequest);
    cloneBody(servletRequest, clonedRequest);
    return clonedRequest;
  }

  ...
}

The disadvantages of the above solution is the return value of cloneRequest() is the same for a GET request as a POST request.

If you prefer, you can remove the switch by adding code to the enum :

abstract class RequestCloner {

  private enum RequestType {
    GET(new GetRequestCloner()),
    POST(new PostRequestCloner()),
    DELETE(new DeleteRequestCLoner());

    private final RequestCloner requestCloner;

    private RequestType(RequestCloner requestCloner) {
      this.requestCloner = requestCloner();
    }
  }

  public static HttpUriRequest cloneRequest(HttpServletRequest servletRequest)
        throws IOException {
    RequestType requestType = RequestType.valueOf(servletRequest. getMethod());
    String uri = servletRequest.getRequestURI();
    return requestType.requestCloner.clone(uri, servletRequest);
  }

  ...
}

If you want the return value to depend on the type of request, then the caller will need to either specify some kind of type token, explicitly reference the subclass of RequestCloner , or add one static method to RequestCloner for each type of request.

If RequestCloner Needs To Be Generic

Given the code in the question, the only benefit of making RequestCloner generic is to make the return value of clone() different for GET or POST.

To do that, you have two options

  1. Making the subclasses (and their constructors) public.
  2. Replace your newInstance() method with multiple creational methods

Here is an example of option 2:

public static RequestCloner<HttpPost> forPostRequest(String URL) {
  return new PostRequestCloner(URL);
}

There's a way to accomplish what you want, with a slight change: you'd need to pass the right Class object to the factory method:

abstract class RequestCloner<T extends HttpUriRequest> {

    protected T clonedRequest;

    @SuppressWarnings("unchecked")
    static <U extends HttpUriRequest, V extends RequestCloner<U>> V newInstance(
        U request, String url) {

        Class<U> clazz = request.getClass();
        Class<V> clz = null;
        if (HttpGet.class == clazz) {
            clz = GetRequestCloner.class;
        } else if (HttpPost.class == clazz) {
            clz = PostRequestCloner.class;
        } // etc

        try {
            return clz.getDeclaredConstructor(String.class).newInstance(url);
        } catch (Exception e) {
            throw new IllegalArgumentException("Factory error", e);
        }
    }

    // no need for this method to be abstract
    public T cloneRequest(HttpServletRequest servletRequest) throws IOException {
        cloneHeaders(servletRequest);
        return clonedRequest;
    }

    protected void cloneHeaders(HttpServletRequest servletRequest) {
        Enumeration<String> e = servletRequest.getHeaderNames();
        while (e.hasMoreElements()) {
            String header = e.nextElement();
            if (!header.equalsIgnoreCase("Content-Length")
                    && !header.equalsIgnoreCase("Authorization")
                    && !header.equalsIgnoreCase("Host")) {
                clonedRequest.setHeader(new BasicHeader(header, servletRequest.getHeader(header)));
            }
        }
    }
}

Then, GET would be as follows:

class GetRequestCloner extends RequestCloner<HttpGet> {

    GetRequestCloner(String url) {
        this.clonedRequest = new HttpGet(url);
    }

    // no need to override cloneRequest here
}

And POST:

class PostRequestCloner extends RequestCloner<HttpPost> {

    PostRequestCloner(String url) {
        this.clonedRequest = new HttpPost(url);
    }

    @Override
    public HttpPost cloneRequest(HttpServletRequest servletRequest) throws IOException {
        super.cloneRequest(servletRequest);
        cloneBody(servletRequest); // Adding POST's body here
        return clonedRequest;
    }
}

Usage:

GetRequestCloner getCloner = RequestCloner.newInstance(servletRequest, url);
HttpGet get = getCloner.cloneRequest(servletRequest);

PostRequestCloner postCloner = RequestCloner.newInstance(servletRequest, url);
HttpPost post = postCloner.cloneRequest(servletRequest);

Note: I've changed clone() method name to cloneRequest() so that it doesn't clash with Java's Clonable.clone() .

Are you looking for this?

static <V extends HttpUriRequest, K extends RequestCloner<V>> K newInstance((String type, String url) 

Here there is not wildcard used in return type, but it's always not bad idea to return wildcards.

More generally, I think returning something with a bounded return type is perfectly reasonable if you really want the call site to have restricted access to the object.

Please refer this answer for more details.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM