浅谈MVP架构模式

MVP即Model-View-Presenter,它是在经典的MVC(Model-View-Controller)架构基础上延伸出来的一种架构模式。Google官方也给出了一个简单的MVP示例

MVC与MVP

MVP和MVC有很多共性,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。

模型关系图如下:

  • Model:定义数据的格式,这一点与JavaBean或者Domain很相似,有时候Model也确实仅仅表示一个实体类。但是在MVP中它还负责存储、检索、操纵数据,这些数据包括从缓存、数据库或者远程网络等处获取的数据。多数时候操纵数据都比较耗时,如操作硬盘、数据库或者网络数据,所以Model中会涉及到异步操作,一般异步操作都是通过回调的方式实现,因此也有开发者把这里种操作异步数据称作业务逻辑处理,有点类似业务层功能。
  • View:负责渲染UI元素、与用户进行交互,在Android中,View可能是指某个Fragment或者Activity
  • Presenter:Presenter直接翻译表示主持人、发言人。在MVP中Presenter可以叫做调度者,这里之所不称作Controller,是因为它主要是为了处理View和Model的调度关系,那么怎么理解这种调度关系呢?假设View接收用户的一个交互事件,需要渲染一个List列表,View可以通过某个方法对Presenter说:“Hi,请给我一些列表数据,我这边急用”。然后Presenter层就去找Model,通过调用Model的某个方法告诉Model,“Hi,View那边需要这种格式的数据,帮处理一下,送些数据过来”。最后Model拿到数据后会通过Presenter传递给View,这个过程中Model并没有直接与View进行交互,而是通过Presenter这个调度者间接交互。
  • Controller:在本文介绍Controller控制层,主要还是为了对比一下Presenter,加深对Presenter的理解。在MVC中,Controller是最重要的控制器,大部分业务都是在Controller中处理的,同时在用户请求到达或者事件发生时都会首先通知控制器并由它来决定如何响应这次请求或者事件。在Android中,Controller一般指的是Fragment或者Activity

无论是在MVC还是MVP中,Model层承担的功能没有什么分别。但是,通过上面分析可以知道,在Android中Fragment或者Activity,不仅担当了View的职责,在MVC中,还担当了Controller的职责,在网上有些开发者将MVC中的View仅仅指代Android的视图View,这么理解也可以,视图View也是在Fragment或者Activity中拿到的。

在MVC中,视图View的逻辑会在Fragment或者Activity处理,而且Model层的业务也是在Fragment或者Activity中处理,这样会导致一个问题,Fragment或者Activity中代码臃肿,难以维护,其实正是这个原因,MVP架构在Android开发中呼声一直很高。

在MVP中,虽然视图View还是在Fragment或者Activity中处理,但是Model层的业务被放到了Presenter层,类似于增加了一层职责部门,Fragment或者Activity有一部分职责转接给了Presenter,所有View层操作都是通过Presenter传递给Model,这样有效减轻了Fragment或者Activity的负担,View与Model不直接交互,而是通过与Presenter来完成交互,这样可以修改视图而不影响模型Model,达到解耦的目的。

在上文称MVP中Presenter为调度者,这个称谓可能不是很确切。Android开发中,有时候某个界面的视图很复杂,并且界面中各模块使用的接口也不同,这种场景则可以显示出MVP架构的优势了。假设当前界面只需要一个接口数据就可以满足,添加了一层Presenter可能在架构上有些多余,但是一旦服务器接口有变动,为了减轻服务端压力,现将接口拆分,本来请求一个接口可以满足需要,现在可能需要请求两个甚至多个接口,这就是所谓的Model层有变动,但是视图层View不变。既然View层所需要的数据格式是特定的,那么就可以根据具体业务需要,在Presenter将拆分的接口重新组合为原视图层需要的数据格式,然后传递给View,这样View层相当于没有做任何变化,很好的解耦了View和Model。

如下是Google官方对于架构的解释。

编写最适合每种情况的应用程序是不可能的。话虽这么说,这个推荐的架构是大多数情况和工作流程的良好起点。如果您已经有一种编写遵循通用架构原则的Android应用程序的好方法,则无需更改它。

MVP示例

本文所介绍的示例很简单,首先有一个书籍列表,点击每一个条目,都可以查看书籍的详情信息。

项目结构图如下:

创建View和Presenter基类

public interface BaseView<T> {

    void setPresenter(T presenter);

}
public interface BasePresenter {

    void start();

}

创建Model基类

这里不要被名字误导,在本示例中将Model的相关类命名为了Controller类名。

public interface ResponseCallBack<T> {

    void onSuccess(T t);

    void onFail(Exception e);

}
public class BaseControl <T>{

    protected MainThreadExecutor mainThreadExecutor;

    public BaseControl() {
        mainThreadExecutor = new MainThreadExecutor();
    }

    public static class MainThreadExecutor implements Executor {
        private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NonNull Runnable command) {
            mainThreadHandler.post(command);
        }
    }
}

