一个简单的MVP模式案例

3,003 阅读3分钟

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.若干思考

  1. MVP与MVC有什么区别?有本质区别么?
  2. 案例中P层和M层为什么要设计成接口?

4. 后续

本文中的Demo源码和思考答案将存在于我的微信公众号中,获取源码(Source)请回复"S2",获取答案(Answer)请回复"A2"。另外欢迎大家关注我的微信公众号~么么么