Вызов методов рефлексивно или использование фиксированных методов с наследованием? - PullRequest
8 голосов
/ 25 июня 2010

Я работаю над крошечной веб-библиотекой и задаюсь вопросом, нужно ли мне вызывать методы обработчика HTTP для GET, POST, PUT и т. Д. Рефлекторно или нет.

Фиксированные методы

Сначалавариант с if else ... блочными вызывающими методами, данными в базовом классе, где у них есть реализация по умолчанию, возвращающая ошибку клиенту.Поскольку для запроса к неподдерживаемому методу требуется заголовок с разрешенными методами, мне нужно подумать о том, какие методы в действительности переопределяются (как, впрочем, и API Servlet).

public abstract class Resource {

    public Response handle(HttpServletRequest request) {
        String action = request.getMethod();
        if(action.equals("GET"))
            return get(request);
        else if(action.equals("POST"))
            return post(request);
        ...
    }

    protected Response get(HttpServletRequest request) {
        return new Response(METHOD_NOT_ALLOWED);
    }

    protected Response post(HttpServletRequest request) {
        return new Response(METHOD_NOT_ALLOWED);
    }

}

Недостатком этого решения является уменьшенная гибкость, поскольку используемые методы фиксируются в базовом классе до тех пор, пока методы handle не будут переопределены в подклассе.

Переменные методы

Другой вариант заключается в рефлексивном поиске методов-обработчиков HTTP в зависимости от их сигнатуры (возьмите HttpServletRequest и верните Response).Эти методы будут храниться в Map и вызываться рефлексивно в зависимости от ключа на карте.

public abstract class Resource {

    private Map<String, Method> handlers;

    public Resource() {
        handlers = findHttpHandlerMethodsReflectivly();
    }

    public Response handle(HttpServletRequest request) {
        String action = request.getMethod();
        Method handler = handlers.get(action);
        return (Response)handler.invoke(this, request);
    }

}

Преимущество этого решения заключается в простой реализации и гибкости, но недостатки, возможно, немного больше времени выполненияиз-за поиска на карте и вызова рефлексивного метода.И интерфейс класса несколько «мягкий» (или динамический), и у компилятора нет шансов проверить это.Но я не уверен, что это будет недостатком, поскольку никакие другие классы не должны полагаться на методы-обработчики HTTP, они являются внешним веб-интерфейсом и границей системы Java.

Шаблон стратегии

Третий вариант и самый чистый ООП - это модель стратегии, рекомендованная «полигеномасляными материалами».Это будет выглядеть так:

class MyResource extends Resource {

    register("GET", 
        new RequestHandler{
            @Override Response handle(HttpServletRequest request) {
                new Response(OK);
            }
        }
    );

}

Это чистый ООП, но код действительно уродливый и многословный.Я бы предпочел Scala с замыканиями здесь, хотя инструментальная поддержка Scala все еще слабая.Сравните это с решением с наследованием и фиксированными методами:

class MyResource extends Resource {

    @Override Response get(HttpServletRequest request) {
        return new Resonse(OK);
    }

}

Что бы вы предпочли и почему?Другие идеи?

Решение

Я узнал, что рефлексия здесь не нужна из-за фиксированного набора методов HTTP.Подход с шаблоном стратегии является чистым, но он кажется мне многословным.Поэтому я решил пойти с фиксированными методами и наследованием.

Ответы [ 3 ]

9 голосов
/ 25 июня 2010

Об использовании интерфейсов вместо отражения

В этом случае не следует использовать отражение, тем более что начинать с него не обязательно (см. Effective Java 2nd Edition, Item 53: Предпочтение интерфейсов для отражения ).

Вместо того, чтобы использовать java.lang.reflect.Method и иметь Map<String, Method> handlers, вы должны определить interface RequestHandler тип и вместо этого иметь Map<String, RequestHandler> handlers.

Это будет выглядеть примерно так:

interface RequestHandler {
   Response handle(HttpServletRequest req);
}

Затем вместо рефлексивного поиска обработчиков заполните карту явным put (или, возможно, используйте файл конфигурации и т. Д.). Тогда вместо рефлексивного Method.invoke вы можете более просто позвонить RequestHandler.handle.


Вкл. enum опция ключей

Если у вас есть только несколько различных типов методов запроса без плана расширения, вы можете иметь enum RequestMethod { GET, POST; }.

Это позволяет вам объявить Map<RequestMethod, RequestHandler> handlers;. Помните, что enum имеет метод valueOf(String), который вы можете использовать для получения константы из имени.


При использовании interface против abstract class

Здесь я снова буду полагаться на суждение Джоша Блоха из Effective Java 2nd, Item 18: Предпочтение интерфейсов абстрактным классам :

Подводя итог, можно сказать, что interface, как правило, является наилучшим способом определения типа, который допускает множественную реализацию. Исключением из этого правила является случай, когда простота эволюции считается более важной, чем гибкость и сила. При таких обстоятельствах вы должны использовать abstract class для определения типа, но только если вы понимаете и можете принять ограничения.

Проблемы, с которыми вы боретесь, были подробно рассмотрены в книге. В этом конкретном случае может быть случаем использования abstract class (то есть подхода "фиксированного метода") из-за нескольких и фиксированных типов методов запроса.

7 голосов
/ 25 июня 2010

Я бы предпочел не использовать рефлексию здесь, так как возможные методы HTTP хорошо известны и не собираются изменяться в ближайшее время.Таким образом, вы ничего не получаете за дополнительную сложность и отсутствие проверок во время выполнения.Вместо if...elseif s вы можете использовать карту, чтобы сделать ваш код чище и легко расширяемым.

В настоящее время ваш рефлексивный код вылетает, если вызывается с именем метода, подобным NOSUCHMETHOD.

1 голос
/ 25 июня 2010

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

Уровень 1: нет расширяемости

Все известно заранее, вы можете закодировать его так, чтобы сделать его максимально простым и понятным.

Уровень 2: капсулированная расширяемость с крючками

Компонент хорошо инкапсулирован, но должна быть возможность адаптировать его поведение или настроить его. Компонент предоставляет затем ловушку, где вы можете подключить вещи, чтобы изменить его поведение. Крючок может принимать множество различных форм. Это, например, случай, когда компонент предоставляет способ подключить к нему стратегию, или если компонент с <verb, actionHandler> использовался компонентом внутри, но карта заполняется снаружи другим компонентом и т. Д.

Уровень 3: расширяемость во время выполнения

Сложности неизвестны, код загружается динамически, система «сценариев» с пользовательскими правилами, выраженными в DSL, и т. Д. Эта степень гибкости требует использования отражения, поскольку часть кода должна вызываться динамический путь.

В вашем случае список HTTP-глаголов является фиксированным и внутренним для ресурса. Затем я выбрал бы самый простой и использованный подход 1. Все остальное мне кажется чрезмерным, и я не возражаю против небольшого списка if-else, когда он оправдан.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...