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

标签 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;
  }
}

最佳答案

查看您的代码,您的类不需要是通用的。进一步看,调用者传入 URL 以创建克隆程序,然后将 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
  }
}

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

  ...
}

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

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

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

  ...
}

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

如果 RequestCloner 需要通用

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

为此,您有两个选择

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

这是选项 2 的示例:

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

关于java - 静态工厂模式返回类型中的有界通配符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31646828/

相关文章:

java - 如何播放Jar文件中resources文件夹中的MP3文件?

java - 如何指定方法 AnyObject<? super T,字符串>

java - 在java中重用类型参数的值

java - 为清楚起见命名集合扩展

java - Java 和 Scala 中的通配符导入用法

java - 是否有将 HTML 转换为纯文本的函数?

java - 将 JDialog 显示为工作表不起作用

java - OpenAM 代理的配置

bash - bash 如何处理不匹配的通配符?

java - 为什么在 put 方法中使用泛型声明 "<? super ArrayList> does not accept value "new Object()"的 HashMap?