Sollte case / Interaktor `execute` Methode verwenden, akzeptieren Sie Parameter

8

In den meisten Beispielen für eine saubere Architektur (meistens jedoch in Android-Projekten) ist mir aufgefallen, dass die Use-Case- / Interaktor-Klassen (Einheiten, die eine Funktion kapseln) häufig die Basisklasse / -schnittstelle gemeinsam nutzen, wie hier oder hier . Andere tun dies nicht ( wie hier oder hier ), sondern erlauben Interaktoren, einige Parameter zu akzeptieren, die dann zum Ausführen einer Logik verwendet werden.

Ist einer dieser Ansätze besser als der andere? Ich bin besonders daran interessiert, wie der parameterlose Ansatz Anwendungsfälle für etwas behandelt, für das beispielsweise Benutzereingaben erforderlich sind. Angenommen, wir möchten, dass Benutzer eine Entität bearbeiten und an den Server übergeben. Wir könnten dieselbe Entität in den Anwendungsfall einfügen und wen auch immer sie aufruft, aber dann müsste sie veränderbar sein, damit Änderungen an beiden Stellen widergespiegelt werden. Aber dann können wir keine unveränderlichen Modelle erstellen, die wir möglicherweise nicht möchten (aufgrund von Threading-Problemen usw.). Wie gehe ich mit einem solchen Fall um?

Verzeihen Sie mir, wenn ich die Begriffe "usecase / interaktor" falsch verwende, aber so werden sie im Android-Land verwendet, was bei Designmustern zugegebenermaßen etwas hinterherhinkt

Wasyl
quelle

Antworten:

6

Lassen Sie mich noch einmal wiederholen, was Sie sagen, um sicherzugehen, dass wir auf derselben Seite sind.

In sauberer Architektur

Geben Sie hier die Bildbeschreibung ein

Fall A.

Anwendungsfall- / Interaktorklassen teilen sich häufig die Basisklasse / Schnittstelle, wie hier

  public abstract class UseCase {

  private final ThreadExecutor threadExecutor;
  private final PostExecutionThread postExecutionThread;

  private Subscription subscription = Subscriptions.empty();

  protected UseCase(ThreadExecutor threadExecutor,
      PostExecutionThread postExecutionThread) {
    this.threadExecutor = threadExecutor;
    this.postExecutionThread = postExecutionThread;
  }

  /**
   * Builds an {@link rx.Observable} which will be used when executing the current {@link UseCase}.
   */
  protected abstract Observable buildUseCaseObservable();

  /**
   * Executes the current use case.
   *
   * @param UseCaseSubscriber The guy who will be listen to the observable build
   * with {@link #buildUseCaseObservable()}.
   */
  @SuppressWarnings("unchecked")
  public void execute(Subscriber UseCaseSubscriber) {
    this.subscription = this.buildUseCaseObservable()
        .subscribeOn(Schedulers.from(threadExecutor))
        .observeOn(postExecutionThread.getScheduler())
        .subscribe(UseCaseSubscriber);
  }

  /**
   * Unsubscribes from current {@link rx.Subscription}.
   */
  public void unsubscribe() {
    if (!subscription.isUnsubscribed()) {
      subscription.unsubscribe();
    }
  }
}

und hier

package cat.ppicas.framework.task;

public interface Task<R, E extends Exception> {

    TaskResult<R, E> execute();

}

Fall B.

Andere tun dies nicht und erlauben Interaktoren stattdessen, einige Parameter zu akzeptieren, die dann zum Ausführen einer Logik verwendet werden.

wie hier

package pl.charmas.shoppinglist.domain.usecase;

public interface UseCase<Result, Argument> {
  Result execute(Argument arg) throws Exception;
}

oder hier

AbstractInteractor.java
GetMarvelCharactersLimit.java
GetMarvelCharactersLimitImp.java
GetMarvelCharactersPaginned.java
GetMarvelCharactersPaginedImp.java

Halt

Sie haben beide recht.

Die Freigabe einer Schnittstelle oder Basisklasse ist Teil der Vererbung.

