MVP架构入门梳理

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








AspNetForums 的汉化版和改写版,主要改写在于: 1、修改了邮件发送程序,设计了可发送需要身份验证的邮件发送组件。 2、设计了一个统一用户群组件(OneUsers.dll),只要你使用 OneUsers 的用户群(数据库可以使用我们的也可以使用你们自己的,我们提供 OneUsers 的数据库的 SQL 脚本),用户只要在加入本站用户群的任何一个论坛注册一次,就可以在其他论坛以同样的帐号登陆。 比如,你在 www.chinamvp.org 注册一次,那么你也可以用此帐号在 www.onebbs.net 登陆,而无需再次注册。 安装文档 1、必须安装MS SQL SERVER 2、必须安装.Net Framework + IIS 1、安装 MS SQL SERVER 论坛数据库文件 首先在 查询分析器里执行 aspnetforums_oneuser1.0.sql脚本安装论坛数据库 然后是 CreateDefaultData1.0.sql 安装默认数据 oneuser1.0.sql 是我们统一使用的用户群库,你可以不安装,而是使用我们的默认数据库,这个默认数据库在我们的服务器上。 如果你是英文系统 请执行 aspnetforums_oneuser1.0forEnSqlServer.sql 和 oneuser1.0forEnSqlServer.sql 但是不能保证论坛 能够正常执行 2、设置 AspNetForums 里的web.config <add key="connectionString" value="server=localhost; User ID=sa; Password=;database=aspnetforums" /> aspnetforums数据库 用户名和密码 <add key="connectionString" value="server=localhost; User ID=sa;database=OneUser" /> oneuser数据库 用户名和密码,你可以删除他或者注释掉,那么就是默认使用我们服务器上的统一用户库 3、设置邮件服务器 <add key="smtpServer" value="yourhost.com" /> <!-- Can specify SMTP Server to use to send out emails. Use "default" to use the default Windows 2000 SMTP Server --> <add key="ChinaASPXMailer" value="True" /> <!-- 如果你使用我们定制的邮件发送组件,请设为 True --> <add key="UserName" value="test@yourhost.com" /> <!--ChinaASPXMailer s UserName ,For Auth mail server --> <add key="Password" value="test" /> <!--ChinaASPXMailer s Password ,For Auth mail server --> <add key="Port" value="25" /> <!-- Port for ChinaASPXMailer s prot --> <add key="FromName" value="ChinaMVP.COM" /> <!-- FromName for ChinaASPXMailer s FromName --> <!--add key="Charset" value="GB2312" /--> <!-- FromName for ChinaASPXMailer s FromName ,Default value is gb2312 --> 把以上的 yourhost.com 改成你的邮件服务器 如果需要验证,请设置 UserName,否则 删除或者注释掉 4 、设置IIS,在你的web站点里设置一个虚拟目录 其物理目录指向 AspNetForums 目录。 5、解决方案 EngineAspNetForums.sln 6、管理员帐号: user:admin pwd:admin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值