清洁架构,使用案例和实体

时间:2018-01-05 18:07:41

标签: android architecture clean-architecture

好的,所以我刚开始一个新的Android项目,并想尝试通过Uncle Bob实现Clean Architecture。我有一个很好的开始使用RxJava和来自GitHub样本和boilerplates和Fernando Cerjas的博客(如this article),但仍然有一些关于如何实现一些UseCases的问题。

TL; DR

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

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

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

基本上,UseCases应该只返回实体吗?实体是否可以由其他实体组成(如在该类的字段中)?实体只是愚蠢的数据模型POJO吗?如何表示“加入SQL”查询?

举个例子,我们来看一个简单的用户/消息应用。 我想实现两个视图:UserListUserDetails

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

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

我的问题出在UserDetails屏幕上。

如果我希望同时在视图中传递所有数据(比如构建一个由User类组成的ViewModel,带有字段列表),我应该如何对GetUserInfoUseCase进行编码? GetUserInfoUseCase的返回值应该是多少? 我应该编码Observable<User> GetUserInfoUseCaseObservable<List<Message>> GetUserLatestMessages并在演示者中以某种方式合并它们吗?如果是的话,我怎么能管理这个,因为我的Presenter中没有Observables(我只传递一个Observer作为我的UseCases参数)?

用户实体

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

GetUsersUseCase

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

UsersPresenter

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

UseCaseObservableWithParameter

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 个答案:

答案 0 :(得分:4)

在一个问题中有很多问题。让我试着巩固我认为我理解的关键问题

  • 实体可以互相引用吗?答案是:是的。也在 清洁架构您可以创建实体互连的域模型

  • 应该从UseCase返回什么内容? 答案:UseCases定义输入DTO(数据传输对象)和输出最适合用例的DTO。在他的书中,叔叔鲍勃写道,不应将实体传递给用例或从用例中返回

  • 演示者的角色是什么?答:理想情况下,演示者仅转换数据。它将对一层最方便的数据转换为对另一层最方便的数据。

希望本指南可以帮助您回答详细的问题

您可以在我最近的帖子中找到更多详细信息和示例: https://plainionist.github.io/Implementing-Clean-Architecture-UseCases/https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/

答案 1 :(得分:1)

基本上,您希望尽可能地(在圆圈上)推送您的“工具”感知代码。

用例非常接近模型并包含大量业务逻辑——您希望这一层非常干净,以便能够快速轻松地进行单元测试。所以,这一层应该对存储一无所知。

但有趣的部分是当 Room 进入房间时 :) Room 使您可以轻松拥有类似模型的对象,您可以在周围使用,而 IMO,无论您是否为模型使用 Room 注释类,它都是一个灰色区域。< /p>

如果您将 Room 对象视为数据层对象,那么您应该在到达用例之前将它们映射到您的业务对象。 如果你使用 Room 作为 DAO 的内置映射器来建模对象,那么 IMO 你可以在你的用例中使用它们,尽管干净的纯粹主义者可能不会同意这一点。

我的实用建议是 - 如果您的模型具有由多个实体构建的复杂结构,则为它创建一个专用的模型类并将实体映射到它。 如果您有地址之类的东西,IMO 只需使用 Room 实体即可。