Ограничения через реализацию интерфейса против свободы через рефлекшн

  В последнее время очень часто появляются фреймворки которые позволяют разработчику реализовывать функционал в свободном виде не прибегая к наследованию от интерфейсов. Примеры можно увидеть в таких как EJB 3, JUnit 4, JSF 2, Spring Framework 3 и т.д.
Под катом найдете обсуждение обоих подходов.

  Итак, недавно мне надо было реализовать фреймворк с механизмом динамического (run-time) подключения сервисов которые бы возвращали свойства (мэп ключ-значение). Но проблема заключалась в том что для пользователей данного фреймворка могло быть не удобно использовать только один метод для формирования этого мэпа, возможные сценарии могли быть как передача ядру объект или примитивное значение которое должно было автоматически добавляться в конечный мэп.

  Одно из решений было создать интерфейсы с методами для каждого из сценариев, либо один интерфейс со всеми возможными вариантами методов. Этот подход имеет недостаток — низкая читаемость такого кода и чрезмерная сложность, как и необходимость запоминать множество деталей фреймворка и отсутствие возможности создания одного класса для передачи ядру всей необходимой информации.

  Другое решение это предоставление программисту полную свободу действий, т.е. избавить от необходимости реализовывать какой-либо интерфейс, предоставить возможность именовать методы в свободном стиле и не регламентировать возвращаемый тип. А вызов методов и получение данных происходит через рефлекшн (reflection).

  Итак, приведу пару примеров:
1. Реализация применяя первый подход.
Интерфейсы для реализации:
public interface ServiceAsMap {
  Map<String, Object> getValue();
}

public interface ServiceAsObject {
  Object getValue();
}

И пример реализаций:

public class UserData implements ServiceAsMap {
  public Map<String, Object> getValue() {
    Map<String, Object> result = new HashMap<String, Object>();
    result.put("user.home", "/home/dev");
    result.put("user.name", "dev");
    return result;
  }
}
public class UserDataObj implements ServiceAsObject {
  public Object getValue() {
    return context.getUser();
  }
}


Как видно на примере не очень удобный способ и не интуитивный.
Теперь расмотрим второй пример реализации (напомню что никаких интерфейсов реализовывать не надо):

@Service
public class UserData {

  public User getUser() {
    return context.getUser();
  }

  public Map<String, String> getProperties() {
    Map<String, Object> result = new HashMap<String, Object>();
    result.put("user.home", "/home/dev");
    result.put("user.name", "dev");
    return result;
  }

  @PropertyName("age")
  public int getUserAge() {
    return 25;
  }

}

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

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

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

Спасибо за внимание.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 10

    +1
    несколько мыслей:
    1. Использовать рефлекшн вбизнес коде — зло, но во вреймворке — нормально
    2. Скорость рефлекшна сильно улучшилась в 1.5, 1.6

    Кто будет писать и сопровождать код фреймворка?
    Кто будет писать и сопровождать код аппликации?

    Если фреймворк будет писаться одним сильным программистом и редко модифицироваться — ничего страшного, если код в нём будет сложным и с рефшекшном, главное облегчить жизнь программистам послабее, которые будут писать аппликацию.
      +1
      Вполне с тобой согласен. Но с моей точки зрения даже код фрейворка должен быть написан в таком стиле чтобы его потом могли мейнтейнить другие девелоперы (EJB контейнеры ведь не мейнтейнят только создатели, но в процесс разработки подключаются и другие, главное чтоб не ламеры чтобы не плодить макароны)
        +1
        я тоже согласен!
        Сейчас правлю большой проект (~ 10000 классов). и там рефлекшен. Отлаживать это — просто ад. Ходишь по коду при помощи полнотекстового поиска. и по другому никак.

        добавлю свои мысли:

        1. между интерфейсом и аннотациями должен быть баланс. Взаимодействие двух модулей — это обычно интерфейс. Реже аннотации в классе, чтобы не плодить не нужную иерархию интерфейсов
        2. нельзя, понимаете, нельзя делать на аннотация что-то большое и сложное. Часто люди увлекаются и делают монстра с полу-динамическим кодом. Для больших проектов — это большое зло, там уследить за всем практически сложно, помогает статический анализ, а рефлекшен убивает статику.

          +1
          Это правильно — интерфейс — это контракт. Аннотации — для метаинформации.

          И вообще — чем меньше аннотаций и рефлекшна, тем больше проверок на этапе компиляции
            0
            да контракт. всё правильно.
            0
            в добавление к первому пункту.
            я часто использую аннотации в inner-классах. Тогда вся логика в одном месте и всё понятно.
            Но это не бизнес логика, это вспомогательные фичи на отображение данных
          +1
          Хоть я и из мира .Net, влезу сюда :) В MS последнее время тоже любят второй подход, называют его Convention Over Configuration. Очень активно используется в Entity Framework Code-First. Что тут сказать: с одной стороны круто, не надо ничего конфигурировать, не нужно наследовать свои объекты от каких-то специальных типов.
          Но, нужно помнить все конвенции наизусть и часто понять, почему что-то не работает становится довольно сложно. Правильно упомянули выше, что для более слабого программиста может быть совсем непонятно, что происходит. Магия — это круто, когда ты полностью понимаешь как она работает. А когда она реально выглядит как магия — ничего хорошего.
          Так что мой вывод: стараться всегда прибегать к жесткой типизации, которая имеет очень много пользы. И идти по второму пути, только очень чётко взвесив плюсы и минусы. Дальше, если уж пошли, то не только реализовать фреймворк, но также подробно описать все конвенции, которые он создаёт, покрыть всё это понятными простым смертным тестами и назначить ответственного за поддержку.
            0
            Работать с классами определенными вторым способом можно не только через Reflection.

            Можно генерировать реализацию соответствующего интерфейса в рантайме и разницы в скорости по сравнению с реализацией интерфейса руками вы практически не заметите.

            Кроме того, в Java 7 появилась такая штука как MethodHandle, которая работает пошустрее.
              +1
              Ха! Из таких «авторитетных» статей и рождаются мифы о Java. Поменяем в тесте строчки usingReflection() и usingMethodHandles() местами и… о чудо! теперь Reflection работает гораздо быстрее MethodHandles :)

              // Там в комментариях к статье автору указали на ошибку. А Reflection на деле все-таки быстрее.
                0
                Спасибо =)

            Only users with full accounts can post comments. Log in, please.