標籤:
使用MVC或者MVP模式會增加很多的類,但是確可以讓代碼結構變得清晰,方便了後期維護拓展方便。把資料層跟視圖層分離,處理事務的邏輯單獨的放在一個類中,讓Activity僅僅具有展示功能。
下面我們就MVC模式跟MVP模式進行分別講解,總之來說各有利弊。在實際的開發中,我們根據實際情況進行取捨。個人認為MVP模式更簡單一些,因為MVP模式中會把部分邏輯Activity中,但是這就造成了Activity的相對繁瑣,沒有實現完全的隔離。而我們採用的MVC模式則是更好的處理了這個問題,但是在應用的過程中,部分邏輯可能比較繞。
下面我們通過一個使用者登入這個例子來分別理解這兩種模式。
首先來講解MVC模式:
這裡說將對MVC模式不是傳統的MVC模式,感謝極光推送IM提供的demo,這個demo所採用的就是一種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; }}
現在我們就使用已經定義好的viewgroup來進行布局,只是一個簡單的登入視窗,一個使用者名稱,一個密碼,還有一個登入按鍵:
<?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下載點擊開啟連結
淺談Android中的MVC與MVP模式