google-todo-mvp-clean解读

    关于clean架构其实很多人都应该听说过,但是由于clean架构的项目会显得比较复杂,而且在实际使用总其实真的要用到的很少,所以真的了解这个架构的人也比较少

    其实我自己是实际在项目中使用了clean架构的,所以其实对于clean架构的一些思想是有了一些了解,不过为了加深理解以及看一下是否理解的有所偏差,所以决定对google的demo看一遍并理解一遍

    首先找到google的架构demo的地址clean架构demo

    然后,在看代码之前,最好还是先了解一下clean架构的一个大致的说明


嗯,还是直接上图吧,这张图很抽象,对吧,好吧,其实我现在看这个图也觉得很蛋疼

Enterprise Business Rules:业务对象

Application Business Rules:用于处理我们的业务对象,业务逻辑所在,也称为Interactor

Interface Adapters: 接口转换,拿到我们需要的数据,表现层(Presenters)和控制层(Controllers)就在这一层

Frameworks and Drivers: 这里是所有具体的实现了:比如:UI,工具类,基础框架等等。


加了这些说明呢?还是很抽象啊,面对这种坑爹的图,我觉得我有比较重新画一张我们比较好理解的比较好



嗯,这张图就清晰多了

1、在图中,除了UI、Web、Devices层会有平台相关的api引用之外,其他的层级,都是纯java的,这样就可以让代码可以在多个java开发的平台进行复用

2、在层与层之间需要进行隔离,在Google的demo中因为数据结构比较简单所以可能看不出来,但是比较明显的一点就是,在出了UseCase层之后,就完全看不到Task这个类对象了,这是基本的层级隔离的思维,同时也是兼容复杂需求时,下层获取到的数据在经过转换之后返回为Presenter层或Control层直接便于显示、交互的数据类型,在需求很复杂的项目时,会需要这样做

        具体表现为,UseCase层到Presenter层时,将NoteEntity、FruitEntity等,或者其他各种数据揉合为新的数据类型时使用,由于面向接口是Clean的基本思维方式,所以在中间有一个Convert层将接口Entity实体集合(各种Entity)转化为上层需要的UserEntity之后进行使用

3、以层级为单位的单元测试,在层级分清楚,并且层级之间都是一个接口通信时,进行层级测试、单元测试将非常方便

4、依赖注入的方式导致灵活修改逻辑、实现,由于实际的数据访问、平台代码都是在UI、Web、Devices层来实体并注入,导致当我们想替换数据的获取来源、替换业务逻辑时,不会影响其他层级以及实现的代码,只需要将新的实现类(新的实现类也只需要专注自己的业务,比如业务逻辑、数据访问等)在注入的地方注入新的实现类即可,并且新的实现类的一些所需要的类等都可以在外层构造器中直接实体化并传入,最小代价实现新的需求


目录说明

在大体的结构有了之后,可以来看一下谷歌的demo的结构



这其中:

1、BaseView、Baseresenter、UseCase是公共基类,UseCaseHandler、UseCaseScheduler、UseCaseThreadPoolScheduler是线程控制作用

2、util包中是工具类

3、data.source是实际数据加载的部分,从本地以及网络加载对应数据,TasksRespository就是在这个包中,在demo中它是实际加载数据的中心,不管是网络数据还是本地数据



剩余的addedittask、statistics、taskdetail等都是以功能为模块的划分

代码分析

我们以tasks这个模块下的代码为例子开始分析,因为这个额模块的代码结构上来说比较齐全一点


从项目结构来看的话,是一个标准的mvp,但是对p层进行了分离,分理处很多歌usecase

图中有说明的我就不说了,没有标明的大概说一下,TaskFragment是View层,TasksActivity可以看一下

public class TasksActivity extends AppCompatActivity {

    private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";

    private DrawerLayout mDrawerLayout;

    private TasksPresenter mTasksPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tasks_act);

        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setHomeAsUpIndicator(R.drawable.ic_menu);
        ab.setDisplayHomeAsUpEnabled(true);

        // Set up the navigation drawer.
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);
        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        if (navigationView != null) {
            setupDrawerContent(navigationView);
        }

        TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

        // Create the presenter
        mTasksPresenter = new TasksPresenter(
                Injection.provideUseCaseHandler(),
                tasksFragment,
                Injection.provideGetTasks(getApplicationContext()),
                Injection.provideCompleteTasks(getApplicationContext()),
                Injection.provideActivateTask(getApplicationContext()),
                Injection.provideClearCompleteTasks(getApplicationContext())
                );

        // Load previously saved state, if available.
        if (savedInstanceState != null) {
            TasksFilterType currentFiltering =
                    (TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
            mTasksPresenter.setFiltering(currentFiltering);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putSerializable(CURRENT_FILTERING_KEY, mTasksPresenter.getFiltering());

        super.onSaveInstanceState(outState);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                // Open the navigation drawer when the home icon is selected from the toolbar.
                mDrawerLayout.openDrawer(GravityCompat.START);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void setupDrawerContent(NavigationView navigationView) {
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem) {
                        switch (menuItem.getItemId()) {
                            case R.id.list_navigation_menu_item:
                                // Do nothing, we're already on that screen
                                break;
                            case R.id.statistics_navigation_menu_item:
                                Intent intent =
                                        new Intent(TasksActivity.this, StatisticsActivity.class);
                                startActivity(intent);
                                break;
                            default:
                                break;
                        }
                        // Close the navigation drawer when an item is selected.
                        menuItem.setChecked(true);
                        mDrawerLayout.closeDrawers();
                        return true;
                    }
                });
    }

    @VisibleForTesting
    public IdlingResource getCountingIdlingResource() {
        return EspressoIdlingResource.getIdlingResource();
    }
}

