使用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下载点击打开链接