前言:本文所写的是博主的个人见解,如有错误或者不恰当之处,欢迎私信博主,加以改正!原文链接,demo链接
MVP 架构简介
说起 MVP 架构,相信很多朋友都看过,网上也有很多这方面的资料。博主使用 MVP 架构搭建项目也有一段时间了。简单谈一谈心得。说到 MVP 架构,很多人都拿它跟 MVC 去对比。这里我就不过多重复说了,单刀直入。
什么是 MVP 架构
MVP 架构由 Model(模型)、View(视图)、Presenter(主持者)构成,下面我们一起来了解它们:
- Model 负责业务逻辑以及数据的处理,主要通过接口实现
- View 负责 UI 显示以及与用户之间的交互
- Presenter 起到一个衔接桥梁的作用,负责 Model 跟 View 之间的交互
MVP 架构的利弊
优点
耦合度低
View 跟 Model 之间由 Presenter 负责两者之间的交互,低了其耦合度,使其更加关注自身逻辑,结构清晰可维护性高
每个 View 都有其对应的 Presenter,容易进行区分,哪个模块出现了问题,或者接口出现了问题,可以迅速的确定。模型与视图之间完全分离,修改视图不影响模型方便单元测试
因其业务逻辑都在 Presenter 里,进行单元测试的时候,可以直接写个测试接口,由 Presenter 去继承
缺点
类数量暴涨
每个 View 都有 Presenter ,跟其对应的接口,类的数量会明显变多,在某些场景下 Presenter 的复用会产生接口冗余。额外的学习曲线
需要花费额外的时间去学习,学习理解成本高,开始编写代码之前需要时间成本(项目的架构)
实战演练
前面讲述了一堆的理论知识,下面一步步解剖 MVP 架构,下图是 Demo 的目录结构(看起来比较复杂,勿怪),实现模拟网络获取图书数据并将其显示的功能
下面我们来看项目实现效果,功能比较简单,gif图就不弄了
Model
创建实体类 Book
public class Book {
private int book_id;
private String book_name;
private String book_author;
private String book_tag;
public Book() {
}
public Book(int book_id, String book_name, String book_author, String book_tag) {
this.book_id = book_id;
this.book_name = book_name;
this.book_author = book_author;
this.book_tag = book_tag;
}
public int getBook_id() {
return book_id;
}
public void setBook_id(int book_id) {
this.book_id = book_id;
}
public String getBook_name() {
return book_name;
}
public void setBook_name(String book_name) {
this.book_name = book_name;
}
public String getBook_author() {
return book_author;
}
public void setBook_author(String book_author) {
this.book_author = book_author;
}
public String getBook_tag() {
return book_tag;
}
public void setBook_tag(String book_tag) {
this.book_tag = book_tag;
}
}
创建一个接口,用于获取回调实体类 Book 携带的数据
public interface BooksDataSource {
interface LoadBooksCallback{
void loadBooks(List bookList);
void dataNotAvailable();
}
void getBooks(@NonNull LoadBooksCallback loadBooksCallback);
}
继承 BooksDataSource 接口,实现模拟数据获取
public class BooksLocalDataSource implements BooksDataSource{
private static BooksLocalDataSource INSTANCE;
public static BooksLocalDataSource getInstance() {
if (INSTANCE == null) {
INSTANCE = new BooksLocalDataSource();
}
return INSTANCE;
}
private BooksLocalDataSource(){}
@Override
public void getBooks(@NonNull LoadBooksCallback loadBooksCallback) {
//模拟数据
List bookList = new ArrayList<>();
Book book1 = new Book(1,"《第一行代码:Android (第2版) 》","郭霖","编程");
Book book2 = new Book(2,"《Android开发艺术探索》","任玉刚","编程");
Book book3 = new Book(3,"《Android群英传》","徐宜生","编程");
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
loadBooksCallback.loadBooks(bookList);
}
}
数据回调业务处理,网络数据和本地数据(这里仅模拟本地数据)
public class BooksRepository implements BooksDataSource{
private static BooksRepository INSTANCE = null;
private final BooksDataSource mBooksRemoteDataSource;
private final BooksDataSource mBooksLocalDataSource;
private BooksRepository(@NonNull BooksDataSource booksRemoteDataSource,
@NonNull BooksDataSource booksLocalDataSource) {
mBooksRemoteDataSource = booksRemoteDataSource;
mBooksLocalDataSource = booksLocalDataSource;
}
public static void destroyInstance() {
INSTANCE = null;
}
public static BooksRepository getInstance() {
if (INSTANCE == null) {
INSTANCE = new BooksRepository(BooksRemoteDataSource.getInstance(), BooksLocalDataSource.getInstance());
}
return INSTANCE;
}
@Override
public void getBooks(@NonNull final LoadBooksCallback loadBooksCallback) {
//数据回调
mBooksLocalDataSource.getBooks(new LoadBooksCallback() {
@Override
public void loadBooks(List bookList) {
loadBooksCallback.loadBooks(bookList);
}
@Override
public void dataNotAvailable() {
loadBooksCallback.dataNotAvailable();
}
});
}
}
到这里,Model的任务算是结束了,得到了所需要的数据源。
View
Presenter 与 View 是通过接口进行交互,所以这里可以定义一个接口,用于进行交互,此处的难点在于,要清楚需要哪些方法。
这里建立两个Base基类,用于初始化
public interface BaseView {
void setPresenter(T presenter);
}
public interface BasePresenter {
void start();
}
从上面的demo运行演示图看,这里 View 的工作主要有2个,一个是显示无数据时的状态,第二个是显示书籍的列表
void showBookList(List bookList);
void showNoBooks();
本demo演示的是本地模拟数据,关于网络数据方面,可以模拟开启一个线程,通过 Thread.sleep( long )
充当耗时操作,使用 ProgressBar,给用户一个友好提示,同时需要在 View 的接口中定义相关方法
小结:在 View 的方法定义上,需要观察功能上的操作,接着考虑:
- 该操作需要做什么?( loadBooks )
- 操作后的结果反馈?(showBookList,showNoBooks)
- 操作中的友好交互?(显示正在加载,加载完成)
经过上面的思考后,接下来就是 View 的实现,也就是 Activity ,MVP 中的 View 主要对应的是 Activity
public class MainActivity extends AppCompatActivity implements BooksContract.View {
private BooksContract.Presenter mPresenter;
private Button showBooksBtn;
private TextView noDataText;
private ListView bookListView;
private BooksAdapter booksAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mPresenter = new BooksPresenter(BooksRepository.getInstance(),this);
}
private void initView() {
showBooksBtn = (Button) findViewById(R.id.show_books_btn);
noDataText = (TextView) findViewById(R.id.no_data_text);
bookListView = (ListView) findViewById(R.id.books_list_view);
showBooksBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.loadBooks();
}
});
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public void setPresenter(BooksContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public void showBookList(List bookList) {
if (!bookList.isEmpty()) {
noDataText.setVisibility(View.INVISIBLE);
}
booksAdapter = new BooksAdapter(getApplicationContext(), bookList);
bookListView.setAdapter(booksAdapter);
}
@Override
public void showNoBooks() {
noDataText.setVisibility(View.VISIBLE);
}
}
从上面的代码看 Activity 实现还是比较简单的,接口引导我们去实现对应的功能,下面是 BooksAdapter 用于显示书籍列表
public class BooksAdapter extends BaseAdapter {
private List mBookList;
private Context mContext;
private LayoutInflater inflater;
public BooksAdapter(Context context, List bookList) {
inflater = LayoutInflater.from(context);
mBookList = bookList;
mContext = context;
}
@Override
public int getCount() {
return mBookList.isEmpty() ? 0 : mBookList.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return mBookList.get(position).getBook_id();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BookViewHolder bookViewHolder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.book_item, parent, false);
bookViewHolder = new BookViewHolder();
bookViewHolder.itemBookName = (TextView) convertView.findViewById(R.id.item_book_name);
bookViewHolder.itemBookAuthor = (TextView) convertView.findViewById(R.id.item_book_author);
bookViewHolder.itemBookTag = (TextView) convertView.findViewById(R.id.item_book_tag);
convertView.setTag(bookViewHolder);
} else {
bookViewHolder = (BookViewHolder) convertView.getTag();
}
bookViewHolder.itemBookName.setText(mBookList.get(position).getBook_name());
bookViewHolder.itemBookAuthor.setText(mBookList.get(position).getBook_author());
bookViewHolder.itemBookTag.setText(mBookList.get(position).getBook_tag());
return convertView;
}
public class BookViewHolder {
private TextView itemBookName;
private TextView itemBookAuthor;
private TextView itemBookTag;
}
}
到这里,View 所需要的工作我们都已经实现了,下面我们来看 Presenter
Presenter
上面讲过,Presenter 是 View 跟 Model 之间的桥梁,那它要做的工作是什么,需要有哪些方法呢?
这里主要看功能有什么操作,比如,上面的运行图是通过按钮去显示书籍列表,那么这里我们需要一个方法用于 Presenter 去跟 Model 拿数据
interface Presenter extends BasePresenter{
void loadBooks();
}
public class BooksPresenter implements BooksContract.Presenter {
private BooksRepository mBooksRepository;
private BooksContract.View mBookView;
public BooksPresenter(@NonNull BooksRepository booksRepository, @NonNull BooksContract.View bookView) {
mBooksRepository = booksRepository;
mBookView = bookView;
mBookView.setPresenter(this);
}
@Override
public void start() {
}
@Override
public void loadBooks() {
mBooksRepository.getBooks(new BooksDataSource.LoadBooksCallback() {
@Override
public void loadBooks(List bookList) {
mBookView.showBookList(bookList);
}
@Override
public void dataNotAvailable() {
mBookView.showNoBooks();
}
});
}
}
Presenter 要完成二者之间的交互,必须实现它们,从上面的代码看,得到按钮点击的通知,也就是loadBooks()
方法,去跟 Model 拿数据,交由 BooksRepository 去处理数据的业务逻辑,最后通过 mBookView 去通知,View 进行对应的视图显示,交互。
源码的解析到这一步,就比较清晰了,更多 MVP 相关的 demo 可以去看官方的 demo
官方demo
博主的开源项目