Das Akzeptieren von Parametern, damit Sie Logik durch sie ausführen können, ist Teil der Komposition.

Ist einer dieser Ansätze besser als der andere?

Beide sind besser darin, was sie gut können. Obwohl ein populäres Designprinzip besagt, "bevorzugen Sie die Komposition gegenüber der Vererbung" .

Wenn ich das verstehe, sehen Sie, wie Komposition und Vererbung Polymorphismus ermöglichen können, und jetzt, da Sie es gesehen haben, haben Sie Schwierigkeiten, zwischen ihnen zu wählen. Sagen Sie dies in Mustersprache: Sie können das Vorlagenmuster oder das Strategiemuster verwenden, um Polymorphismus zu erhalten.

Vererbung gibt Ihnen Polymorphismus für sich. Somit weniger Tippen auf der Tastatur. Für die Komposition müssen Sie eine Delegierung hinzufügen, um die Schnittstelle verfügbar zu machen und mit dem Parameter zu verbinden. Das bedeutet mehr Tippen. Die Komposition ist jedoch nicht statisch gebunden, daher ist sie sehr flexibel und überprüfbar.

Sollte die Use-Case / Interaktor- executeMethode Parameter akzeptieren?

Denken Sie daran, dass diese Methode x.y()und diese Funktion y(x)im Wesentlichen identisch sind. Eine execute() Methode erhält immer mindestens einen Parameter.

Ich bin besonders daran interessiert, wie der parameterlose Ansatz Anwendungsfälle für etwas behandelt, für das beispielsweise Benutzereingaben erforderlich sind. Angenommen, wir möchten, dass Benutzer eine Entität bearbeiten und an den Server übergeben. Wir könnten dieselbe Entität in den Anwendungsfall einfügen und wen auch immer sie aufruft, aber dann müsste sie veränderbar sein, damit Änderungen an beiden Stellen widergespiegelt werden. Aber dann können wir keine unveränderlichen Modelle erstellen, die wir möglicherweise nicht möchten (aufgrund von Threading-Problemen usw.). Wie gehe ich mit einem solchen Fall um?

Nun, jetzt sprechen wir über Entitäten, keine Anwendungsfälle oder Polymorphismus.

Eine Entität hat eine ID. Es ist sehr gut, eine Einheit unveränderlich zu machen. Warum soll ein Benutzer etwas Unveränderliches bearbeiten? Warum möchten Sie, dass die Informationen an zwei Stellen vorhanden sind? Wenn ja, welcher Ort weiß es am besten?

Nein, es ist besser, den Benutzer etwas Neues bauen zu lassen. Wenn es eine ID haben muss, erhält es eine neue eindeutige ID. Wenn nicht, ist es ein Wertobjekt. Wenn es noch etwas mit einer bereits vorhandenen ID gibt, mit der diese Informationen verknüpft werden müssen, erstellen Sie eine neue Zuordnung. Stöbern Sie nicht in unveränderlichen Wesenheiten herum.

Es ist durchaus möglich, eine sich verändernde Welt zu modellieren, ohne Ihre Entitäten jemals zu aktualisieren, solange Sie den Raum haben, weiterhin Dinge zu erstellen, die das Neue darstellen.

candied_orange
quelle
Ich habe jedoch immer noch einige Zweifel. Sind nicht beide Ansätze, A und B, Beispiele für Vererbung? Und eine separate Klasse zum Ausführen einer Logik zu haben, ist Komposition. Ich sehe nicht, dass A Vererbung und B Komposition ist - könnten Sie das näher erläutern? Was method is always getting at least one parameter- das ist eine Technik. "Sollte die executeMethode mehr als einen Parameter akzeptieren", wenn Sie dies wünschen. Und schließlich geht der Teil über die Unveränderlichkeit auch nicht auf das Problem ein: "Wie übergebe ich die Verwendung einer neuen unveränderlichen Entität (die die Änderungen des Benutzers widerspiegelt), wenn ihre Ausführungsmethode keine Parameter akzeptiert?
wasyl