1. MVC、MVP、MVVM
1.1 MVC
- Model 模型层: 业务模型的数据与行为=数据+业务逻辑
- View 展示层: 管理用户界面=组合模式的View集合
- Controller: Model与View的桥梁,用于控制程序流程, 确保View可以访问和显示Model的数据。Android中为生命周期和事件机制收发,例如Activity、Fragment
- 优点:开创性提出了View与Model分离
- 缺点:存在View和Controller没有彻底解耦和Controller承担了过重的非本职任务
- 解耦原理:通过Activity实现View和Model的解耦
1.2 MVP
- Model 模型层: 业务模型的数据与行为=数据+业务逻辑
- View 展示层: 管理用户界面=组合模式的View集合
- Presenter:负责View和Model的沟通即具体业务逻辑
- 优点:完全解耦
- 缺点:项目越复杂,接口定义越多,导致开发难度新增
- 解耦原理:通过接口编程的方式实现View和Model分离
1.3 MVVM
- Model 模型层: 业务模型的数据与行为=数据+业务逻辑
- View 展示层: 管理用户界面=组合模式的View集合
- ViewModel: 业务逻辑与数据,ViewModel与View绑定之后,ViewModel变化会通知View更新变化
- 优点:MV完全解耦,适合大型项目
- 缺点:需要依赖Jetpack实现
- 解耦原理:Jetpack框架实现,implementation ‘android.arch.lifecycle:extensions:1.1.1’
2. MVC、MVP、MVVM Demo
用3种写法实现:登录界面功能
2.1 Model和View层
2.1.1 Model
package com.sufadi.study.mvx;
import android.util.Log;
/**
* Model层
* Model 模型层: 业务模型的数据与行为=数据+业务逻辑
*/
public class ModelUser {
String name;
String pwd;
public ModelUser() {
Log.d("ModelUser", "Model 模型层实例化");
}
String getInfo(String name) {
if ("fadi.su".equals(name)) {
return "从数据库查询到,该用户永远18岁";
} else {
return "保密";
}
}
}
2.1.2 View
简单的登录界面:2个输入框分别输入用户名和密码和1个登录按钮 setContentView(R.layout.login_layout);
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title_tv"
android:text="MVC demo"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:text="用户名称:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/name_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="fadi.su"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:text="用户密码:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/pwd_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="123"/>
</LinearLayout>
<Button
android:id="@+id/login_btn"
android:text="登录"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
2.2 MVC
- Model 模型层: 业务模型的数据与行为=数据+业务逻辑
- View 展示层: 管理用户界面=组合模式的View集合
- Controller: Model与View的桥梁,用于控制程序流程, 确保View可以访问和显示Model的数据。Android中为生命周期和事件机制收发,例如Activity、Fragment
- 优点:开创性提出了View与Model分离
- 缺点:存在View和Controller没有彻底解耦和Controller承担了过重的非本职任务
代码执行逻辑为:
2022-02-11 15:35:59.896 21467-21467/com.sufadi.study D/MVC: MVC
Model 模型层: 业务模型的数据与行为=数据+业务逻辑
View 展示层: 管理用户界面=组合模式的View集合
Controller: Model与View的桥梁,用于控制程序流程, 确保View可以访问和显示Model的数据。Android中为生命周期和事件机制收发,例如Activity、Fragment
优点:开创性提出了View与Model分离
缺点:存在View和Controller没有彻底解耦和Controller承担了过重的非本职任务
2022-02-11 15:35:59.896 21467-21467/com.sufadi.study D/MVC: Controller 层:onCreate生命周期中findViewById()存在Controller层耦合了View层
2022-02-11 15:36:04.909 21467-21467/com.sufadi.study D/MVC: Controller 层:Activity主业是生命周期和事件机制外发,兼职验证用户名合法性
2022-02-11 15:36:04.909 21467-21467/com.sufadi.study D/MVC: Controller 层:Activity主业是生命周期和事件机制外发,兼职验证密码合法性
2022-02-11 15:36:04.910 21467-21467/com.sufadi.study D/MVC: Controller 层:Activity主业是生命周期和事件机制外发,兼职验证用户名密码是否正确
2022-02-11 15:36:04.910 21467-21467/com.sufadi.study D/ModelUser: Model 模型层实例化
2022-02-11 15:36:04.910 21467-21467/com.sufadi.study D/MVC: View 展示层:弹出一个对话框,提示登录成功:fadi.su, 从数据库查询到,该用户永远18岁
源码
MVCActivity.java
package com.sufadi.study.mvx;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.sufadi.study.R;
/**
* MVC
*
* adb shell am start -n com.sufadi.study/com.sufadi.study.mvx.MVCActivity
*/
public class MVCActivity extends Activity {
public static String TAG = "MVC";
// Controller 耦合了 View
TextView title_tv;
EditText name_et, pwd_et;
Button login_btn;
private static final String info = "\nMVC\n" +
"Model 模型层: 业务模型的数据与行为=数据+业务逻辑\n" +
"View 展示层: 管理用户界面=组合模式的View集合\n" +
"Controller: Model与View的桥梁,用于控制程序流程, 确保View可以访问和显示Model的数据。Android中为生命周期和事件机制收发,例如Activity、Fragment\n" +
"优点:开创性提出了View与Model分离\n" +
"缺点:存在View和Controller没有彻底解耦和Controller承担了过重的非本职任务\n";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_layout);
Log.d(TAG, info);
initView();
initValue();
initListener();
}
/**
* Controller 耦合了 View
*/
private void initView() {
Log.d(TAG, "Controller 层:onCreate生命周期中findViewById()存在Controller层耦合了View层");
title_tv = findViewById(R.id.title_tv);
name_et = findViewById(R.id.name_et);
pwd_et = findViewById(R.id.pwd_et);
login_btn = findViewById(R.id.login_btn);
}
private void initValue() {
title_tv.setText(info);
}
private void initListener() {
login_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Controller做了View的工作
Log.d(TAG, "Controller 层:Activity主业是生命周期和事件机制外发,兼职验证用户名合法性");
String name = name_et.getText().toString();
if ("".equals(name)) {
Log.d(TAG, "View 展示层:弹出一个对话框,提示用户名不能为空");
return;
}
// Controller做了View的工作
Log.d(TAG, "Controller 层:Activity主业是生命周期和事件机制外发,兼职验证密码合法性");
String pwd = pwd_et.getText().toString();
if ("".equals(pwd)) {
Log.d(TAG, "View 展示层:弹出一个对话框,提示密码不能为空");
return;
}
verifyLogin(name, pwd, new LoginCallBack() { // Controller 工作:验证用户名密码是否正确
@Override
public void success(ModelUser user) {
// Controller做了View的工作
Log.d(TAG, "View 展示层:弹出一个对话框,提示登录成功:" + user.name + ", " + user.getInfo(user.name));
}
@Override
public void fail() {
// Controller做了View的工作
Log.d(TAG, "View 展示层:弹出一个对话框,提示登录失败");
}
});
}
});
}
private interface LoginCallBack{
void success(ModelUser user);
void fail();
}
/**
* Controller 工作:验证用户名密码是否正确
*/
private void verifyLogin(String name, String pwd, LoginCallBack callBack) {
Log.d(TAG, "Controller 层:Activity主业是生命周期和事件机制外发,兼职验证用户名密码是否正确");
if ("fadi.su".equals(name) && "123".equals(pwd)) {
ModelUser mModelUser = new ModelUser();
mModelUser.name = name;
callBack.success(mModelUser);
} else {
callBack.fail();
}
}
}
2.3 MVP
- Model 模型层: 业务模型的数据与行为=数据+业务逻辑
- View 展示层: 管理用户界面=组合模式的View集合
- Presenter:负责View和Model的沟通即具体业务逻辑
- 优点:完全解耦
- 缺点:项目越复杂,接口定义越多,导致开发难度新增
- 原理:通过接口编程的方式实现View和Model分离
2022-02-11 16:03:49.597 23295-23295/com.sufadi.study D/MVP: Model、View、Presenter都有各自接口定义各自的行为方法。通过接口编程达到View和Model的解耦。
2022-02-11 16:03:52.869 23295-23295/com.sufadi.study D/Presenter: Presenter 层:执行用户名和密码的合法性和正确性流程
2022-02-11 16:03:52.869 23295-23295/com.sufadi.study D/ModelUser: Model 模型层实例化
2022-02-11 16:03:52.869 23295-23295/com.sufadi.study D/Presenter: Presenter 层:回调接口-success
2022-02-11 16:03:52.869 23295-23295/com.sufadi.study D/MVP: View 展示层:弹出一个对话框,提示登录成功:fadi.su, 从数据库查询到,该用户永远18岁
源码
MVPActivity.java
package com.sufadi.study.mvx;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.sufadi.study.R;
/**
* MVP
* Model 模型层: 业务模型的数据与行为=数据+业务逻辑
* View 展示层: 管理用户界面=组合模式的View集合
* Presenter:负责View和Model的沟通即具体业务逻辑
* 优点:完全解耦
* 缺点:项目越复杂,接口定义越多,导致开发难度新增
* 通过接口编程的方式实现View和Model分离。
*
* adb shell am start -n com.sufadi.study/com.sufadi.study.mvx.MVPActivity
*/
public class MVPActivity extends Activity implements IViewLogin {
public static String TAG = "MVP";
TextView title_tv;
EditText name_et, pwd_et;
Button login_btn;
PresenterLogin presenterLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Model、View、Presenter都有各自接口定义各自的行为方法。通过接口编程达到View和Model的解耦。");
setContentView(R.layout.login_layout);
initView();
initValue();
initListener();
}
private void initView() {
title_tv = findViewById(R.id.title_tv);
name_et = findViewById(R.id.name_et);
pwd_et = findViewById(R.id.pwd_et);
login_btn = findViewById(R.id.login_btn);
}
private void initValue() {
title_tv.setText(TAG);
presenterLogin = new PresenterLogin(this);
}
private void initListener() {
login_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenterLogin.login(name_et.getText().toString(), pwd_et.getText().toString());
}
});
}
@Override
public void success(ModelUser user) {
Log.d(TAG, "View 展示层:弹出一个对话框,提示登录成功:" + user.name + ", " + user.getInfo(user.name));
}
@Override
public void fail() {
Log.d(TAG, "View 展示层:弹出一个对话框,提示登录失败");
}
@Override
public void invalidName() {
Log.d(TAG, "View 展示层:弹出一个对话框,提示用户名不能为空");
}
@Override
public void invalidPwd() {
Log.d(TAG, "View 展示层:弹出一个对话框,提示密码不能为空");
}
}
PresenterLogin.java
package com.sufadi.study.mvx;
import android.util.Log;
public class PresenterLogin {
private IViewLogin viewLogin;
public PresenterLogin(IViewLogin _viewLogin) {
this.viewLogin = _viewLogin;
}
public void login(String name, String pwd) {
Log.d("Presenter", "Presenter 层:执行用户名和密码的合法性和正确性流程");
if ("".equals(name)) {
Log.d("Presenter", "Presenter 层:回调接口-invalidName");
viewLogin.invalidName();
return;
}
if ("".equals(pwd)) {
Log.d("Presenter", "Presenter 层:回调接口-invalidPwd");
viewLogin.invalidPwd();
return;
}
if ("fadi.su".equals(name) && "123".equals(pwd)) {
ModelUser mModelUser = new ModelUser();
mModelUser.name = name;
Log.d("Presenter", "Presenter 层:回调接口-success");
viewLogin.success(mModelUser);
} else {
Log.d("Presenter", "Presenter 层:回调接口-fail");
viewLogin.fail();
}
}
}
IViewLogin.java
package com.sufadi.study.mvx;
/**
* 通过接口编程的方式实现View和Model分离
*
* IView: 定义了哪些情况需要更新View
*/
public interface IViewLogin {
void success(ModelUser name);
void fail();
void invalidName();
void invalidPwd();
}
2.4 MVVM
- Model 模型层: 业务模型的数据与行为=数据+业务逻辑
- View 展示层: 管理用户界面=组合模式的View集合
- ViewModel: 业务逻辑与数据,ViewModel与View绑定之后,ViewModel变化会通知View更新变化
- 优点:MV完全解耦,适合大型项目
- 缺点:需要依赖Jetpack实现
- 解耦原理:Jetpack框架实现,implementation ‘android.arch.lifecycle:extensions:1.1.1’
2022-02-11 16:28:58.967 24637-24637/com.sufadi.study D/MVVM: MVVM
Model 模型层: 业务模型的数据与行为=数据+业务逻辑
View 展示层: 管理用户界面=组合模式的View集合
ViewModel: 业务逻辑与数据,ViewModel与View绑定之后,ViewModel变化会通知View更新变化
优点:MV完全解耦,适合大型项目
缺点:需要依赖Jetpack实现
解耦原理:Jetpack框架实现,implementation 'android.arch.lifecycle:extensions:1.1.1'
2022-02-11 16:28:59.272 24637-24637/com.sufadi.study D/ViewRootImpl[MVVMActivity]: reportDrawFinished
2022-02-11 16:29:02.705 24637-24637/com.sufadi.study D/ViewModel: ViewModel 层:执行用户名和密码的合法性和正确性流程
2022-02-11 16:29:02.705 24637-24637/com.sufadi.study D/ModelUser: Model 模型层实例化
2022-02-11 16:29:02.705 24637-24637/com.sufadi.study D/ViewModel: ViewModel 层:回调接口-success
2022-02-11 16:29:02.712 24637-24637/com.sufadi.study D/MVVM: View 展示层:弹出一个对话框,提示登录成功:fadi.su, 从数据库查询到,该用户永远18岁
源码
package com.sufadi.study.mvx;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.sufadi.study.R;
/**
* MVVM
* Model 模型层: 业务模型的数据与行为=数据+业务逻辑
* View 展示层: 管理用户界面=组合模式的View集合
* ViewModel: 业务逻辑与数据,ViewModel与View绑定之后,ViewModel变化会通知View更新变化
* 优点:MV完全解耦,适合大型项目
* 缺点:需要依赖Jetpack实现
* 解耦原理:Jetpack框架实现,implementation 'android.arch.lifecycle:extensions:1.1.1'
*
* adb shell am start -n com.sufadi.study/com.sufadi.study.mvx.MVVMActivity
*/
public class MVVMActivity extends AppCompatActivity {
public static String TAG = "MVVM";
TextView title_tv;
EditText name_et, pwd_et;
Button login_btn;
private ViewModelLogin viewModelLogin;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "\nMVVM\n" +
"Model 模型层: 业务模型的数据与行为=数据+业务逻辑\n" +
"View 展示层: 管理用户界面=组合模式的View集合\n" +
"ViewModel: 业务逻辑与数据,ViewModel与View绑定之后,ViewModel变化会通知View更新变化\n" +
"优点:MV完全解耦,适合大型项目\n" +
"缺点:需要依赖Jetpack实现\n" +
"解耦原理:Jetpack框架实现,implementation 'android.arch.lifecycle:extensions:1.1.1'");
setContentView(R.layout.login_layout);
initView();
initValue();
initListener();
}
private void initView() {
title_tv = findViewById(R.id.title_tv);
name_et = findViewById(R.id.name_et);
pwd_et = findViewById(R.id.pwd_et);
login_btn = findViewById(R.id.login_btn);
}
private void initValue() {
title_tv.setText(TAG);
viewModelLogin = ViewModelProviders.of(this).get(ViewModelLogin.class);
}
private void initListener() {
login_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
viewModelLogin.login(name_et.getText().toString(), pwd_et.getText().toString());
}
});
viewModelLogin.loginStatue.observe(this, new Observer<String>() {
@Override
public void onChanged(String statue) {
if ("invalidName".equals(statue)) {
Log.d(TAG, "View 展示层:弹出一个对话框,提示用户名不能为空");
} else if ("invalidPwd".equals(statue)) {
Log.d(TAG, "View 展示层:弹出一个对话框,提示密码不能为空");
} else if("success".equals(statue)) {
ModelUser user = viewModelLogin.userMutableLiveData.getValue();
Log.d(TAG, "View 展示层:弹出一个对话框,提示登录成功:" + user.name + ", " + user.getInfo(user.name));
} else {
Log.d(TAG, "View 展示层:弹出一个对话框,提示登录失败");
}
}
});
}
}
ViewModelLogin.java
package com.sufadi.study.mvx;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
/**
* Jetpack 提供ViewModel组件用于实现界面(Activity或Fragment)和数据分离
*/
public class ViewModelLogin extends ViewModel {
public MutableLiveData<String> loginStatue = new MutableLiveData<>();
public MutableLiveData<ModelUser> userMutableLiveData = new MutableLiveData<>();
public void login(String name, String pwd) {
Log.d("ViewModel", "ViewModel 层:执行用户名和密码的合法性和正确性流程");
if ("".equals(name)) {
Log.d("ViewModel", "ViewModel 层:回调接口-invalidName");
loginStatue.postValue("invalidName");
return;
}
if ("".equals(pwd)) {
Log.d("ViewModel", "ViewModel 层:回调接口-invalidPwd");
loginStatue.postValue("invalidPwd");
return;
}
if ("fadi.su".equals(name) && "123".equals(pwd)) {
ModelUser user = new ModelUser();
user.name = name;
userMutableLiveData.setValue(user);
Log.d("ViewModel", "ViewModel 层:回调接口-success");
loginStatue.postValue("success");
} else {
Log.d("ViewModel", "ViewModel 层:回调接口-fail");
loginStatue.postValue("fail");
}
}
}
|