前言:本文所寫的是博主的個人見解,如有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文連結,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<Book> 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<Book> 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<Book> bookList) {
loadBooksCallback.loadBooks(bookList);
}
@Override
public void dataNotAvailable() {
loadBooksCallback.dataNotAvailable();
}
});
}
}複製程式碼
到這裡,Model的任務算是結束了,得到了所需要的資料來源。
View
Presenter 與 View 是通過介面進行互動,所以這裡可以定義一個介面,用於進行互動,此處的難點在於,要清楚需要哪些方法。
這裡建立兩個Base基類,用於初始化
public interface BaseView<T> {
void setPresenter(T presenter);
}複製程式碼
public interface BasePresenter {
void start();
}複製程式碼
從上面的demo執行演示圖看,這裡 View 的工作主要有2個,一個是顯示無資料時的狀態,第二個是顯示書籍的列表
void showBookList(List<Book> 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<Book> 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<Book> mBookList;
private Context mContext;
private LayoutInflater inflater;
public BooksAdapter(Context context, List<Book> 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<Book> bookList) {
mBookView.showBookList(bookList);
}
@Override
public void dataNotAvailable() {
mBookView.showNoBooks();
}
});
}
}複製程式碼
Presenter 要完成二者之間的互動,必須實現它們,從上面的程式碼看,得到按鈕點選的通知,也就是loadBooks()
方法,去跟 Model 拿資料,交由 BooksRepository 去處理資料的業務邏輯,最後通過 mBookView 去通知,View 進行對應的檢視顯示,互動。
原始碼的解析到這一步,就比較清晰了,更多 MVP 相關的 demo 可以去看官方的 demo