美文网首页
Android中MVC、MVP、MVVM

Android中MVC、MVP、MVVM

作者: TianFB | 来源:发表于2019-07-15 23:17 被阅读0次

首先我们要知道MVC、MVP、MVVM是架构模式,和我们说的设计模式不是一个概念。我们使用架构模式时有可能会使用到设计模式。

一、MVC

MVC是Modle-View-Controller的简写。
我们先看一下MVC的模型图。


mvc.png

接下来我们说一下MVC各层的含义。
VIew(V):与用户的交互和数据的展示。
Controller(C):用于业务逻辑的处理
Model(M):用于数据的操作。
下面我们以一个简单的登录业务说明一下。


activity_main.xml
MainActivity.kt
LogInfo.kt
LogModel.kt
CallBack.kt

上面的登录代码就是通过MVC模式完成的,现在我们来根据类来区分一下。
数据层(M):数据类LogInfo.kt、LoginModel.kt ,通过CallBack通知View层。
UI层(V):布局资源文件activity_main.xml、MainActivity.kt也担当了一部分
控制层(C):MainActiviry.kt
MVC优点:实现了一定程度的解耦
MVC缺点:M与V并没有实现真正的解耦,业务逻辑复杂时Acitivty会变的十分庞大不便于维护。

二、MVP

MVP是Modle-View-Presenter的简写


mvp.png

MVP是MVC的升级版,它实现了M与V的真正分离。我看一下MVP各层的含义
View(V):用户的交互和数据的展示
Presenter(P):代替Controller进行业务逻辑的处理
Modle(M):用于数据的处理
下面我们还是使用登录的业务场景进行演示。


activity_main.xml
MainActivity_mvp.kt
LoginPresenter.kt
LoginModel.kt
LogInfo.kt

上面就是一个最简单的MVP模式。
activity_main.xml、MainActivity_mvp.kt属于View层
LoginPresenter.kt属于Presenter层
LoginModel.kt、LogInfo.kt属于Model层
View层中持有Presenter的引用,Model层持有Presenter的引用,而Presenter分别持有View、Model的引用。
MVP最大的优点就是他将Activity的划分到了VIew层。使用Presenter替换了Controller的职能。从而达到了View和Model的解耦。
上面的例子只是简单的说明MVP各层的关系,并不能用于实际开发。我们实际开发过程中都是面向接口进行编程的。下面提供一个可用于开发使用的例子。我们还是以登录为例。



先看base包中的代码
  /**
 * 请求数据返回的基类,用于限定范型的上限
 */
public class BaseEntry {
    private String code;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    private String message;
}
/**
 * MVP中View的基类
 * 为了避免Activity的泄漏,我们在生命周期方法中进行绑定和解绑的操作。
 * 子类实现是指定具体的Presenter和Contract
 *
 * @param <P>        子类要绑定的Presenter
 * @param <CONTRACT> 子类要实现的合约,即该子类有哪些被Presenter回掉方法
 */
public abstract class BaseView<P extends BasePresenter, CONTRACT> extends AppCompatActivity {
    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = getPresenter();
        mPresenter.bindView(this);
    }

    public abstract P getPresenter();

    public abstract CONTRACT getContract();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.unBindView();
    }
}
/**
 * MVP中Presenter的基类
 * 使用弱引用的方式来处理View也就是activity,避免activity的泄漏
 *
 * @param <V>        子类要绑定的具体的View
 * @param <M>        子类中要绑定的具体的Model
 * @param <CONTRACT> 子类要实现的具体的Contract
 */
public abstract class BasePresenter<V extends BaseView, M extends BaseModel, CONTRACT> {
    protected M mModel;
    private WeakReference<V> mWeakReferenceView;

    public BasePresenter() {
        mModel = getModel();
    }

    public abstract M getModel();

    public abstract CONTRACT getContract();

    public void bindView(V v) {
        mWeakReferenceView = new WeakReference(v);
    }

    public void unBindView() {
        if (null != mWeakReferenceView) {
            mWeakReferenceView.clear();
            mWeakReferenceView = null;
            System.gc();
        }
    }