这个类很简单,就做类两件事

1、多个View的组织,一个侧边栏,一个主页面

2、依赖注入

TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

        // Create the presenter
        mTasksPresenter = new TasksPresenter(
                Injection.provideUseCaseHandler(),
                tasksFragment,
                Injection.provideGetTasks(getApplicationContext()),
                Injection.provideCompleteTasks(getApplicationContext()),
                Injection.provideActivateTask(getApplicationContext()),
                Injection.provideClearCompleteTasks(getApplicationContext())
                );

Presenter的实体化、View的实体化,以及GetTask等UseCase的实体化都在这里,这里有一个Injection.providegetTasks(...)要注意

先来看GetTask,这是最小功能点,看他内部的代码

public class GetTasks extends UseCase<GetTasks.RequestValues, GetTasks.ResponseValue> {

    private final TasksRepository mTasksRepository;

    private final FilterFactory mFilterFactory;

    public GetTasks(@NonNull TasksRepository tasksRepository, @NonNull FilterFactory filterFactory) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        mFilterFactory = checkNotNull(filterFactory, "filterFactory cannot be null!");
    }

    @Override
    protected void executeUseCase(final RequestValues values) {
        if (values.isForceUpdate()) {
            mTasksRepository.refreshTasks();
        }

        mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                TasksFilterType currentFiltering = values.getCurrentFiltering();
                TaskFilter taskFilter = mFilterFactory.create(currentFiltering);

                List<Task> tasksFiltered = taskFilter.filter(tasks);
                ResponseValue responseValue = new ResponseValue(tasksFiltered);
                getUseCaseCallback().onSuccess(responseValue);
            }

            @Override
            public void onDataNotAvailable() {
                getUseCaseCallback().onError();
            }
        });
    }

    public static final class RequestValues implements UseCase.RequestValues {

        private final TasksFilterType mCurrentFiltering;
        private final boolean mForceUpdate;

        public RequestValues(boolean forceUpdate, @NonNull TasksFilterType currentFiltering) {
            mForceUpdate = forceUpdate;
            mCurrentFiltering = checkNotNull(currentFiltering, "currentFiltering cannot be null!");
        }

        public boolean isForceUpdate() {
            return mForceUpdate;
        }

        public TasksFilterType getCurrentFiltering() {
            return mCurrentFiltering;
        }
    }

    public static final class ResponseValue implements UseCase.ResponseValue {

        private final List<Task> mTasks;

        public ResponseValue(@NonNull List<Task> tasks) {
            mTasks = checkNotNull(tasks, "tasks cannot be null!");
        }

        public List<Task> getTasks() {
            return mTasks;
        }
    }
}

可以看到他这里面实质性的访问数据是用TasksRespository来进行的,这个对象哪里来呢?就是Injection.providegetTasks(...)

其实写的大一点的话TasksRespository这边在其他人的demo里面也是一个接口,然后所有实质性执行、类的实例化都是在Activity层注入实现的,这就是:面向接口+依赖注入


然后这时候回过头来理解一下的话:

TasksFragment是View层

TasksPresenter是P层

GetTasks是UseCase层

在Presenter层和UseCase层是实际的使用逻辑,View是使用的表现

在UseCase层是针对实际需求点,内部数据访问是通过Respository层

而这里面所有的实现,原则上是可以全部用接口、抽象类来实现的,而真正的实现,都可以在Activity层实例化之后注入


这样就可以做到

1、无缝修改,不会影响其他部分

2、跨平台制作,因为所有跟平台有关的代码都是注入进去的,在最外层

3、测试方便,实例化时可以直接注入测试数据生成类,而不需要修改代码来mock数据

....





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编写基于WPF的桌面应用——Todo,是一个非常有趣且具有挑战性的任务。WPF是一种用于创建桌面应用程序的技术,它提供了丰富的用户界面元素和MVVM(模型-视图-视图模型)模式,使开发人员能够创建美观且响应式的应用程序。 首先,在WPF中创建Todo应用的用户界面会更加直观和灵活。可以使用各种控件和布局,如列表框、按钮、文本框等,来设计Todo列表的显示和编辑界面。还可以添加复选框来标记已完成的任务,使用滚动视图以处理较长的任务列表,并设计一致的样式和颜色方案以提升用户体验。 其次,在WPF中实现Todo应用的功能也相对简单。可以创建一个Todo类来表示单个任务,包含任务标题、描述、截止日期等属性,并在界面中创建一个列表来显示所有的任务。用户可以通过输入框和按钮,添加、修改或删除任务。可以使用数据绑定机制将任务数据与界面元素实时同步,并借助WPF的命令系统实现任务操作的逻辑。此外,还可以使用数据验证机制对任务的属性进行验证,以确保输入的合法性。 另外,WPF还提供了强大的样式和模板功能,使开发人员能够轻松地对界面进行自定义。可以根据个人喜好和应用的特点,设计各种不同的主题和样式,使Todo应用拥有独特而美观的外观。 总之,编写基于WPF的桌面应用Todo是一项有趣且具有挑战性的任务。借助WPF丰富的用户界面元素和强大的功能,我们可以创造出一个美观、功能完善的Todo应用,提升用户体验并提高工作效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值