談談 Android MVP 架構

JewelSecres 7年前發布 | 15K 次閱讀 安卓開發 Android開發 移動開發

MVP 架構簡介

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

什么是 MVP 架構

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

MVP 架構圖

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

MVP 架構的利弊

優點

  1. 耦合度低

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

  2. 可維護性高

    每個 View 都有其對應的 Presenter,容易進行區分,哪個模塊出現了問題,或者接口出現了問題,可以迅速的確定。模型與視圖之間完全分離,修改視圖不影響模型

  3. 方便單元測試

    因其業務邏輯都在 Presenter 里,進行單元測試的時候,可以直接寫個測試接口,由 Presenter 去繼承

缺點

  1. 類數量暴漲

    每個 View 都有 Presenter ,跟其對應的接口,類的數量會明顯變多,在某些場景下 Presenter 的復用會產生接口冗余。

  2. 額外的學習曲線

    需要花費額外的時間去學習,學習理解成本高,開始編寫代碼之前需要時間成本(項目的架構)

實戰演練

前面講述了一堆的理論知識,下面一步步解剖 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;
}

}</code></pre>

創建一個接口,用于獲取回調實體類 Book 攜帶的數據

public interface BooksDataSource {

interface LoadBooksCallback{
    void loadBooks(List<Book> bookList);
    void dataNotAvailable();
}

void getBooks(@NonNull LoadBooksCallback loadBooksCallback);

}</code></pre>

繼承 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);

}

}</code></pre>

數據回調業務處理,網絡數據和本地數據(這里僅模擬本地數據)

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();
        }
    });
}

}</code></pre>

到這里,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);

}

}</code></pre>

從上面的代碼看 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;
}

}</code></pre>

到這里,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();
        }
    });
}

}</code></pre>

Presenter 要完成二者之間的交互,必須實現它們,從上面的代碼看,得到按鈕點擊的通知,也就是 loadBooks() 方法,去跟 Model 拿數據,交由 BooksRepository 去處理數據的業務邏輯,最后通過 mBookView 去通知,View 進行對應的視圖顯示,交互。

源碼的解析到這一步,就比較清晰了,更多 MVP 相關的 demo 可以去看官方的 demo

 

來自:https://juejin.im/post/590065a0570c350058fb8d4a

 

 本文由用戶 JewelSecres 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!