干净的架构、用例和实体

16

好的,所以我刚开始一个新的Android项目,并想尝试实现Uncle Bob的Clean Architecture。我已经使用RxJava、GitHub示例和样板以及Fernando Cerjas的博客(比如这篇文章)有了良好的开端,但仍然有一些关于如何实现某些UseCases的问题。


简短概述

一个实体是否应该具有另一个实体的字段(例如,在我的例子中,User具有一个List<Messages>字段)?

或者,Presenter是否应该组合UseCases以构建映射到多个实体的ViewModel(那么,你如何编写映射器代码)?

还是Presenter应该将每个UseCase/Entity与一个ViewModel相关联,并创建某种"等待所有数据onNext"来为每个ViewModel调用view.show()?


基本上,UseCases只应该返回Entities吗?一个实体可以由其他实体组成(如类的字段)吗?Entities只是无脑的数据模型POJO吗?如何表示“join SQL”查询?

以一个简单的用户/消息应用为例。

  • UserList 显示一个Users列表。
  • UserDetails 显示用户的信息以及其最新的消息。

UserList非常简单,我可以看到如何编写相关的UseCase和层(下面的代码)。

我的问题在于UserDetails屏幕上。

如果我希望所有数据同时传递给视图(例如构建由User类组成的ViewModel,带有一个List字段),应该如何编写GetUserInfoUseCaseGetUserInfoUseCase的返回值应该是什么?应该编写Observable<User> GetUserInfoUseCaseObservable<List<Message>> GetUserLatestMessages然后在Presenter中合并它们吗?如果是,我该如何管理这个,因为我的Presenter中没有Observables(我只传递Observer作为我的UseCases参数)?

User Entity

public abstract class User {
    public abstract long id();
    public abstract String name();
 ...
}

消息实体

public abstract class Message {
    public abstract long id();
    public abstract long senderId();
    public abstract String text();
    public abstract long timstamp();
 ...
}

获取用户用例

public class GetUsersUseCase extends UseCaseObservableWithParameter<Boolean, List<User>, UsersRepository> {

@Inject
public GetUsersUseCase(UsersRepository UsersRepository,
                              @Named("Thread") Scheduler threadScheduler,
                              @Named("PostExecution") Scheduler postExecutionScheduler) {
    super(usersRepository, threadScheduler, postExecutionScheduler);
}

@Override
protected Observable<List<User>> buildObservable(Boolean forceRefresh) {

    if(forceRefresh)
        repository.invalidateCache();

    return repository.getUsers();
}
}

用户展示器

public class UsersPresenter extends BasePresenter<UsersContract.View> implements UsersContract.Presenter {

    @Inject
    GetUsersUseCase mGetUsersUseCase;

    @Inject
    UserViewModelMapper mUserMapper;

    @Inject
    public UsersPresenter() {
    }

    @Override
    public void attachView(UsersContract.View mvpView) {
        super.attachView(mvpView);
    }

    @Override
    public void detachView() {
        super.detachView();

        mGetUsersUseCase.unsubscribe();
    }

    @Override
    public void fetchUsers(boolean forceRefresh) {
        getMvpView().showProgress();

        mGetUsersUseCase.execute(forceRefresh, new DisposableObserver<List<User>>() {
            @Override
            public void onNext(List<User> users) {
                getMvpView().hideProgress();
                getMvpView().showUsers(mUsersMapper.mapUsersToViewModels(users));
            }

            @Override
            public void onComplete() {

            }

            @Override
            public void onError(Throwable e) {
                getMvpView().hideProgress();
                getMvpView().showErrorMessage(e.getMessage());
            }
        });
    }
}

使用带参数的UseCaseObservable

public abstract class UseCaseObservableWithParameter<REQUEST_DATA, RESPONSE_DATA, REPOSITORY> extends UseCase<Observable, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {

    public UseCaseObservableWithParameter(REPOSITORY repository, Scheduler threadScheduler, Scheduler postExecutionScheduler) {
        super(repository, threadScheduler, postExecutionScheduler);
    }

    protected abstract Observable<RESPONSE_DATA> buildObservable(REQUEST_DATA requestData);

    public void execute(REQUEST_DATA requestData, DisposableObserver<RESPONSE_DATA> useCaseSubscriber) {
        this.disposable.add(
                this.buildObservable(requestData)
                        .subscribeOn(threadScheduler)
                        .observeOn(postExecutionScheduler)
                        .subscribeWith(useCaseSubscriber)
        );
    }
}

使用案例

public abstract class UseCase<OBSERVABLE, REQUEST_DATA, RESPONSE_DATA, REPOSITORY> {

    protected final REPOSITORY repository;

    protected final Scheduler threadScheduler;

    protected final Scheduler postExecutionScheduler;

    protected CompositeDisposable disposable = new CompositeDisposable();

    public UseCase(REPOSITORY repository,
                   @Named("Thread") Scheduler threadScheduler,
                   @Named("PostExecution") Scheduler postExecutionScheduler) {
        Timber.d("UseCase CTOR");
        this.repository = repository;
        this.threadScheduler = threadScheduler;
        this.postExecutionScheduler = postExecutionScheduler;
    }

    protected abstract OBSERVABLE buildObservable(REQUEST_DATA requestData);

    public boolean isUnsubscribed() {
        return disposable.size() == 0;
    }

    public void unsubscribe() {
        if (!isUnsubscribed()) {
            disposable.clear();
        }
    }
}
2个回答

29

这是一个涵盖多个问题的问题。让我尝试整合一下,我认为你的主要问题如下:

  • 实体之间可以相互引用吗?答案是:可以。在Clean Architecture中,您可以创建一个域模型,其中实体相互关联。

  • UseCase 应该返回什么?答案:UseCases 定义输入DTO(数据传输对象)和输出DTO,这些DTO最方便用于该用例。在他的书中,Bob叔叔写道,实体不应从用例传递或从用例返回。

  • 那Presenter的作用是什么?答案:理想情况下,演示者只转换数据。它将最方便一层的数据转换为最方便另一层的数据。

希望这些指导能帮助您回答详细问题。更多细节和示例,请参见我的最近帖子:https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/


3
谢谢你的回答。关于用例,我发现几乎每个干净架构实现的例子都会返回实体,然后演示器将实体映射到视图模型。这是为了避免在实体和其他部分之间再添加另一个DTO对象吗? - w00ly
Bob大叔在他的书的某一章节中写道:“我们不想欺骗并通过实体对象或数据库行传递它们。”如果用例Interactor返回的DTO非常类似于实体,则可能是用例逻辑太少。 - plainionist

1
基本上,您希望尽可能地推进您的“工具”感知代码(在圆圈上)。
用例非常接近模型,并包含大量业务逻辑-您希望该层非常干净,以便能够进行快速且易于单元测试。因此,该层不应了解存储的任何信息。
但是有趣的部分是当 Room 进入房间时 :) Room 使得拥有类似模型的对象变得非常容易,您可以在周围使用它们,而且在我看来,是否应该使用 Room 注释的类作为您的模型是一个灰色领域。
如果您将 Room 对象视为数据层对象,则应在到达用例之前将其映射到业务对象。 如果您将 Room 用作 DAO 到模型对象的内置映射器,那么在我看来,您可以在用例中使用它们,尽管清晰的纯粹主义者可能不同意这一点。
我的实用建议是-如果您的模型具有由多个实体构建的复杂结构,则为其专门设置一个模型类,并将实体映射到该类。 如果您有像地址这样的东西,那么只需使用 Room 实体即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接