С помощью различных сообщений и комментариев на этой странице было создано решение, которое я считаю правильным для моего сценария.
Ниже показаны итеративные изменения решения для соответствия принципам SOLID.
Требование
Чтобы создать ответ для веб-службы, к объекту ответа добавляются пары ключ + объект. Необходимо добавить множество различных пар ключ + объект, каждая из которых может иметь уникальную обработку, необходимую для преобразования данных из источника в формат, требуемый в ответе.
Из этого ясно, что хотя разные пары ключ/значение могут иметь разные требования к обработке для преобразования исходных данных в целевой объект ответа, все они имеют общую цель добавления объекта к объекту ответа.
Таким образом, в итерации решения 1 был создан следующий интерфейс:
Итерация решения 1
ResponseObjectProvider<T, S> {
void addObject(T targetObject, S sourceObject, String targetKey);
}
Любой разработчик, которому нужно добавить объект в ответ, теперь может сделать это, используя существующую реализацию, соответствующую их требованиям, или добавить новую реализацию с учетом нового сценария.
Это здорово, поскольку у нас есть общий интерфейс, который действует как контракт для этой распространенной практики добавления объектов ответа.
Однако в одном сценарии требуется, чтобы целевой объект был взят из исходного объекта с учетом определенного ключа, «идентификатора».
Здесь есть варианты, первый — добавить реализацию существующего интерфейса следующим образом:
public class GetIdentifierResponseObjectProvider<T extends Map, S extends Map> implements ResponseObjectProvider<T, S> {
public void addObject(final T targetObject, final S sourceObject, final String targetKey) {
targetObject.put(targetKey, sourceObject.get("identifier"));
}
}
Это работает, однако этот сценарий может потребоваться для других ключей исходного объекта ("startDate", "endDate" и т. д.), поэтому эту реализацию следует сделать более общей, чтобы разрешить повторное использование в этом сценарии.
Кроме того, в других реализациях может потребоваться больше контекстной информации для выполнения операции addObject... Поэтому для этого следует добавить новый универсальный тип.
Итерация решения 2
ResponseObjectProvider<T, S, U> {
void addObject(T targetObject, S sourceObject, String targetKey);
void setParams(U params);
U getParams();
}
Этот интерфейс подходит для обоих сценариев использования; реализации, требующие дополнительных параметров для выполнения операции addObject, и реализации, не требующие
Однако, учитывая последний из сценариев использования, реализации, не требующие дополнительных параметров, нарушат принцип разделения интерфейса SOLID, поскольку эти реализации переопределяют методы getParams и setParams, но не реализуют их. например:
public class GetObjectBySourceKeyResponseObjectProvider<T extends Map, S extends Map, U extends String> implements ResponseObjectProvider<T, S, U> {
public void addObject(final T targetObject, final S sourceObject, final String targetKey) {
targetObject.put(targetKey, sourceObject.get(U));
}
public void setParams(U params) {
//unimplemented method
}
U getParams() {
//unimplemented method
}
}
Итерация решения 3
Чтобы исправить проблему разделения интерфейса, методы интерфейса getParams и setParams были перемещены в новый интерфейс:
public interface ParametersProvider<T> {
void setParams(T params);
T getParams();
}
Реализации, которым требуются параметры, теперь могут реализовывать интерфейс ParametersProvider:
public class GetObjectBySourceKeyResponseObjectProvider<T extends Map, S extends Map, U extends String> implements ResponseObjectProvider<T, S>, ParametersProvider<U>
private String params;
public void setParams(U params) {
this.params = params;
}
public U getParams() {
return this.params;
}
public void addObject(final T targetObject, final S sourceObject, final String targetKey) {
targetObject.put(targetKey, sourceObject.get(params));
}
}
Это решает проблему разделения интерфейса, но вызывает еще две проблемы... Если вызывающий клиент хочет запрограммировать интерфейс, то есть:
ResponseObjectProvider responseObjectProvider = new GetObjectBySourceKeyResponseObjectProvider<>();
Тогда для экземпляра будет доступен метод addObject, но НЕ методы getParams и setParams интерфейса ParametersProvider... Для их вызова требуется приведение, и для безопасности также должна быть выполнена проверка instanceof:
if(responseObjectProvider instanceof ParametersProvider) {
((ParametersProvider)responseObjectProvider).setParams("identifier");
}
Это не только нежелательно, но и нарушает принцип подстановки Лискова: «если S является подтипом T, то объекты типа T в программе могут быть заменены объектами типа S без изменения каких-либо желаемых свойств этого объекта. программа"
т. е. если мы заменили реализацию ResponseObjectProvider, которая также реализует ParametersProvider, реализацией, которая не реализует ParametersProvider, то это могло бы изменить некоторые желаемые свойства программы... Кроме того, клиент должен знать, какая реализация находится в данный момент. использовать для вызова правильных методов
Дополнительная проблема заключается в использовании для вызова клиентов. Если бы вызывающий клиент хотел использовать экземпляр, который реализует оба интерфейса для многократного выполнения addObject, метод setParams необходимо было бы вызвать перед addObject... Это могло бы привести к ошибкам, которых можно избежать, если не соблюдать осторожность при вызове.
Итерация решения 4 – окончательное решение
Интерфейсы, созданные на третьей итерации решения, решают все известные в настоящее время требования к использованию с некоторой гибкостью, обеспечиваемой дженериками для реализации с использованием различных типов. Однако это решение нарушает принцип подстановки Лисков и имеет неочевидное использование setParams для вызывающего клиента.
Решение состоит в том, чтобы иметь два отдельных интерфейса, ParameterisedResponseObjectProvider и ResponseObjectProvider.
Это позволяет клиенту программировать интерфейс и выбирать соответствующий интерфейс в зависимости от того, требуют ли объекты, добавляемые в ответ, дополнительные параметры или нет.
Новый интерфейс был впервые реализован как расширение ResponseObjectProvider:
public interface ParameterisedResponseObjectProvider<T,S,U> extends ResponseObjectProvider<T, S> {
void setParams(U params);
U getParams();
}
Однако у этого все еще была проблема с использованием, когда вызывающему клиенту сначала нужно было вызвать setParams перед вызовом addObject, а также сделать код менее читаемым.
Таким образом, окончательное решение имеет два отдельных интерфейса, определенных следующим образом:
public interface ResponseObjectProvider<T, S> {
void addObject(T targetObject, S sourceObject, String targetKey);
}
public interface ParameterisedResponseObjectProvider<T,S,U> {
void addObject(T targetObject, S sourceObject, String targetKey, U params);
}
Это решение устраняет нарушения принципов разделения интерфейса и замены Лисков, а также улучшает использование для вызова клиентов и улучшает читаемость кода.
Это означает, что клиент должен знать о разных интерфейсах, но, поскольку контракты разные, это кажется оправданным решением, особенно если учесть все проблемы, которых удалось избежать.
person
jml
schedule
16.01.2019
public <T extends Interfac1 & Interface2> void doSomething(T t)
- person Lino   schedule 14.01.2019Example example = new Example();
? - person Andy Turner   schedule 14.01.2019Interface1 & Interface2 example = new Example();
. Зацените, на Цейлоне много интересного! - person Lii   schedule 29.01.2019