    public V getView() {
        if (null != mWeakReferenceView) {
            return mWeakReferenceView.get();
        }
        return null;
    }
}
/**
 * MVP中Model的基类
 * 主要值责就是根据契约进行操作,并回调Presenter
 *
 * @param <P>        MVP中BasePresenter的子类
 * @param <CONTRACT> 要执行的契约
 */
public abstract class BaseModel<P extends BasePresenter, CONTRACT> {
    protected P mPresenter;

    public BaseModel(P p) {
        mPresenter = p;
    }
    
    public abstract CONTRACT getContract();
}

接下来看一下bean包

/**
 * 继承自BaseBean的数据类javaBean
 */
data class LogInfo(var userName: String, var passWord: String, var isLogin: Boolean) : BaseEntry()

在就是contract包

/**
 * 登录功能中的MVP各层的合约
 */
public interface Login_Contract {
    /**
     * V层的合约
     *
     * @param <T>
     */
    interface LoginView<T extends BaseEntry> {
        void loginCall(T t);
    }

    /**
     * P层的合约
     *
     * @param <T>
     */
    interface LoginPresenter<T extends BaseEntry> {
        void login(String user, String password);

        void loginCall(T t);
    }

    /**
     * M层的合约
     */
    interface LoginModel {
        void login(String user, String password);
    }
}

接下来看一下MVP各层的登录合约的子类

public class LoginActivity extends BaseView<LoginPresenter, Login_Contract.LoginView> {
    private Button mLogin;
    private EditText mUserName;
    private EditText mPassWord;
    private Context mContext;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        setContentView(R.layout.activity_main);
        mLogin = findViewById(R.id.login);
        mUserName = findViewById(R.id.userName);
        mPassWord = findViewById(R.id.passWord);
        mLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userName = mUserName.getText().toString();
                if (TextUtils.isEmpty(userName)) {
                    return;
                }
                String passWord = mPassWord.getText().toString();
                if (TextUtils.isEmpty(passWord)) {
                    return;
                }
                mPresenter.getContract().login(userName, passWord);
            }
        });
    }

    @Override
    public LoginPresenter getPresenter() {
        return new LoginPresenter();
    }

    @Override
    public Login_Contract.LoginView getContract() {
        return new Login_Contract.LoginView<LogInfo>() {
            @Override
            public void loginCall(LogInfo logInfo) {
                if (logInfo.isLogin()) {
                    Toast.makeText(mContext, "登录成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(mContext, "登录失败", Toast.LENGTH_SHORT).show();
                }
            }
        };
    }
}

public class LoginPresenter extends BasePresenter<LoginActivity, LoginModel, Login_Contract.LoginPresenter> {


    @Override
    public LoginModel getModel() {
        return new LoginModel(this);
    }

    @Override
    public Login_Contract.LoginPresenter getContract() {
        return new Login_Contract.LoginPresenter<LogInfo>() {
            @Override
            public void login(String userName, String passWord) {
                mModel.getContract().login(userName, passWord);
            }

            @Override
            public void loginCall(LogInfo logInfo) {
                getView().getContract().loginCall(logInfo);
            }
        };
    }

}

public class LoginModel extends BaseModel<LoginPresenter, Login_Contract.LoginModel> {

    public LoginModel(LoginPresenter loginPresenter) {
        super(loginPresenter);
    }

    @Override
    public Login_Contract.LoginModel getContract() {
        return new Login_Contract.LoginModel() {
            @Override
            public void login(String userName, String passWord) {
                LogInfo logInfo = new LogInfo(userName, passWord, false);
                if (passWord.equals("123")) {
                    logInfo.setLogin(true);
                }
                mPresenter.getContract().loginCall(logInfo);
            }
        };
    }
}

以上就是实际开发中MVP的使用了。MVP是一种思想实际开发中会有很多的表现形式,上面的只能说是其中的一种。比如说Contract契约中的功能可以根据一个界面来进行封装,也可以根据不同的功能进行封装。根据不同功能封装的话就会出现MVP中一层对多层的情况,这时候我们可以考虑使用外观模式来处理。


图片.png

三、MVVM与DataBinding

我们先说一下MVVM和DataBinding的关系。MVVM是Model-View-ViewModel的简写,它与MVC、MVP一样是一种架构思想。而DataBinding是谷歌推出了方便实现MVVM的一种工具。


