2015-07-27 2 views
4

Я прочитал в «Эффективной 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; 
    } 
} 
+0

Как 'clonedRequest' используется в' RequestCloner'? Из кода не ясно, почему 'RequestCloner' должен быть общим – NamshubWriter

+0

@NamshubWriter. Я добавил остальную часть кода. Я клонирую «HttpServletRequest» в Apache httpclient library, и каждый тип запроса имеет свой собственный класс. – carcaret

ответ

1

Рассматривая свой код, ваш класс не обязательно должен быть общим. Если смотреть дальше, возникает странная проблема, когда вызывающий абонент передает URL-адрес для создания клонера, а затем передает метод HttpServletRequest (который теоретически может быть другим типом запроса).

Я вижу два вида решений, в зависимости от того, действительно ли вам нужен RequestCloner.

Если RequestCloner не нужно быть Generic

Изменение базового класса следующим образом:

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 должен быть Generic

Учитывая код в вопросе, единственное преимущество решений RequestCloner родовых, чтобы сделать возвращаемое значение clone() различной для GET или POST.

Чтобы сделать это, у вас есть два варианта

  1. Создание подклассов (и их конструкторы) общественности.
  2. Замените метод newInstance() с несколькими порождающих методами

Вот пример варианта 2:

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

Но таким образом я должен добавить свойство 'clonedRequest' для каждого класса (' HttpGet', 'HttpPost' ...) вместо того, чтобы иметь только одно свойство в абстрактном классе и наследовать его, вот как я 'd хотел бы сделать это, если это возможно. – carcaret

+0

@ carcaret Вам не нужно делать абзац 'getClonedRequest', если вы хотите добавить поле в базовый класс, затененный подклассами. Это очень небольшое количество дублирования кода (также мое решение позволяет вам создавать поля «final», что полезно делать, когда это возможно). Если это дублирование позволит вам сделать «RequestCloner» неэквивалентным, это стоит того ИМХО. – NamshubWriter

+0

@ carcaret В случае, если вы не заметили, я обновил свой ответ. Похоже, у вас есть несколько хороших ответов на выбор из – NamshubWriter

-1

Вы ищете это?

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

Здесь нет подстановочных знаков, используемых в обратном типе, но это всегда неплохая идея, чтобы вернуть подстановочные знаки.

В целом, я думаю, что возвращение чего-то с ограниченным типом возврата вполне разумно, если вы действительно хотите, чтобы сайт вызова имел ограниченный доступ к объекту.

refer this answer для получения более подробной информации.

+0

Если это было возможно, вызов типа 'RequestCloner. newInstance («POST», url) 'не будет безопасным по типу –

+0

@AndyTurner. Вы правы, но я думаю, что это не вопрос PO, случай, о котором вы говорили, также возможен с приведенным примером PO. –

+0

Он просто не может скомпилировать, хотя с вашим предложением. Каждая из линий, например. 'return new GetRequestCloner (url);' будет сообщать, что этот тип несовместим с 'RequestCloner '. –

0

Там есть способ сделать то, что вы хотите, с небольшим изменением: вам нужно передать правильный Class объекта к заводским способом:

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

Тогда GET будет выглядеть следующим образом:

class GetRequestCloner extends RequestCloner<HttpGet> { 

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

    // no need to override cloneRequest here 
} 

И ПОСТ:

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

Использование:

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

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

Примечание: Я изменил clone() имя метода для cloneRequest() так, чтобы он не конфликтует с в Java Clonable.clone().

+0

Спасибо Федерико. Я нахожу одну проблему с этим подходом. Теперь клиент должен знать, какой класс сопоставляется с каждым типом запроса (если я клонирую GET, мне нужно передать «GetRequestCloner.class'), вместо того, чтобы позволить фабричному методу решить его (используя, например,' servletRequest.getMethod() '). – carcaret

+0

@ carcaret Вы можете легко преодолеть это: вместо сопоставления простой строкой (тип в вашем случае) вы можете получить нужный клонер, получив фактический запрос в качестве аргумента в своем заводском методе. Я обновлю свой ответ, чтобы показать это. –

+0

Спасибо за обновление. Проблема в том, что в 'newInstance' вы ожидаете' U extends HttpUriRequest', но у меня есть простой 'HttpServletRequest', который я хочу клонировать к' HttpUriRequest', поэтому в 'if' statement I до сих пор не будет сопоставлять «HttpGet.class». – carcaret

Смежные вопросы