一個簡單的MVP模式案例

orzangleli發表於2018-04-13

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>
複製程式碼

一個簡單的MVP模式案例

我們設計書本的資料模型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());
    }
}
複製程式碼

現在就可以看到效果了。

一個簡單的MVP模式案例

3.若干思考

  1. MVP與MVC有什麼區別?有本質區別麼?
  2. 案例中P層和M層為什麼要設計成介面?

4. 後續

本文中的Demo原始碼和思考答案將存在於我的微信公眾號中,獲取原始碼(Source)請回復"S2",獲取答案(Answer)請回復"A2"。另外歡迎大家關注我的微信公眾號~麼麼麼

一個簡單的MVP模式案例

相關文章