Это не распространенная проблема. Проблема, которую вы должны решить - это ожидание вашего интерфейса. Вы комбинируете поведение интерфейса без побочных эффектов с интерфейсом, который позволяет побочные эффекты.
Учитывайте следующее:
public class CommandMonitor {
public static void main(String[] args) {
Command<?> sec = new SideEffectCommand();
reportResults(sec);
}
public static void reportResults(Command<?> cmd){
final Object results = cmd.execute("arg");
if (results != null ) {
System.out.println(results.getClass());
}
}
}
Нет ничего плохого в использовании <Void>
в качествеТип шаблона, но позволяющий смешивать его с реализациями для «Command <T>
», означает, что некоторые клиенты интерфейса могут не ожидать пустого результата. Не изменяя интерфейс, вы позволили реализации создать неожиданный результат.
Когда мы передаем наборы данных с использованием классов Collection, моя команда согласилась, что never вернет null, хотя синтаксически это нормально. Проблема заключалась в том, что классам, которые использовали возвращаемые значения, постоянно приходилось проверять наличие пустот, чтобы предотвратить NPE. Используя приведенный выше код, вы увидите это повсюду:
if (results != null ){
Потому что теперь есть способ узнать, есть ли у реализации объект или нет. Для конкретного случая вы наверняка знаете, потому что вы знакомы с реализацией. Но как только вы начнете собирать их или они выходят за пределы вашего горизонта кодирования (используется в качестве библиотеки, будущего обслуживания и т. Д.), Проблема с нулевым значением возникнет сама собой.
Далее я попробовал это:
public class SideEffectCommand implements Command<String> {
@Override
public String execute(String... args) {
return "Side Effect";
}
}
public class NoSideEffectCommand implements Command<Void>{
@Override
public Void execute(String... args) {
return null;
}
}
public class CommandMonitor {
public static void main(String[] args) {
Command<?> sec = new SideEffectCommand();
Command<?> nsec = new NoSideEffectCommand();
reportResults(sec.execute("args"));
reportResults(nsec.execute("args")); //Problem Child
}
public static void reportResults(Object results){
System.out.println(results.getClass());
}
public static void reportResults(Void results){
System.out.println("Got nothing for you.");
}
}
Перегрузка не сработала, потому что второй вызов reportResults по-прежнему вызывал версию, ожидающую объект (конечно). Я предполагал изменить его на
public static void reportResults(String results){
Но это проиллюстрировало корень проблемы: ваш клиентский код начинает знать детали реализации. Предполагается, что интерфейсы помогают изолировать зависимости кода, когда это возможно. Добавление их в данный момент кажется плохим дизайном.
Суть в том, что вам нужно использовать дизайн, который дает понять, когда вы ожидаете, что команда будет иметь побочный эффект, и продумать, как вы будете обрабатывать наборкоманд, то есть массив неизвестных команд.
Это может быть случай неплотных абстракций .