mvvm

MVVM也是分为三个层次
Model(M):数据类
View(V):用户交互与数据展示
ViewModel(VM):处理业务逻辑
接下来我们还是用一个简单的例子演示一下。
使用DataBinding时在build.gradle中添加下面的代码。

android {
   ...
    dataBinding{
        enabled true
    }
}

MVVM中布局文件与我们普通的布局文件有所不同,他的最外层使用的是layout标签,并且还有一个data标签,variable标签中的name属性相当于声明了一个全局变量,它指向的是type中声明的值(全类名)也就是我们的ViewModel。我们可以使用@{}来绑定控件的事件,也可以使用@{}绑定View中显示的数据。使用@{}绑定数据时是单向绑定,Model中的数据会同步到View上。使用@={}就是双向绑定View和Model的变化是同步的。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 定义该布局需要绑定的数据名称和类型 -->
    <data>
        <variable
                name="loginViewModel"
                type="com.scjd.framemodel.mvvm.LoginViewModel"/>
    </data>
    <!-- 下部分内容和平时布局文件一样 -->
    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="20dp"
                android:addTextChangedListener="@{loginViewModel.mUserNameWatcher}"
                android:hint="请输入账户"
                android:text="@={loginViewModel.mUserInfo.userName}"/>
        <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="20dp"
                android:addTextChangedListener="@{loginViewModel.mPassWordWatcher}"
                android:hint="请输入密码"
                android:text="@={loginViewModel.mUserInfo.passWord}"/>
        <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:onClick="@{loginViewModel.loginClickListener}"
                android:text="登录"/>
    </LinearLayout>
</layout>

接下来看一下对应的Activity,就是进行了布局的绑定。DataBindingUtil.setContentView()方法会创建一个以布局文件名开头(去除特殊字符并遵循驼峰式命名)Binding结尾的java文件。

public class MvvmActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        new LoginViewModel(binding);
    }
}

ViewModle中只需要通过binding.setLoginViewModel(this);进行绑定即可,setXXX的方法名称跟Xml文件中name属性的值有关。

public class LoginViewModel {
    public UserInfo mUserInfo;

    public LoginViewModel(ActivityMvvmBinding binding) {
        mUserInfo = new UserInfo();
        binding.setLoginViewModel(this);
    }

    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //修改Model中的数据 View层也会同步变化
            mUserInfo.userName.set("xixi");
            mUserInfo.passWord.set("123");
        }
    };

    public TextWatcher mUserNameWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            //双向绑定时 修改View层中的数据,Model也会同步变化
            Log.e("TAG----账号发生了变化", "ed:" + s + "/mUserInfo.userName:" + mUserInfo.userName.get());
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    };
    public TextWatcher mPassWordWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            //双向绑定时 修改View层中的数据,Model也会同步变化
            Log.e("TAG----密码发生了变化", "ed:" + s + "/mUserInfo.passWord:" + mUserInfo.passWord.get());
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    };

}

实体类的定义与我们平常的定义不同,它使用ObservableField声明变量,并使用范型的形式来指定变量类型。ObservableField会自动生成get和set方法。

public class UserInfo {
    public ObservableField<String> userName = new ObservableField<>();
    public ObservableField<String> passWord = new ObservableField<>();
}

上面就是对MVVM的简单实用了。

四、总结

  1. MVC、MVP、MVVM最大的相同点就是针对Activity(Fragment)进行了解耦。
  2. 在MVC中由于Activity职责的不纯粹(既属于View又属于Controller)导致Model与View层没有达到真正的解耦。
  3. MVP使各层中类的职责变得单一,但是由于各层的通讯问题会增加很多的回掉方法或是回调接口。
  4. MVVM它和MVP一样都是各层的职责变的单一并且它的双向绑定也避免了数据回调的问题,但是DataBinding使用了apt的技术,他会生成相应的文件,有可能增加apk体积,同时DataBinding也会出现内存增大的问题。

DataBinding原理浅析

该文档是自己的学习记录如有错误欢迎指出。

相关文章

网友评论

      本文标题:Android中MVC、MVP、MVVM

      本文链接:https://www.haomeiwen.com/subject/lfhckctx.html