MVP与MVVM模式

最近公司项目重构,主管要求使用耳熟能详的MVP模式,优点是代码复用性高,易于调试维护。觉得很有必要记录一下自己的学习心得。

先看效果图:
MVP与MVVM模式
很简单的布局与逻辑,点击按钮以后,模拟网络加载数据,两秒过后,将加载的数据显示。(截屏略卡)传统的MVC模式实现很简单,今天尝试用MVP模式怎么去实现。

Model:依然是业务逻辑和实体模型,用于数据的增删改查等,也包括一些数据对象
View:负责View的绘制以及与用户交互,用于界面的显示与用户操作的接收,在Android里面View通常就是Actvitiy,Fragment。
Presenter:负责完成View与Model之间的交互,接收View的请求后,从Model获取数据交给View。

与传统的MVC模式不同,在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。

接下来就是具体的应用了:

(1) StringView(接口),用于定义我们当前Activity(View)的行为,Activity会实现这个接口。接口里定义的方法最常见的一种情况就是:请求数据成功,返回result;请求数据失败,返回message

public interface StringView {
     void ShowStringSuccess(String result);
     void ShowStringFail(String message);
}

(2) OnStringListener(接口),数据返回之后的回调,回调给Presenter层

public interface OnStringListener {
    //成功时回调
    void OnSuccess(String result);
    //失败时回调
    void OnFail(String message);
}

(3) StringPresenter(类),定义着Activity(View层)与Presenter层交互的方法,并且处理Model层返回数据

/**
 * Created by tangyangkai on 16/4/11.
 * Presenter作为中间层,持有View和Model的引用,既对model层的数据进行处理,同事又控制View层的展示
 */
public class StringPresenter implements OnStringListener {
    private StringView stringview;
    private StringModel stringmodel;
    private Context context;


    public StringPresenter(Context context,StringView stringview) {
        this.stringview = stringview;
        this.context=context;
        stringmodel = new StringModel();
    }

    //对view层提供方法,调用model层请求数据
    public void SetUrl(String url) {
        stringmodel.load(context,url, this);
    }


    //model层的回调,将返回的数据传递给view层
    @Override
    public void OnSuccess(String result) {
    stringview.ShowStringSuccess(result);
    }

    @Override
    public void OnFail(String message) {
    stringview.ShowStringFail(message);
    }
}

(4) StringModel(类) 实现我们的数据请求

/**
 * Created by tangyangkai on 16/4/11.
 * 模拟请求数据,具体实战根据返回结果处理
 */
public class StringModel {
    private OnStringListener listener;
    private Context context;
    public void load(Context context, String url, OnStringListener listener) {
        this.listener = listener;
        this.context = context;
        new MyAsyncTask().execute();

    }
    // 异步任务
    private class MyAsyncTask extends android.os.AsyncTask<Void, Integer, Void> {
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();

            Toast.makeText(context, "正在加载数据,请等待...", Toast.LENGTH_SHORT).show();
        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            // TODO Auto-generated method stub
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            // 进行数据加载完成后的UI操作
            listener.OnSuccess("模拟数据请求成功");
            //listener.OnFail("模拟数据请求失败");
        }
    }
}

(5)最后就是Activity(View)的代码处理

public class MainActivity extends AppCompatActivity implements StringView {
    private TextView txt;
    private Button btn;
    private StringPresenter stringPresenter;
    private Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        //初始化presenter
        stringPresenter = new StringPresenter(context,this);
        initviews();
    }
    private void initviews() {
        txt = (TextView) findViewById(R.id.txt);
        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stringPresenter.SetUrl("实际应用传入一个URL请求网络数据即可");

            }
        });
    }
    @Override
    public void ShowStringSuccess(String result) {
        txt.setText(result);
    Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
    }
    @Override
    public void ShowStringFail(String message) {

Toast.makeText(getApplicationContext(),message,Toast.LENGTH_SHORT).show();
    }


}

至此,使用MVP模式实现了一个简单的业务逻辑。MVP模式层级分级明显,高度可复用,耦合度低,很适合今后代码的维护与更新。

之后主管让我去了解MVC,MVP ,MVVM,Data Binding相关的知识,于是便有了这篇博客的下文。参考资料:
选择恐惧症的福音!教你认清MVC,MVP和MVVM
完全掌握Android Data Binding
这里很感谢这两篇文章的理论与技术支持,感谢这些大神。

OK,开始今天的主题。
关于MVP模式与MVVM模式孰是孰非,我想不是我一个初学者短短几句就能够说清楚的,我们也不用纠结于此。引用大神的话“真正的最佳实践都是人想出来的”。我们为何不结合一下MVP和MVVM的特点:MVP+Data Binding,依旧使用presenter去做和model层的通信,同时使用data binding去轻松的bind data。

Data Binding—-2015年的Google IO 大会上,Android 团队发布的一个 数据绑定框架。以后可以直接在 layout 布局 xml 文件中绑定数据,无需再通过findViewById或者注解框架去设置数据。

具体看看MVP+Data Binding在项目中的应用:
MVP与MVVM模式
点击button,请求加载网络数据,两秒过后,将模拟的数据显示在textView中。

1.准备工作
新建一个 Project,确保 Android 的 Gradle 插件版本不低于 1.5.0-alpha1:

classpath 'com.android.tools.build:gradle:1.5.0'

然后修改对应模块(Module)的 build.gradle:

