静态工厂模式返回类型中的有界通配符

car*_*ret 5 java generics wildcard

我在 Effective Java 中读到你不应该使用有界通配符作为返回类型,但我不知道我应该怎么做。我的代码编译的唯一方法是RequestCloner<? extends HttpUriRequest>在静态工厂中使用作为返回类型。我做错了什么还是有解决方法?

注意:要注意的一件事是HttpUriRequest有方法setHeader,但只有HttpPost方法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;
  }
}
Run Code Online (Sandbox Code Playgroud)

Nam*_*ter 1

查看您的代码,您的类不需要是通用的。进一步看,有一个奇怪的问题:调用者传入一个 URL 来创建克隆器,然后将 a HttpServletRequest(理论上可能是不同类型的请求)传递到克隆方法中。

我可以看到两种解决方案,具体取决于您是否真的需要RequestCloner通用。

如果 RequestCloner 不需要是通用的

更改基类如下:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

的子类RequestCloner将覆盖clone(),可选择更改返回值以返回 的子类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;
  }

  ...
}
Run Code Online (Sandbox Code Playgroud)

上述解决方案的缺点是 GET 请求与 POST 请求的返回值cloneRequest()相同。

如果您愿意,可以通过向枚举添加代码来删除开关

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

  ...
}
Run Code Online (Sandbox Code Playgroud)

如果您希望返回值取决于请求的类型,那么调用者将需要指定某种类型标记、显式引用 的子类RequestCloner,或者RequestCloner为每种类型的请求添加一个静态方法。

如果 RequestCloner 需要通用

鉴于问题中的代码,通用的唯一好处RequestCloner是使clone()GET 或 POST 的返回值不同。

为此,您有两种选择

  1. 使子类(及其构造函数)公开。
  2. newInstance()用多种创建方法替换您的方法

以下是选项 2 的示例:

public static RequestCloner<HttpPost> forPostRequest(String URL) {
  return new PostRequestCloner(URL);
}
Run Code Online (Sandbox Code Playgroud)