В последнее время очень часто появляются фреймворки которые позволяют разработчику реализовывать функционал в свободном виде не прибегая к наследованию от интерфейсов. Примеры можно увидеть в таких как 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;
}
}
Как видно из примера, имена методов описывают производимые действия и в классе сколь угодно методов, что дает возможность реализовать все необходимое в одном месте.
В примере я использовал анотации которые несут дополнительную информацию для ядра.
Единственный недостаток этого подхода, так это использование рефлекшна который не шибко шустрый, и второе код по выполнению и получению данных из подобной структуры достаточно громоздкий следовательно потенциально больше ошибок может быть (если не написать тесты хорошенько).
Дорогие читатели, мне интересно обсудить с вами данный вопрос об областях применения второго подхода и прочих нюансов которые могут случиться, ну и в общем ваше видение подобной ситуации.
Я не пропагантирую применение только свободы через решлекшн, достаточно много примеров когда наследование от интерфейса является единственно верное решение и рефлекшн быдет только мешать.
Спасибо за внимание.
Автор: serjx