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

时间:2015-07-27 07:04:37

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

3 个答案:

答案 0 :(得分:1)

查看您的代码,您的类不需要是通用的。进一步看,有一个奇怪的问题,调用者传入一个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()的返回值与作为POST请求的GET请求相同。

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

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的子类,或者向{{添加一个静态方法1}}用于每种类型的请求。

如果RequestCloner需要通用

考虑到问题中的代码,使RequestCloner通用的唯一好处是使RequestCloner的返回值与GET或POST不同。

要做到这一点,你有两个选择

  1. 使子类(及其构造函数)公开。
  2. 用多种创作方法替换您的clone()方法
  3. 以下是选项2的示例:

    newInstance()

答案 1 :(得分:0)

有一种方法可以实现您想要的效果,只需稍加改动:您需要将正确的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))); } } } } 对象传递给工厂方法:

class GetRequestCloner extends RequestCloner<HttpGet> {

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

    // no need to override cloneRequest here
}

然后,GET如下:

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

和POST:

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

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

用法:

clone()

注意:我已将cloneRequest()方法名称更改为Clonable.clone(),以便它不会与Java <html> <link href='dialog.css' rel='stylesheet' type='text/css'/> <body> <div class='dialog' ng-style="{'width': model.width+'px', 'height': model.height+'px', 'top':model.top+'px', 'left':model.left+'px', 'z-index': model.zindex}" ng-mousedown='zorder()'> <span class='topbar'> <button class='minimize' ng-click="minimize()"> _ </button> </span> <div class='content'> <ng-include src=model.template></ng-include> </div> <div class='drag'></div> //the resize div </div> </body> </html> 发生冲突。

答案 2 :(得分:-1)

你在找这个吗?

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

这里没有在返回类型中使用的通配符,但返回通配符总是不错的主意。

  

更一般地说,如果你真的希望调用网站能够限制访问该对象,我认为返回带有有界返回类型的内容是完全合理的。

refer this answer了解详情。