談談 Android MVP 架構 | 掘金技術徵文

前言:本文所寫的是博主的我的看法,若有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文連接demo連接java

MVP 架構簡介

提及 MVP 架構,相信不少朋友都看過,網上也有不少這方面的資料。博主使用 MVP 架構搭建項目也有一段時間了。簡單談一談心得。說到 MVP 架構,不少人都拿它跟 MVC 去對比。這裏我就不過多重複說了,單刀直入。android

什麼是 MVP 架構

MVP 架構由 Model(模型)、View(視圖)、Presenter(主持者)構成,下面咱們一塊兒來了解它們:
git

MVP 架構圖

  • Model 負責業務邏輯以及數據的處理,主要經過接口實現
  • View 負責 UI 顯示以及與用戶之間的交互
  • Presenter 起到一個銜接橋樑的做用,負責 Model 跟 View 之間的交互

MVP 架構的利弊

優勢

  1. 耦合度低
    View 跟 Model 之間由 Presenter 負責二者之間的交互,低了其耦合度,使其更加關注自身邏輯,結構清晰github

  2. 可維護性高
    每一個 View 都有其對應的 Presenter,容易進行區分,哪一個模塊出現了問題,或者接口出現了問題,能夠迅速的肯定。模型與視圖之間徹底分離,修改視圖不影響模型編程

  3. 方便單元測試
    因其業務邏輯都在 Presenter 裏,進行單元測試的時候,能夠直接寫個測試接口,由 Presenter 去繼承網絡

缺點

  1. 類數量暴漲
    每一個 View 都有 Presenter ,跟其對應的接口,類的數量會明顯變多,在某些場景下 Presenter 的複用會產生接口冗餘。架構

  2. 額外的學習曲線
    須要花費額外的時間去學習,學習理解成本高,開始編寫代碼以前須要時間成本(項目的架構)ide

實戰演練

前面講述了一堆的理論知識,下面一步步解剖 MVP 架構,下圖是 Demo 的目錄結構(看起來比較複雜,勿怪),實現模擬網絡獲取圖書數據並將其顯示的功能單元測試

MVP Demo 目錄圖

下面咱們來看項目實現效果,功能比較簡單,gif圖就不弄了
demo 運行圖1
demo 運行圖2

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

相關文章
相關標籤/搜索