创建书籍列表的Model

public class BookListControl extends BaseControl<List<Book>> {

    //模拟数据从子线程获取数据
    public void getBookList(String uid, final ResponseCallBack<List<Book>> callback) {
        new Thread() {
            @Override
            public void run() {
                SystemClock.sleep(3000);
                mainThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        List<Book> list = DataUtils.getList();
                        if (list == null) {
                            callback.onFail(new Exception("list is null"));
                        } else {
                            callback.onSuccess(DataUtils.getList());
                        }
                    }
                });
            }
        }.start();
    }
}

创建View和Presenter契约类Contact

public interface BookListContact {

    interface Presenter extends BasePresenter {
        void getBookList(String uid);

        void onItemClick(Book book, int index);
    }

    interface View extends BaseView<Presenter> {
        void setLoadingIndicator(boolean active);

        void showBookList(List<Book> list);

        void showBookDetailsUi(int index);

        void showError(String errorMsg);
    }
}

创建书籍列表的Presenter

public class BookListPresenter implements BookListContact.Presenter {

    private BookListContact.View bookListView;

    public BookListPresenter(BookListContact.View bookListView) {
        this.bookListView = bookListView;
    }

    @Override
    public void getBookList(String uid) {
        bookListView.setLoadingIndicator(true);
        new BookListControl().getBookList(uid, new ResponseCallBack<List<Book>>() {
            @Override
            public void onSuccess(List<Book> books) {
                bookListView.showBookList(books);
                bookListView.setLoadingIndicator(false);
            }

            @Override
            public void onFail(Exception e) {
                bookListView.showError(e.getMessage());
                bookListView.setLoadingIndicator(false);
            }
        });
    }

    @Override
    public void onItemClick(Book book, int index) {
        checkNotNull(book, "book cannot be null!");
        bookListView.showBookDetailsUi(index);
    }

    @Override
    public void start() {
        getBookList("1001");
    }
}

创建书籍列表的Activity

public class BookListActivity extends AppCompatActivity implements BookListContact.View {

    private ListView listView;
    private TextView tvError;
    private ContentLoadingProgressBar progressBar;

    private BookListAdapter bookListAdapter;
    private BookListContact.Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_list);
        initView();
        setListener();
        initData();
    }

    private void initView() {
        listView = findViewById(R.id.listView);
        tvError = findViewById(R.id.tv_error);
        progressBar = findViewById(R.id.progressBar);
    }

    private void setListener() {
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                presenter.onItemClick(bookListAdapter.getItem(position), position);
            }
        });
    }

    private void initData() {
        presenter = new BookListPresenter(this);
        bookListAdapter = new BookListAdapter(this);
        listView.setAdapter(bookListAdapter);
        presenter.start();
    }

    @Override
    public void setLoadingIndicator(boolean active) {
        if (active) {
            progressBar.show();
        } else {
            progressBar.hide();
        }
    }

    @Override
    public void showBookList(List<Book> list) {
        tvError.setVisibility(View.GONE);
        listView.setVisibility(View.VISIBLE);
        bookListAdapter.setList(list);
        bookListAdapter.notifyDataSetChanged();
    }

    @Override
    public void showError(String errorMsg) {
        listView.setVisibility(View.GONE);
        tvError.setVisibility(View.VISIBLE);
        tvError.setText(errorMsg);
    }

    @Override
    public void showBookDetailsUi(int index) {
        Intent intent = new Intent(this, BookDetailActivity.class);
        intent.putExtra(BookDetailActivity.BOOK_INDEX, index);
        startActivity(intent);
    }

    @Override
    public void setPresenter(BookListContact.Presenter presenter) {
        this.presenter = presenter;
    }
}

小结

MVP对Android开发人员并不陌生,Google官方也给出了相应的示例,说明MVP在开发中也是一个备受推崇的一个架构模式。作为Android开发,也接触过几个使用MVP的项目,但是没有一个项目是严格按照MVP模式开发的。网上也有很多人在讨论MVP模式,但是基本上是一些简单的示例,比如就是一个登录注册示例。虽然通过一个示例确实可以显示出对MVP架构的理解程度,但是通过示例介绍后,可能发现MVP并不是一定比MVC更具有适用性。

MVP作为一个比较有争议的架构模式,虽然可以解决MVC中遇到的某些问题,可是也要认识到MVP的不足,开发中可能更多的也会结合MVC的方式,Model直接通知View进行变更。在实际的应用中可能已经在不知不觉中将几种模式融合在一起,但是为了代码的可扩展、可测试性,必须做到模块的解耦,不相关的代码不要放在一起。

参考资料

Android架构(一)MVP全解析

浅谈MVP设计模式

浅谈 MVC、MVP 和 MVVM 架构模式

Android App的设计架构:MVC,MVP,MVVM与架构经验谈

评论

您确定要删除吗?删除之后不可恢复