dataBinding {
    enabled true
}

2.数据对象
这里的数据对象有两种情况,一种是普通的数据对象,一种是绑定的数据对象,能够自动更新数据。

先看第一种:

/**
 * Created by tangyangkai on 16/4/27.
 */
public class User{
    private String firstName;
    private String lastName;


    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

一个简单的User,两个属性以及它的 getter 和 setter。

3.布局文件

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user"
            type="com.example.tangyangkai.myapplication.User"></variable>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="getWebMsg"
            android:text="模拟请求网络数据" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="姓氏:" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="名字:" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}" />
    </LinearLayout>
</layout>

(1.)data节点:使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data。data 节点的作用就像一个桥梁, 把数据(Model)与 UI(View) 进行绑定,搭建了 View 和 Model 之间的通路。
(2.)申明variable:

<data>
<variable
      name="user"
      type="com.example.tangyangkai.myapplication.User">            </variable>
</data>

在 xml 布局文件的 data 节点中声明一个 variable,这个变量会为 UI 元素提供数据(例如 TextView 的 android:text),然后在 Java 代码中把『后台』获取的数据与这个 variable 进行绑定。其中 type 属性就是我们在 Java 文件中定义的 User 类。
(3.)使用variable:

            <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}" />

数据与 Variable 绑定之后,xml 的 UI 元素就可以直接使用了。

4.Activity实现

public class FiveActivity extends AppCompatActivity implements StringView {

    private StringPresenter mStringPresenter;
    ActivityFiveBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mStringPresenter = new StringPresenter(this);
        binding = DataBindingUtil.setContentView(
                this, R.layout.activity_five);

    }


    //请求网络数据
    public void getWebMsg(View view) {
        mStringPresenter.SetUrl();
    }

    @Override
    public void ShowString(String firstName, String lastName) {
        User user = new User();
        user.setFirstName(firstName);
        user.setLastName(lastName);
        binding.setUser(user);

    }
}

(1.)ActivityFiveBinding是框架自动生成的, 其中的set 方法也是根据 variable 名称而生成的。
(2.)修改 FiveActivity 的 onCreate 方法,用 DatabindingUtil.setContentView() 来替换掉 setContentView()
(3.)创建一个 User 对象,将从网络获取的数据通过set方法传递给User,通过 binding.setUser(user) 与 variable 进行绑定。

至此,使用第一种数据对象的data binding流程已经过了一遍,其实核心就是申明variable,绑定variable,使用variable。第二种数据对象也一样,只不过是绑定variable时候有些不一样。

5.第二种数据类型

/**
 * Created by tangyangkai on 16/4/27.
 */
public class User extends BaseObservable{
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    @Bindable
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

(1.)要实现 数据对象的绑定,Android 原生提供了已经封装好的一个类 - -BaseObservable,并且实现了监听器的注册机制,我们只需要继承 BaseObservable。
(2.)BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中自动生成一个 entry。
(3.)当数据发生变化时会调用 notifyPropertyChanged(BR.firstName) 方法,通知系统 BR.firstName 这个 entry 的数据已经发生变化,再去更新 UI。

public class FiveActivity extends AppCompatActivity implements StringView {

    private StringPresenter mStringPresenter;
    User user = new User();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mStringPresenter = new StringPresenter(this);
        ActivityFiveBinding binding = DataBindingUtil.setContentView(
                this, R.layout.activity_five);
        binding.setUser(user);

    }


    //请求网络数据
    public void getWebMsg(View view) {
        mStringPresenter.SetUrl();
    }

    @Override
    public void ShowString(String firstName, String lastName) {
        user.setFirstName(firstName);
        user.setLastName(lastName);
    }
}

与第一种数据对象不同的是,variable的数据绑定会在初始化的时候就完成。获取网络数据成功以后,只要直接传递给user即可,数据以及UI的更新会在内部进行。

6.MVP的代码

Presenter层

/**
 * Created by tangyangkai on 16/4/11.
 * Presenter作为中间层,持有View和Model的引用,对model层的数据进行处理,控制View层的展示
 */
public class StringPresenter implements StringModel.getMsgListener {
    private StringView stringview;
    private StringModel stringmodel;


    public StringPresenter(StringView stringview) {
        this.stringview = stringview;
        stringmodel = new StringModel();
    }

    //对view层提供请求数据方法
    public void SetUrl() {
        stringmodel.getWebMsg(this);
    }

    //将model层返回的数据传递给view
    @Override
    public void getMsgSuccess(String firstName, String lastName) {
        stringview.ShowString(firstName, lastName);
    }
}

Model层


/**
 * Created by tangyangkai on 16/4/11.
 * 模拟请求数据,具体实战根据返回结果处理
 */
public class StringModel {

    public interface getMsgListener {
        void getMsgSuccess(String firstName, String lastName);
    }

    public void getWebMsg(getMsgListener listener) {
        //模拟网络数据请求
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        listener.getMsgSuccess("tang", "yangkai");
    }

}

关于Data Binding的高级用法以及注意事项,那两篇文章介绍的很详细,大家可以细细品尝。

关于这些模式与框架,仁者见仁,智者见智,其实都是为了尽量降低程序的耦合性和提高代码的复用性。以上是记录自己最近学习这些框架模式的一些心得,有不当之处欢迎大家指出,一起进步。

上一篇:两种方式实现购物车动画


下一篇:基于Material Design设计的分享文字图片的APP