MVP架构入门梳理

来源:互联网 发布:下载app软件 编辑:程序博客网 时间:2024/05/22 20:03

Mvp架构demo梳理

        MVP已经算是一个很常见的架构了,网上一搜一堆相关的内容。但是貌似实际的项目中使用的还不是很多,周围的朋友有个别的公司项目架构用到了。自己也常常听说相关的概念。虽然没有使用到,但还是动手操作,梳理一下,有一个更深刻的认识。

        按照正常思路,首先梳理一下什么是MVP?

        MVP是相对于MVC这种架构模式来说的一个架构模式。MVC按照大众的说法就是Model、View、Controller。在Android系统下大致可以理解为Model就是数据库,接口数据的数据bean模型和这些数据相关的处理逻辑。View就是我们可以看到的部分,在Android中以xml的方式展现,最后看,基本上Activity承担了Controller的功能,大部分对Model和View的处理都在Activity中完成。视图通过反馈到用户的操作,把操作传递给控制器部分,控制器决定调用什么模型,模型在去调用相应的业务数据处理,需要返回数据的处理完成后返回给控制器,控制器在调用View的操作方法,最终将数据渲染在View上显示出来。

       根据网上的说法MVP主要是为了方便后续的单元测试来做的,降低代码耦合度,方便测试人员测试。这样,我们先来看一个google提供的有关MVP的案例:

       给上Google的连接:https://github.com/googlesamples/android-architecture/tree/master

      运行出来添加两个数据大概是这个样子:

        

    代码结构:

    

   包含了一个主页模块task、一个编辑笔记模块addedittask、一个统计模块statistics、还有一个data模块以及tasktdetail。还有两个单独的类BasePresenter和BaseView。

   从主要的task主页和编辑addedittask中我们发现,task模块中除了ScrollChildSwipeRefreshLayout、TasksFilterType这两个类外其他的都是一样的模式。一个Contract、一个Activity、一个Fragment、一个Presenter。这样我们就可以发现规律了,在这个demo中MVP的写法基本就是固定的这样。进入这几个类看一下:

AddEditTaskActivity:

 AddEditTaskFragment addEditTaskFragment = (AddEditTaskFragment) getSupportFragmentManager()                .findFragmentById(R.id.contentFrame);        String taskId = getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);        setToolbarTitle(taskId);        if (addEditTaskFragment == null) {            addEditTaskFragment = AddEditTaskFragment.newInstance();            if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {                Bundle bundle = new Bundle();                bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);                addEditTaskFragment.setArguments(bundle);            }            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),                    addEditTaskFragment, R.id.contentFrame);        }        boolean shouldLoadDataFromRepo = true;        // Prevent the presenter from loading data from the repository if this is a config change.        if (savedInstanceState != null) {            // Data might not have loaded when the config change happen, so we saved the state.            shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY);        }        // Create the presenter        mAddEditTaskPresenter = new AddEditTaskPresenter(                taskId,                Injection.provideTasksRepository(getApplicationContext()),                addEditTaskFragment,                shouldLoadDataFromRepo);
除了设置toolBar现在就剩这些主要代码,首先去根据contentFrame id去找是否存在这个Fragment,不存在就通过newInstance()创建一个,最后添加到本Activity

 ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),                    addEditTaskFragment, R.id.contentFrame);
最后一小段代码创建了一个AddEditTaskPresenter Presenter对象,进去看看:

 public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,            @NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {        mTaskId = taskId;        mTasksRepository = checkNotNull(tasksRepository);        mAddTaskView = checkNotNull(addTaskView);        mIsDataMissing = shouldLoadDataFromRepo;        mAddTaskView.setPresenter(this);    }
前面看到,参数是一个taskid,第二个是TasksDataSource 类型参数,第三个是一个AddEditTaskContract.View 类型的参数,回到前面的Activity我们看到第二个参数调用方法:

 public static TasksRepository provideTasksRepository(@NonNull Context context) {        Log.e("TasksRepository","创建TasksRepository");        checkNotNull(context);        return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),                TasksLocalDataSource.getInstance(context));    }
返回了TasksRepository 对象。第三个参数传递了Fragment的对象addEditTaskFragment,为什么是Fragment对象呢?查看一下Fragment发现AddEditTaskFragment实现了AddEditTaskContract.View这接口,而参数类型正好就是这个类型。现在在来看前面这个构造方法,内部是给成员赋值的操作,最后一行给赋值后的成员设置了Presenter(this);
 mAddTaskView.setPresenter(this);

然后去AddEditTaskContract类看,发现就是定义了两个接口,View和Presenter接口和一些方法,再看看AddEditTaskFragment,发现实现了View的接口TasksContract.View并且重写了所有的接口方法。在onActivityCreated方法中添加了FloatingActionButton 并且添加了点击监听,这里就是demo中创建一个笔记的操作:

 FloatingActionButton fab =                (FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done);        fab.setImageResource(R.drawable.ic_done);        fab.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());            }        });
点击中调用了AddEditTaskContract.Presenter mPresenter成员的方法 mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());

最后看AddEditTaskPresenter这个类,看到这个类实现了AddEditTaskContract.Presenter, TasksDataSource.GetTaskCallback这两个接口并重写了所有的方法。这就是我们的Presenter的实现类。前面我们看到了他的构造方法参数,其中第二参数是一个TasksDataSource类型的参数。我们查看一下,这是data包下一个接口,从名字上看,这里是对数据的处理,有一个local和remote也就是本地和网络数据的处理。TasksDataSource这个接口中定义了一些方法。然后在搜索发现,TasksLocalDataSource,TasksRemoteDataSource、FakeTasksRemoteDataSource、TasksRepository这几个类都实现了这个接口并重写了方法。还记得AddEditTaskPresenter这个构造方法中传递了数据,我们去看看

 public static TasksRepository provideTasksRepository(@NonNull Context context) {        Log.e("TasksRepository","创建TasksRepository");        checkNotNull(context);        return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),                TasksLocalDataSource.getInstance(context));    }
