1.三个基本架构
①MVC(Model-View-Controller)
Model:代表数据模型,管理数据状态。
View:视图,即呈现给用户的UI,包括布局文件及Activity。
Controller:控制者,负责处理用户与app之间的交互,包含业务逻辑。是Model与View的桥梁,用来控制程序的流程,比如Activity/Fragment。
缺点:View与Model之间还存在依赖关系,在View中容易直接操作Model。Controller很‘重’很复杂,Activity既是View又是Controller,有时候一个Activity会有成千甚至上万行代码,很复杂。
②MVP(Model-View-Presenter)
Model:代表数据模型,包括数据和一些业务逻辑,即数据的结构定义、数据的存储和获取等。
View:视图,即呈现给用户的UI,并且负责与客户进行交互。比如XML/Activity/Fragment。
Presenter:主持者,Presenter通过View接收用户的输入,然后在Model的帮助下处理用户的数据并将结果传递回View。Presenter通过接口与View进行通信。接口在presenter类中定义,它传递所需的数据。Activity/Fragment及其他View视图组件实现此接口获得他们想要的数据并呈现数据。
优点:Presenter和View层之间通过定义接口实现通信,将View与Model解耦,方便进行单元测试。
缺点:业务场景比较复杂时,接口会定义很多,使Presenter依旧很‘重’很复杂。Presenter如果持有Activity等的引用,容易出现内存泄露问题。
③MVVM(Model-View-ViewModel)
Model:代表数据模型,包括数据和一些业务逻辑,即数据的结构定义、数据的存储和获取等。
View:视图,即呈现给用户的UI,并且负责与客户进行交互。比如XML/Activity/Fragment。
ViewModel:解决了MVP的问题,使ViewModel和View之间不再依赖接口通信,而是通过LiveData、RxJava、Flow等响应式开发的方式来通信,即将数据以可观察对象的形式提供给View,View和ViewModel层分离,ViewModel不应该知道与之交互的View是什么。
在MVVM中View持有ViewModel,但ViewModel得不到任何关于View的信息。所以View与ViewModel之间存在着一对多的关系,一个View可以持有多个ViewModel。MVVM是MVP模式的一个优化。
2.三个架构模式举例
分别用MVC、MVP、MVVM模式实现一个用户登录的功能。
①MVC
(1)Controller层:
public class MvcLoginActivity extends AppCompatActivity {
private EditText userNameEt;
private EditText passwordEt;
private User user; //view层依赖了Model层
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.activity_login);
user = new User();
userNameEt = findViewById(R.id.name);
passwordEt = findViewById(R.id.pwd);
Button loginBtn = findViewById(R.id.lgin);
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
login( userNameEt.getText().toString(), passwordEt.getText().toString());
}
});
}
private void login(String userName, String password) {
if (userName.equals("admin") && password.equals("123")) { //业务逻辑
user.setUserName(userName);
user.setPassword(password);
Toast.makeText(this, userName + " Login Successful", Toast.LENGTH_SHORT ).show(); //UI更新
} else {
Toast.makeText(this, "Login Failed", Toast.LENGTH_SHORT).show();
}
}
}
可以看到,所有的数据处理逻辑和界面更新都写在了Controller层里,耦合严重,不方便单元测试。
MVC一般的交互流程是:
1)用户操作View, 比如说产生了一个点击事件。
2)Controller接收事件,对其作出反应。比如说是点击登录事件,它会校验用户输入是否为空,若为空则直接返回View让其提示用户;若不为空则请求Model层。
3)Model作出处理后,需要把登录用户的数据通知到相关成员,这里是View层。View收到后作出相关展示。
②MVP
(1)Model层:处理业务逻辑
public interface IUserBiz { //业务逻辑接口
boolean login(String userName, String password);
}
//业务逻辑实现
public class UserBiz implements IUserBiz {
@Override
public boolean login(String userName, String password) {
if (userName.equals("admin") && password.equals("123")) {
User user = new User();
user.setUserName(userName);
user.setPassword(password);
return true;
}
return false;
}
}
(2)Presenter层:
public class LoginPresenter{
private UserBiz userBiz; //持有Model
private IMvpLoginView iMvpLoginView; //持有View引用
public LoginPresenter(IMvpLoginView iMvpLoginView) {
this.iMvpLoginView = iMvpLoginView;
this.userBiz = new UserBiz();
}
public void login() {
//1.从view获取数据
String userName = iMvpLoginView.getUserName();
String password = iMvpLoginView.getPassword();
//2.调用Model处理业务逻辑
boolean isLoginSuccessful = userBiz.login(userName, password);
//3.根据结果,使用接口的方式更新view
iMvpLoginView.onLoginResult( isLoginSuccessful);
}
}
(3)View层:
public interface IMvpLoginView {
String getUserName();
String getPassword();
void onLoginResult(Boolean isLoginSuccess);
}
public class MvpLoginActivity extends AppCompatActivity implements IMvpLoginView{
private EditText userNameEt;
private EditText passwordEt;
private LoginPresenter loginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
userNameEt = findViewById(R.id.name);
passwordEt = findViewById(R.id.pwd);
Button loginBtn = findViewById(R.id.logi);
loginPresenter = new LoginPresenter( this); //view中持有presenter
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
loginPresenter.login(); //需要处理业务逻辑时,调用presenter去处理(不在view里处理业务逻辑)
}
});
}
//实现view定义的接口,供presenter调用
@Override
public String getUserName() {
return userNameEt.getText().toString();
}
@Override
public String getPassword() {
return passwordEt.getText().toString();
}
@Override
public void onLoginResult(Boolean isLoginSuccess) {
if (isLoginSuccess) {
Toast.makeText(this, getUserName() + " Login Successful", Toast.LENGTH_SHORT ).show();
} else {
Toast.makeText(this, "Login Failed", Toast.LENGTH_SHORT).show();
}
}
}
业务逻辑全部在Model里,view中用户操作时如果需要处理业务逻辑,就通过presenter调用Model里的方法去处理。业务逻辑处理完成后,处理结果在presenter里调用view里的方法去更新view显示。
MVP的交互流程是:
1)用户操作View层,产生了一个事件;
2)Presenter接收事件,并对其作出反应,请求Model层;
3)Model层作出处理后通知给Presenter,Presenter进而再通知到View层。
③MVVM
(1)ViewModel层:
public class LoginViewModel extends ViewModel {
private User user;
private MutableLiveData<Boolean> isLoginSuccessfulLD; //该变量表示login业务处理结果,用该结果去驱动界面更新,所以这里使用了LiveData
public LoginViewModel() {
this.isLoginSuccessfulLD = new MutableLiveData<>();
user = new User();
}
public MutableLiveData<Boolean> getIsLoginSuccessfulLD() {
return isLoginSuccessfulLD;
}
public void setIsLoginSuccessfulLD(boolean isLoginSuccessful) {
isLoginSuccessfulLD.postValue( isLoginSuccessful);
}
public void login(String userName, String password) { // 暴露login方法,处理业务逻辑
if (userName.equals("admin") && password.equals("123")) {
user.setUserName(userName);
user.setPassword(password);
setIsLoginSuccessfulLD(true);
} else {
setIsLoginSuccessfulLD(false);
}
}
public String getUserName() {
return user.getUserName();
}
}
(2)View层:
public class MvvmLoginActivity extends AppCompatActivity {
private LoginViewModel loginVM;
private EditText userNameEt;
private EditText passwordEt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
userNameEt = findViewById(R.id.name);
passwordEt = findViewById(R.id.pwd);
Button loginBtn = findViewById(R.id.logi);
loginBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
loginVM.login( userNameEt.getText().toString(), passwordEt.getText().toString()); //需要处理业务逻辑时,调用vm去处理(不在view里处理业务逻辑)
}
});
loginVM = ViewModelProviders.of( this).get(LoginViewModel.class);
//用ViewModel里的数据驱动界面更新
loginVM.getIsLoginSuccessfulLD().observ e(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean isLoginSuccessFul) {
if (isLoginSuccessFul) {
Toast.makeText(this, loginVM.getUserName() + " Login Successful", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Login Failed", Toast.LENGTH_SHORT).show();
}
}
});
}
界面中用户操作时,如果需要处理业务逻辑,就调用ViewModel的方法去处理,处理结果保存在ViewModel中的LiveData里,然后activity通过observe监听该LiveData的数据变化从而自动更新界面。
注意:
我们知道Model层里包括了一些业务逻辑和业务数据模型,而ViewModel层即是视图模型(Model of View),其内是视图的表示数据和逻辑。比如说Model层的业务数据是1, 2, 3, 4, 而翻译到View层,则可能是表示A, B, C, D了。ViewModel除了做这个事情外,还会封装视图的行为动作,如点击某个控件后的行为等。另外注意这里的ViewModel和Jetpack包里提供的ViewModel组件不是一个东西,这里的ViewModel是一个概念,而Jetpack包则提供了一个比较方便的实现方式。
很多讲MVVM的文章示例都会用DataBinding,然而没有DataBinding照样可以使用MVVM架构,比如说借用LiveData、RxJava、Flow等,这些工具都是基于响应式开发的原理来替代基于接口的通信方式。这里的响应式开发强调一种基于观察者模式的开发方式:View订阅ViewModel暴露的响应式接口,接收到通知后进行相应逻辑,而ViewModel不再持有任何形式的View的引用,减少耦合,提高了可复用性。
另外如果使用LiveData的话, ViewModel对View层仅暴露LiveData接口,在View层不允许直接更新LiveData,因为一旦View层拥有直接更新LiveData的能力,就无法约束View层进行业务处理的行为。
最后说一下Repository,Repository模式主要思想是通过抽象一个Repository层,对业务(领域)层屏蔽不同数据源的访问细节,业务层(可能是 ViewModel)无需关注具体的数据访问细节。
Repository内部实现了对不同数据源(DataSource)的访问,典型的DataSource包括远程数据、Cache缓存、Database数据库等,可以用不同的Fetcher来实现,Repository持有多个Fetcher引用。
因此上面实例中的LoginModel可以换成LoginRepository类, LoginRepository不暴露具体的数据访问方式,只暴露出这一能力的接口。