使用MVC或者MVP模式会增加很多的类,但是确可以让代码结构变得清晰,方便了后期维护拓展方便。把数据层跟视图层分离,处理事务的逻辑单独的放在一个类中,让Activity仅仅具有展示功能。
下面我们就MVC模式跟MVP模式进行分别讲解,总之来说各有利弊。在实际的开发中,我们根据实际情况进行取舍。个人认为MVP模式更简单一些,因为MVP模式中会把部分逻辑Activity中,但是这就造成了Activity的相对繁琐,没有实现完全的隔离。而我们采用的MVC模式则是更好的处理了这个问题,但是在应用的过程中,部分逻辑可能比较绕。
下面我们通过一个用户登录这个例子来分别理解这两种模式。
首先来讲解MVC模式:
这里说将对MVC模式不是传统的MVC模式,在使用Fragment的嵌套到Activity中的时候,其实就是进行碎片化,把逻辑视图全部交给Fragment,然而不幸的是Fragment的生命周期过于繁琐,导致在使用的过程中会遇到各种各样的坑,让人头疼。Square公司也发现了这个问题,于是有了本文所讲的MVC模式,当你有了足够的编程经验,这套体系模式就会自动在你脑海中生成。具体的实现思路是将model层,view层,controller层分离,而Activity只是用来加载视图使用,初始化控件交给view层,对数据的获取交给model层,对数据的处理交给controller层,我们的Activity是异常的清爽!
首先我们来设计view层,自定义一个view,继承自我们需要的viewgroup,LinearLayout或者FrameLayout等。我们自定义的目的是为了下一步实例化控件,从而不必在Activity中实例化了。这里我们只是简单的继承,通常不需要自己多加逻辑,所以不要慌。代码如下:
public class LoginView extends RelativeLayout{
private Context mContext;
/**
* 因为我们是在布局文件中使用,所以只需要重写这个构造方法就可以了
* @param context
* @param attrs
*/
public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
}
<?xml version="1.0" encoding="utf-8"?>
<com.baiyyyhjl.mode.mvc.view.LoginView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="用户名"
android:padding="10dp" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/et_username"
android:hint="密码"
android:padding="10dp" />
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/et_password"
android:padding="10dp"
android:text="登录" />
</com.baiyyyhjl.mode.mvc.view.LoginView>
现在我们在LoginView中添加控件的初始化的方法。
public void initModule(){
mUsername = (EditText) findViewById(R.id.et_username);
mPassword = (EditText) findViewById(R.id.et_password);
mLoginBtn = (Button) findViewById(R.id.btn_login);
}
获取到EditText中的内容,以及对mLoginBtn点击事件的监听,这里我们可以添加更多关于view的操作,一切根据实际需求来:
public String getUserName(){
return mUsername.getText().toString().trim();
}
public String getPassword(){
return mPassword.getText().toString().trim();
}
public void setListeners(OnClickListener onClickListener){
mLoginBtn.setOnClickListener(onClickListener);
}
public void userNameError(Context context){
Toast.makeText(context, "用户名不能为空", Toast.LENGTH_SHORT).show();
}
public void passWordError(Context context){
Toast.makeText(context, "密码不能为空", Toast.LENGTH_SHORT).show();
}
public void loginSuccess(Context context){
Toast.makeText(context, "登录成功啦!", Toast.LENGTH_SHORT).show();
}
public void loginFailure(Context context){
Toast.makeText(context, "登录失败。", Toast.LENGTH_SHORT).show();
}
}
好了,一个view层已经完成,下面来说model层,主要是对数据的请求,比如获取数据库中的数据,网络请求等:
这里我们假定进行网路请求,一个LoginModel,有一个返回结果的回调接口,具体模拟代码如下:
<pre style="font-family: 宋体; font-size: 15pt; background-color: rgb(255, 255, 255);"><pre name="code" class="java">public class LoginModel implements ILoginModel {
@Override
public void login(final String username, final String password, final ResultCallBack callBack) {
// 模拟子线程的耗时操作,例如网路请求,这里我们使用异步请求
new MyAsyncTask(callBack).execute(username, password);
}
private class MyAsyncTask extends AsyncTask<String, Void, Boolean>{
ResultCallBack mCallBack;
public MyAsyncTask(ResultCallBack callBack){
this.mCallBack = callBack;
}
@Override
protected Boolean doInBackground(String... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (params[0].equals("hjl") && params[1].equals("123456")){
return true;
} else {
return false;
}
}
@Override
protected void onPostExecute(Boolean aBoolean) {
if (aBoolean){
mCallBack.success();
} else {
mCallBack.failure();
}
}
}
}
最后实现controller层:
public class LoginController implements View.OnClickListener{
private LoginView mLoginView;
private MVCLoginActivity mContext;
private ILoginModel mLoginModel;
public LoginController(LoginView loginView, MVCLoginActivity context){
this.mLoginView = loginView;
this.mContext = context;
mLoginModel = new LoginModel();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login:
final String username = mLoginView.getUserName();
final String password = mLoginView.getPassword();
if (TextUtils.isEmpty(username)){
mLoginView.userNameError(mContext);
break;
}
if (TextUtils.isEmpty(password)){
mLoginView.passWordError(mContext);
break;
}
// 调用model层进行网络请求
mLoginModel.login(username, password, new ResultCallBack() {
@Override
public void success() {
mLoginView.loginSuccess(mContext);
mContext.finish();
}
@Override
public void failure() {
mLoginView.loginFailure(mContext);
}
});
break;
}
}
}
这样MVC框架就完成了,在Activity中展示:
public class MVCLoginActivity extends AppCompatActivity {
private LoginView mLoginView = null;
private LoginController mLoginController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mvc_activity_login);
// 控件的绑定
mLoginView = (LoginView) findViewById(R.id.login_view);
mLoginView.initModule();
mLoginController = new LoginController(mLoginView, this);
// 事件的监听
mLoginView.setListeners(mLoginController);
}
}
一个相对复杂的登录操作,在我们的Activity中只有简单的几行代码,具体的逻辑全都交给MVC,这里我们的思想是Activity不属于view层,而只供展示以及一些简单逻辑的处理,我们把MVC层全部单独为几个类,让Activity中仅仅几行代码,而且整体的代码结构相当的清晰!总结:对于控件的绑定等相关操作逻辑我们写在view层中,对于数据的获取(读取数据库,获取网络数据)我们写在model中,对于代码的所有逻辑(点击事件的处理,相关事件)等我们写在controller层中。
下一项我们来介绍MVP模式,个人感觉这个模式更好理解,弊端在文章开头已经说了。
之前看过大神分析过MVP模式,其实我第一次接触这种模式是我刚工作不久,公司项目里用到。当时因为基础不大好,总感觉超级麻烦,又是这么多类,这么多接口的,直接写在Activity中多省事。后来代码写的多一点了,就发现如果全都写在Activity中维护起来相当困难,有时候代码量多了,可能自己都看不懂自己写的代码了,也渐渐理解了设计模式的好处。
我们还是以这个登录需求为例,用MVP模式写一遍,体会一下。model层还是进行实体模型和业务逻辑,view层这里我们直接使用Activity,就是绑定控件等操作放在Activity中,不再多加一个类,presenter层负责处理model和view之间的交互。
首先还是写view层接口ILoginView,这里我们需要把所有需要的条件考虑到:
public interface ILoginView {
String getUsername();
String getPassword();
void userError(Context context);
void passwordError(Context context);
void loginSuccess(Context context);
void loginFailure(Context context);
}
接下来是model层,模拟网络请求
public class LoginModel implements ILoginModel {
@Override
public void login(final String username, final String password, final ResultCallBack callBack) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (username.equals("hjl") && password.equals("123456")) {
callBack.success();
} else {
callBack.failure();
}
}
}).start();
}
}
最后在Activity中实现:
public class MVPLoginActivity extends AppCompatActivity implements View.OnClickListener, ILoginView {
private EditText mUsername;
private EditText mPassword;
private Button mLoginBtn;
private LoginPresenter mLoginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mvc_activity_login);
mLoginPresenter = new LoginPresenter(this);
initView();
}
private void initView() {
mUsername = (EditText) findViewById(R.id.et_username);
mPassword = (EditText) findViewById(R.id.et_password);
mLoginBtn = (Button) findViewById(R.id.btn_login);
mLoginBtn.setOnClickListener(this);
}
@Override
public String getUsername() {
return mUsername.getText().toString().trim();
}
@Override
public String getPassword() {
return mPassword.getText().toString().trim();
}
@Override
public void loginSuccess() {
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();
finish();
}
@Override
public void loginFailure() {
Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
if (TextUtils.isEmpty(getUsername())) {
Toast.makeText(this, "用户名不能为空", Toast.LENGTH_SHORT).show();
break;
}
if (TextUtils.isEmpty(getPassword())) {
Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
break;
}
mLoginPresenter.login();
break;
}
}
}
以上就是android中的两种设计模式,不知道自己写的是否标准,但是感觉这样写已经达到了代码结构的清晰。其中MVC是借鉴极光推送im的demo,从中学习,而且感觉他写的这种方法是在很棒。第二种是项目中用到的,也很简单清晰。
以上所说的demo下载点击打开链接