Lassen Sie mich noch einmal wiederholen, was Sie sagen, um sicherzugehen, dass wir auf derselben Seite sind.
In sauberer Architektur
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- execute
Methode 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.
method is always getting at least one parameter
- das ist eine Technik. "Sollte dieexecute
Methode 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?