前言:本文所寫的是博主的我的看法,若有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文連接,demo連接java
提及 MVP 架構,相信不少朋友都看過,網上也有不少這方面的資料。博主使用 MVP 架構搭建項目也有一段時間了。簡單談一談心得。說到 MVP 架構,不少人都拿它跟 MVC 去對比。這裏我就不過多重複說了,單刀直入。android
MVP 架構由 Model(模型)、View(視圖)、Presenter(主持者)構成,下面咱們一塊兒來了解它們:
git
耦合度低
View 跟 Model 之間由 Presenter 負責二者之間的交互,低了其耦合度,使其更加關注自身邏輯,結構清晰github
可維護性高
每一個 View 都有其對應的 Presenter,容易進行區分,哪一個模塊出現了問題,或者接口出現了問題,能夠迅速的肯定。模型與視圖之間徹底分離,修改視圖不影響模型編程
方便單元測試
因其業務邏輯都在 Presenter 裏,進行單元測試的時候,能夠直接寫個測試接口,由 Presenter 去繼承網絡
類數量暴漲
每一個 View 都有 Presenter ,跟其對應的接口,類的數量會明顯變多,在某些場景下 Presenter 的複用會產生接口冗餘。架構
額外的學習曲線
須要花費額外的時間去學習,學習理解成本高,開始編寫代碼以前須要時間成本(項目的架構)ide
前面講述了一堆的理論知識,下面一步步解剖 MVP 架構,下圖是 Demo 的目錄結構(看起來比較複雜,勿怪),實現模擬網絡獲取圖書數據並將其顯示的功能單元測試
建立實體類 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的任務算是結束了,獲得了所須要的數據源。
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 的方法定義上,須要觀察功能上的操做,接着考慮:
通過上面的思考後,接下來就是 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 是 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