1. 問題背景
首先需要清楚的一點是MVP模式的設計初衷是:為了解決在MVC模式中,過於複雜的邏輯和介面之間的互動中Activity的職責不單一的問題,Activity既充當了View層,又充當了Controller層的角色。刨除問題的複雜度,直接談MVP模式的優越性,都是耍流氓。
這也就是為什麼我們很多人,為什麼不願意學習MVP的原因。但是如果遇到了一個比較複雜的問題,MVP的解耦能夠讓你更加輕鬆地應對需求的迭代。
本文將一個案例來解釋MVP模式的設計方法,但是這裡有一個矛盾點:MVP模式本身應該作用於較複雜問題的,但是本文作為入門文章又必須使用一個較簡單的場景去設計,這樣才能容易看出MVP的結構。
場景描述如下:
APP中有一本書(Model),書本的價格會顯示在Activity(View)中,Activity中有兩個按鈕,可以對書本的價格進行控制(Presenter)。
2. MVP模式的實現
基於上面提出的場景,我們先簡單的設計介面:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.orzangleli.mvpdemo.MainActivity">
<TextView
android:id="@+id/desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="16dp"
android:gravity="center_vertical"
android:padding="5dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/increase"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="漲價1元"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/decrease"
app:layout_constraintTop_toBottomOf="@id/desc"
android:layout_marginTop="60dp"
/>
<Button
android:id="@+id/decrease"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="降價1元"
app:layout_constraintLeft_toRightOf="@id/increase"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/increase"
/>
</android.support.constraint.ConstraintLayout>
複製程式碼
我們設計書本的資料模型BookVo
。
public class BookVo {
private String name;
private int price;
private String author;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("BookVo{");
sb.append("name='").append(name).append('\'');
sb.append(", price=").append(price);
sb.append(", author='").append(author).append('\'');
sb.append('}');
return sb.toString();
}
}
複製程式碼
Activity只負責顯示書本的資訊,不參與漲價/降價的邏輯處理,所以我們需要建立一個Presenter類,把價格邏輯交給他處理,處理完之後再在Activity中顯示。
Presenter類應該具有漲價/降價的能力,因此我們把Presenter設計成介面更加合理。IPresenter.java
的內容如下:
public interface IPresenter {
void increasePrice();
void decreasePrice();
}
複製程式碼
再設計一個PresenterImpl
類繼承IPresenter
介面,並實現漲價和降價的兩個方法。因為需要在Presenter中操作Model,並在View中顯示,所以Presenter
需要持有Model和View的物件。Model物件很簡單,直接將BookVo
傳給Presenter
即可,但是View物件如何處理呢?
我們制定一個IView
介面,向Presenter
暴露我們能夠提供的能力,比如這個場景裡的顯示書籍資訊,於是IView
介面內容如下:
public interface IView {
void showBookInfo(BookVo vo);
}
複製程式碼
我們讓MainActivity
實現IView
介面,
@Override
public void showBookInfo(BookVo vo) {
this.mDescTv.setText(vo.toString());
}
複製程式碼
現在我們只需要給Presenter新增一個構造方法就行,構造方法中新增兩個引數:BookVo和IView物件。PresenterImpl.java
程式碼如下:
public class PresenterImpl implements IPresenter {
private IView mIView;
private BookVo mBookVo;
public PresenterImpl(IView iView, BookVo vo) {
this.mIView = iView;
this.mBookVo = vo;
}
@Override
public void increasePrice() {
Log.i("lxc", " ---> 漲價了一元");
mBookVo.setPrice(mBookVo.getPrice() + 1);
this.mIView.showBookInfo(mBookVo);
}
@Override
public void decreasePrice() {
Log.i("lxc", " ---> 降價了一元");
mBookVo.setPrice(mBookVo.getPrice() - 1);
this.mIView.showBookInfo(mBookVo);
}
}
複製程式碼
MainActivity
程式碼如下:
public class MainActivity extends AppCompatActivity implements IView{
private TextView mDescTv;
private Button mIncreaseBtn, mDecreaseBtn;
private IPresenter mPresenter;
private BookVo vo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initPresenter();
mDescTv = findViewById(R.id.desc);
mIncreaseBtn = findViewById(R.id.increase);
mDecreaseBtn = findViewById(R.id.decrease);
mIncreaseBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.increasePrice();
}
});
mDecreaseBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.decreasePrice();
}
});
mDescTv.setText(vo.toString());
}
private void initData() {
vo = new BookVo();
vo.setName("《百年孤獨》");
vo.setAuthor("泰戈爾");
vo.setPrice(100);
}
private void initPresenter() {
mPresenter = new PresenterImpl(this, vo);
}
@Override
public void showBookInfo(BookVo vo) {
this.mDescTv.setText(vo.toString());
}
}
複製程式碼
現在就可以看到效果了。
3.若干思考
- MVP與MVC有什麼區別?有本質區別麼?
- 案例中P層和M層為什麼要設計成介面?
4. 後續
本文中的Demo原始碼和思考答案將存在於我的微信公眾號中,獲取原始碼(Source)請回復"S2",獲取答案(Answer)請回復"A2"。另外歡迎大家關注我的微信公眾號~麼麼麼