Debería usar el método case / interactor `execute` para aceptar parámetros

8

Sin embargo, en la mayoría de los ejemplos de arquitectura limpia (proyectos de Android en su mayoría), he notado que las clases de caso de uso / interactor (unidades que encapsulan una característica) a menudo comparten la clase / interfaz base, como aquí o aquí . Otros no lo hacen ( como aquí o aquí ), sino que permiten que los interactores acepten algunos parámetros que luego se utilizan para ejecutar algo de lógica.

¿Alguno de estos enfoques es mejor que el otro? Estoy particularmente interesado en cómo el enfoque sin parámetros maneja los casos de uso para algo que requiere la entrada del usuario, por ejemplo, digamos que queremos permitir que el usuario edite una entidad y la pase al servidor. Podríamos inyectar la misma entidad en el caso de uso y quien lo invoque, pero entonces tendría que ser mutable, de modo que los cambios se reflejen en ambos lugares. Pero entonces no podemos crear modelos inmutables, que quizás no queramos (debido a problemas de subprocesos, etc.). ¿Cómo manejar ese caso?

Disculpe si uso incorrectamente los términos usecase / interactor, pero así es como se usan en Android Land, lo que ciertamente puede estar un poco atrasado en los patrones de diseño

wasilo
fuente

Respuestas:

6

Permíteme reiterar lo que estás diciendo para estar seguro de que estamos en la misma página.

En arquitectura limpia

ingrese la descripción de la imagen aquí

Caso A

las clases de caso de uso / interactor a menudo comparten la clase / interfaz base, como aquí

  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();
    }
  }
}

y aqui

package cat.ppicas.framework.task;

public interface Task<R, E extends Exception> {

    TaskResult<R, E> execute();

}

Caso B

Otros no lo hacen, y en su lugar permiten que los interactores acepten algunos parámetros que luego se utilizan para ejecutar cierta lógica.

como aqui

package pl.charmas.shoppinglist.domain.usecase;

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

o aqui

AbstractInteractor.java
GetMarvelCharactersLimit.java
GetMarvelCharactersLimitImp.java
GetMarvelCharactersPaginated.java
GetMarvelCharactersPaginatedImp.java

Detener

Los dos tienen razón.

Compartir una interfaz o clase base es parte de la herencia.

Aceptar parámetros para poder ejecutar la lógica a través de ellos es parte de la composición.

¿Alguno de estos enfoques es mejor que el otro?

Ambos son mejores en lo que son buenos. Aunque un principio de diseño popular establece, "favorecer la composición sobre la herencia" .

Si entiendo, estás viendo cómo la composición y la herencia pueden permitir el polimorfismo y ahora que lo has visto, estás luchando por elegir entre ellos. Dicho esto en lenguaje de patrones: puede usar el patrón de plantilla o el patrón de estrategia para obtener polimorfismo.

La herencia te da polimorfismo por sí mismo. Por lo tanto, menos escribir en el teclado. La composición requiere que agregue delegación para exponer y conectar la interfaz al parámetro. Eso significa más tipeo. Pero la composición no está estática, por lo que es muy flexible y comprobable.

¿Debería usar el executemétodo caso / interactor aceptar parámetros?

Recuerde que este método x.y()y esta función y(x)son esencialmente los mismos. Un execute() método siempre obtiene al menos un parámetro.

Estoy particularmente interesado en cómo el enfoque sin parámetros maneja los casos de uso para algo que requiere la entrada del usuario, por ejemplo, digamos que queremos permitir que el usuario edite una entidad y la pase al servidor. Podríamos inyectar la misma entidad en el caso de uso y quien lo invoque, pero entonces tendría que ser mutable, de modo que los cambios se reflejen en ambos lugares. Pero entonces no podemos crear modelos inmutables, que quizás no queramos (debido a problemas de subprocesos, etc.). ¿Cómo manejar ese caso?

Bueno, ahora estamos hablando de entidades, no casos de uso, ni polimorfismos.

Una entidad tiene una identificación. Es muy bueno hacer que una entidad sea inmutable. ¿Por qué quieres que un usuario edite algo inmutable? ¿Por qué quieres que su información exista en dos lugares? Si es así, ¿qué lugar sabe mejor?

No, mejor dejar que el usuario construya algo nuevo. Si debe tener una ID, obtiene una nueva ID única. Si no, es un objeto de valor. Si hay algo más con una ID preexistente con la que esta información debe estar asociada, cree una nueva asociación. No andes hurgando en entidades inmutables.

Es completamente posible modelar un mundo cambiante sin actualizar sus entidades, siempre y cuando tenga el espacio para seguir creando cosas que representen las novedades.

naranja confitada
fuente
Sin embargo, todavía tengo algunas dudas. ¿No son ambos enfoques, A y B, ejemplos de herencia? Y tener una clase separada para ejecutar algo de lógica es composición. No veo que A es herencia, y B es composición, ¿podría explicarlo? En cuanto a method is always getting at least one parameter- eso es un tecnicismo. "Si el executemétodo acepta más de un parámetro", si lo desea. Y finalmente, la parte sobre la inmutabilidad tampoco aborda el problema, que es "¿cómo paso el uso de una nueva entidad inmutable (que refleja los cambios del usuario) si su método de ejecución no acepta parámetros?
wasyl