传递数据的时候在Injection类中调用的上述方法,创建了TasksRepository这个对象。我们进入TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),TasksLocalDataSource.getInstance(context));这个方法发现他的构造方法是
 private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,                            @NonNull TasksDataSource tasksLocalDataSource) {        mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);        mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);    }
上面这样的,传入了两个数据类型。点击传入的两个参数,最后发现,分别是返回一个FakeTasksRemoteDataSource和TasksLocalDataSource的对象,从名字上我们知道这两个类就是实现了TasksRepository接口并处理网络数据和本地数据的类。在进入这两个类内部查看代码发现,而在这些类内部处理了本地数据保存数据库和网络数据的操作。并且在AddEditTaskPresenter中创建了TasksDataSource接口引用,也就是包装起来本地数据和网络数据直接给调用者使用。所以我们也明白了。AddEditTaskPresenter的构造方法中传递了主要的两个参数一个是笔记的数据也就是Model,一个是MVP中的View。

现在想想,如果是AddEditTaskContract.View这个是MVP中的View,而实现了Presenter接口的AddEditTaskPresenter就是Mvp中的P。这样看,AddEditTaskFragment在这个demo中Fragment充当了View的角色,在Activity的onCreate方法中同时创建了Fragment对象和Presenter对象,并且在AddEditTaskPresenter的构造方法中完成了View和Presenter的赋值绑定。这样我们根据M、V、P这样的方式基本上看清了demo中不同的角色。Activity本身并没有处理逻辑,而是添加了一个Fragment让Fragment充当了View的角色,实现View的方法。AddEditTaskPresenter 中传入了数据也就是Model和View的参数,Presenter中持有了View,从而完成Model和View的控制交互。这样避免了在Activity中写控制代码。

然后看看demo中的其他模块如:statistics,发现基本也是上面这种思路来组织代码的。这也就是真个Google提供的MVP demo写法吧。看完了Google的demo,网络上还有一个比较火的demo有4000多星。顺便看看其他的Mvp是怎么组织代码的。

        首先给出链接:https://github.com/antoniolg/androidmvp

       运行出来样子和代码结构图:

          
 按照MVP代码的组织方式,我们根据名字很快就知道代码中有LoginView、LoginPresenter接口,和实现类LoginPresenter、LoginPresenter。也就是说在这个demo椎间盘每个Activity充当了MVP中View的角色,P的实现类是LoginPresenter。我们在去实现类的构造方法看一下:

 public LoginPresenterImpl(LoginView loginView) {        this.loginView = loginView;        this.loginInteractor = new LoginInteractorImpl();    }
LoginPresenterImpl 构造方法中传入了一个LoginView的参数。构造方法创建了一个LoginInteractorImpl 成员,我们查看这个类,这个类实际上实现了LoginInteractor接口,重写模拟了登录业务方法,处理了登录的数据。也就是说这是一个业务层的封装。这里似乎看的不太明白,相比上面的Google的demo这个没有data数据项的处理。我们在看一下这个demo中的Main,和Login类似MainActivity实现了MainView充当了View。activity中创建了Presenter 

presenter = new MainPresenterImpl(this, new FindItemsInteractorImpl());
这里Presenter传入的两个参数this和FindItemsInteractorImpl 类的实例,进入看一下这个FindItemsInteractorImpl,发现实现了FindItemsInteractor的接口,并且重写了一个方法:

 @Override public void findItems(final OnFinishedListener listener) {        new Handler().postDelayed(new Runnable() {            @Override public void run() {                listener.onFinished(createArrayList());            }        }, 2000);    }
这个方法用来模拟获取一个List列表的数据的过程,调用了createArrayList() 方法,因此这个类可以看做是Main中Model的模块,包含处理业务数据的逻辑实现。这样大致明白Login中的LoginInteractorImpl类作用应该类似,是处理数据的Model。

这样我们就基本把两个MVP的demo看完了,发现第一个demo中Activity是一个单独的类没有做什么功能,Fragment充当了View的功能。第二个demo中Activity本身实现了View的接口充当View。其他的Presenter和Model基本一致,在Model中处理本地或者网路数据bean以及获取数据的数据库操作逻辑和网络操作逻辑。在创建Presenter实例的时候传入View和Model数据,在Presenter控制Veiw和Model的交互。google的demo使用了一个Fragment来充当View可能就是进一步分离Activity,让整体看起来更清晰,但是感觉这样似乎有些多余了,本来MVP模式下由于接口的作用已经多了很多类,这样在加上Fragment处理,又会多出很多的类,有时候可以直接在Activity处理更好,不过这也看自己的实现了。

        上面可以看出MVP架构很大程度上也是接口编程的一种形式,采用接口类封装逻辑的处理方法,最后提供一个包装的引用工调用者调用(google demo中的data部分)。理解了以后发现代码结构是比较清晰的。但是相比原来写代码,可能是MVC吧,代码量和类的数量增加了很多,对于初学者真的还是很难理解。以上就是我对demo和MVP学习的一些拙见。


参考:

http://blog.csdn.net/lmj623565791/article/details/46596109

http://www.jianshu.com/p/6e3fd11ad09d

https://github.com/antoniolg/androidmvp

https://github.com/googlesamples/android-architecture/tree/master