Чистая Архитектура предлагает позволить случай использования Interactor назвать фактическую реализацию ведущих (который вводится, после DIP) для обработки ответа / дисплея. Тем не менее, я вижу людей, реализующих эту архитектуру, возвращающих выходные данные из интерактора, а затем позволяющих контроллеру (на уровне адаптера) решать, как с ним работать. Является ли второе решение утечкой ответственности приложения из уровня приложения, в дополнение к нечеткому определению входных и выходных портов для интерактора?
Порты ввода и вывода
Принимая во внимание определение чистой архитектуры и особенно небольшую блок-схему, описывающую отношения между контроллером, интерактивом варианта использования и докладчиком, я не уверен, правильно ли я понимаю, каким должен быть «Порт вывода варианта использования».
Чистая архитектура, как и гексагональная архитектура, различает первичные порты (методы) и вторичные порты (интерфейсы, реализуемые адаптерами). Следуя коммуникационному потоку, я ожидаю, что «Use Case Input Port» будет основным портом (таким образом, просто методом), а «Use Case Output Port» интерфейсом, который будет реализован, возможно, аргумент конструктора, принимающий фактический адаптер, так что интерактор может его использовать.
Пример кода
Чтобы сделать пример кода, это может быть код контроллера:
Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();
Интерфейс докладчика:
// Use Case Output Port
interface Presenter
{
public void present(Data data);
}
Наконец, сам интерактор:
class UseCase
{
private Repository repository;
private Presenter presenter;
public UseCase(Repository repository, Presenter presenter)
{
this.repository = repository;
this.presenter = presenter;
}
// Use Case Input Port
public void doSomething()
{
Data data = this.repository.getData();
this.presenter.present(data);
}
}
На интеракторе звонит ведущий
Предыдущая интерпретация, кажется, подтверждается самой вышеупомянутой диаграммой, где отношение между контроллером и входным портом представлено сплошной стрелкой с «острой» головкой (UML для «ассоциации», что означает «имеет», где Контроллер "имеет" вариант использования), в то время как связь между презентатором и выходным портом представлена сплошной стрелкой с "белой" головкой (UML для "наследования", что не является "реализацией", но, вероятно, это смысл в любом случае).
Кроме того, в этом ответе на другой вопрос Роберт Мартин точно описывает случай использования, когда интерактор вызывает докладчика по запросу чтения:
Нажатие на карту приводит к тому, что вызывается либо placePinController. Он собирает местоположение щелчка и любые другие контекстные данные, создает структуру данных placePinRequest и передает ее в PlacePinInteractor, который проверяет местоположение булавки, проверяет его при необходимости, создает объект Place для записи булавки, создает EditPlaceReponse объект и передает его в EditPlacePresenter, который вызывает экран редактора места.
Чтобы это хорошо работало с MVC, я мог бы подумать, что логика приложения, которая традиционно идет в контроллер, здесь перенесена в интерактор, потому что мы не хотим, чтобы какая-либо логика приложения просачивалась за пределы уровня приложения. Контроллер на уровне адаптеров просто вызовет интерактор и, возможно, в процессе выполнит небольшое преобразование формата данных:
Программное обеспечение в этом слое представляет собой набор адаптеров, которые преобразуют данные из формата, наиболее удобного для вариантов использования и сущностей, в формат, наиболее удобный для некоторого внешнего агентства, такого как База данных или Интернет.
из оригинальной статьи, говоря об интерфейсных адаптерах.
На интеракторе, возвращающем данные
Однако моя проблема с этим подходом состоит в том, что сценарий использования должен заботиться о самой презентации. Теперь я вижу, что цель Presenter
интерфейса состоит в том, чтобы быть достаточно абстрактным, чтобы представлять несколько различных типов презентаторов (GUI, Web, CLI и т. Д.), И что он на самом деле просто означает «вывод», что может быть примером использования очень хорошо, но все же я не совсем уверен в этом.
Сейчас, просматривая в Интернете приложения чистой архитектуры, я, похоже, обнаружил только людей, которые интерпретируют выходной порт как метод, возвращающий некоторое DTO. Это было бы что-то вроде:
Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious
Это привлекательно, потому что мы снимаем с себя ответственность «вызова» презентации из варианта использования, поэтому сценарий использования не заботится о том, чтобы больше знать, что делать с данными, а просто предоставляет данные. Кроме того, в этом случае мы все еще не нарушаем правило зависимости, потому что вариант использования все еще ничего не знает о внешнем слое.
Тем не менее, вариант использования не контролирует момент, когда фактическая презентация выполняется больше (что может быть полезно, например, для выполнения дополнительных действий в этот момент, таких как ведение журнала, или для полного отмены при необходимости). Кроме того, обратите внимание, что мы потеряли порт ввода варианта использования, потому что теперь контроллер использует только getData()
метод (который является нашим новым портом вывода). Кроме того, мне кажется, что мы здесь нарушаем принцип «говори, не спрашивай», потому что мы просим у интерактора какие-то данные, чтобы что-то с ним сделать, вместо того, чтобы сказать, чтобы они действовали в первое место.
К точке
Итак, является ли какая-либо из этих двух альтернатив «правильной» интерпретацией порта вывода варианта использования в соответствии с чистой архитектурой? Они оба жизнеспособны?