Чистий Архітектура пропонує дозволити випадок використання Interactor назвати фактичну реалізацію провідних (який вводиться, після DIP) для обробки відповіді / дисплея. Однак я бачу людей, що реалізують цю архітектуру, повертають вихідні дані з інтерактора, а потім дозволяють контролеру (у адаптерному шарі) вирішувати, як з ним поводитися. Чи є друге рішення, що витікає з додаткового рівня обов'язки програми, окрім чіткого визначення портів вводу та виведення в інтерактор?
Порти введення та виведення
Враховуючи визначення «Чиста архітектура» , і особливо маленьку схему потоку, що описує взаємозв’язки між контролером, інтерактором випадку використання та презентатором, я не впевнений, чи правильно я розумію, яким повинен бути «Порт використання вихідного випадку».
Чиста архітектура, як і гексагональна архітектура, розрізняє первинні порти (методи) та вторинні порти (інтерфейси, які слід реалізувати адаптерами). Після комунікаційного потоку я очікую, що "Порт введення випадку використання" є основним портом (таким чином, просто методом), а "Використовувати вихідний порт виходу", який буде реалізований інтерфейс, можливо, аргумент конструктора з фактичним адаптером, щоб інтерактор міг ним користуватися.
Приклад коду
Щоб зробити приклад коду, це може бути код контролера:
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, який перевіряє розташування штифта, при необхідності перевіряє його, створює сутність місця для запису PIN-коду, будує 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
Це привабливо, оскільки ми перекладаємо відповідальність за "виклик" презентації із випадку використання, тому випадок використання вже не стосується того, щоб знати, що робити з даними, а не просто наданням даних. Також у цьому випадку ми все ще не порушуємо правило залежності, оскільки випадок використання все ще нічого не знає про зовнішній шар.
Однак випадок використання не контролює момент, коли фактична презентація вже виконується (що може бути корисним, наприклад, робити додаткові речі в цей момент, як-от реєстрація, або при необхідності взагалі відміняти його). Також зауважте, що ми втратили Use Case Input Port, оскільки зараз контролер використовує лише getData()
метод (який є нашим новим вихідним портом). Крім того, мені здається, що тут ми порушуємо принцип "скажи, не запитуй", тому що ми просимо інтерактора для того, щоб зробити щось із цим, а не казати йому робити фактичну справу в першість.
До суті
Отже, чи є одна з цих двох альтернатив «правильною» інтерпретацією вихідного порту Use Case відповідно до чистої архітектури? Вони обидва життєздатні?