通常,如果你是一名面向對象的開發者,或多或少都瞭解和接觸過大名鼎鼎的“MVC”模式。
到了Android移動端上,因爲其自身的某些特性。於是,從“MVC”模式裏又衍生出了一種新的模式,既“MVP”模式。
關於其二者的特點,從根本來說,十分相似:
- Controller/Presenter負責接受數據,並命令View或Model做出相應的修改;
- Model負責封裝應用程序的數據模型及業務邏輯。
- View負責應用程序的顯示。
- MVP:View並不直接使用Model,它們之間的通信是通過Presenter(MVC中的Controller)來進行的,所有的交互都發生Presenter內部。
- MVC:允許View跳過Controller,直接對Model進行訪問。
那麼,到了Android開發當中,爲什麼MVP模式會顯得比MVC模式更加合適,我們加以分析。
以傳統的JavaWeb開發而言,其對應於MVC模式來說,通常表現爲:
- Servlet或者Filter作爲Controller;
- JSP作爲View;
- POJO + 封裝BIZ的Action類作爲Model。
我們按照同樣的思想換算到Android當中,則應該表現爲:
- Activity作爲Controller;
- 佈局xml文件作爲View;
- POJO + BIZ作爲Model。
這時,細心的人可能已經注意到了,強行把Activity作爲Controller似乎並不那麼合理。
因爲在Android當中:Activity除了負責接收用戶的輸入之外,還承擔着加載界面佈局,實例化控件,綁定監聽事件等等工作。
你之所以選擇架構模式,根本就是爲了最大程度的對代碼結構進行解耦和模塊分離。
所以,如果出現Activity既像View,又像Controller這種不倫不類的情況,我們自然應該避免其發生。
也正是基於此,所以在衍生出的MVP模式中,選擇這樣做。
- 將Activity作爲View,只負責界面顯示和用戶交互。
- Model依然保持本色。
- 而建立Presenter負責View與Model的交互。
這個週末抽空花了一點時間,嘗試了一下所謂的MVP模式。多多少少有些體會,在此記錄。
首先,既然要使用到MVP模式,自然要了解爲什麼使用它。如果沒有好處,我們用它搞毛呢?
簡單歸納,其好處爲:
- 易於維護
- 易於測試
- 松耦合度
- 複用性高
- 健壯穩定
不過相對於平常來說,經分離之後,類文件,接口文件反而相對卻變多了。
所以個人感覺如果是規模較小,邏輯較爲簡單的項目,其實反而沒有使用的必要。
話入正題,所謂的M-V-P,對應 來說:
M - Model數據模型(業務邏輯層)。
既是主要處理伴隨數據模型的業務邏輯層。
V - View 視圖(表現層)。
更基本的來說,也就是界面,負責顯示數據及與用戶進行交互。
P - Presenter 表示器。
個人認爲可以理解爲一箇中轉站,也就是M與P的連接樞紐。
其行爲表現爲:接收“V”上反饋的指令,分發給“M”進行處理,最後將處理的結果再反饋在“V”上。
知道了較爲書面化的概念,結合一點生活中熟悉的事物,希望能幫助我們更好的進行理解。
我們這樣想象,你開了一家小餐館,那麼對應來說:
V - 餐館門市。
既消費者雙眼所見的,門市既是整個“餐館系統”的視圖。
M - 餐館廚房。
用戶在“V”裏,既是在“餐館”內產生了交互行爲“點餐”,“廚房”負責處理該業務邏輯,最終產生要反饋給用戶的結果“菜餚”。
P - 餐館店小二。
通常來說,食客來到餐館,不會直接開始尋找廚房,然後直接告訴廚師自己所點的菜餚。而廚師也不會在完成菜品之後,再親自上菜。
所以,你需要一個“店小二”,既MVP裏的“P”。這裏你就能十分形象的理解到“表示器P”其充當的角色,既一個“樞紐”的角色。
分析一下:
- 食客點餐完畢。
- 將菜單交至店小二;
- 店小二將菜單送到廚房;
- 廚房根據菜單做好菜餚,通知店小二;
- 店小二將菜餚上至對應餐桌。
對應來說:
- 用戶在android設備上(既“V”)進行了操作;
- “V”接受到交互,將用戶的行爲傳遞給表示器“P”;
- 表示器收到反饋行爲,通知對應的業務邏輯處理器;
- “M”收到,進行處理,將處理得到的結果返回給“P”;
- “P”得到結果,將結果返回給“V”,即反饋給用戶;
public class LoginActivity extends Activity implements ILoginView {
private EditText userName, passWord;
private Button login, clearInput;
private ProgressBar loginProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Init view
initView();
}
private void initView() {
userName = (EditText) this.findViewById(R.id.user_name);
passWord = (EditText) this.findViewById(R.id.password);
login = (Button) this.findViewById(R.id.btn_login);
clearInput = (Button) this.findViewById(R.id.btn_clear);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//need to do..
}
});
clearInput.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//need to do..
}
});
loginProgress = (ProgressBar) this.findViewById(R.id.login_loading);
}
}
}
3.門市的裝修工作終於完成了,我們送了一口氣。這個時候,爲了將來餐館能夠有條不紊的順利營業。此時,也許我們就應該開始着手考慮,當我們的餐館正式開始營業後,可能與消費產生哪些交互行爲?從而定下一些好的營業行爲規範了!
public interface ILoginView {
// 獲取用戶輸入用戶名
String getUserNameInput();
// 獲取用戶輸入密碼
String getPassWordInput();
// 登錄成功,跳轉界面
void toAppMainActivity();
// 登錄失敗,提示用戶
void showLoginFailedInfo();
// 清空輸入記錄
void clearInputValue();
// 提示正在登錄
void showLoginLoading();
// 隱藏正在登錄提示
void hideLoginLoading();
}
public class LoginActivity extends Activity implements ILoginView {
private EditText userName, passWord;
private Button login, clearInput;
private ProgressBar loginProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Init view
initView();
}
private void initView() {
userName = (EditText) this.findViewById(R.id.user_name);
passWord = (EditText) this.findViewById(R.id.password);
login = (Button) this.findViewById(R.id.btn_login);
clearInput = (Button) this.findViewById(R.id.btn_clear);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// need to do..
}
});
clearInput.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// need to do..
}
});
loginProgress = (ProgressBar) this.findViewById(R.id.login_loading);
}
// implements interface method
@Override
public String getUserNameInput() {
return userName.getEditableText().toString();
}
@Override
public String getPassWordInput() {
return passWord.getEditableText().toString();
}
@Override
public void toAppMainActivity() {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
}
@Override
public void showLoginFailedInfo() {
Toast.makeText(this, "Sorry,Plase check your input!", Toast.LENGTH_LONG).show();
}
@Override
public void clearInputValue() {
userName.setText("");
passWord.setText("");
}
@Override
public void showLoginLoading() {
loginProgress.setVisibility(View.VISIBLE);
}
@Override
public void hideLoginLoading() {
loginProgress.setVisibility(View.INVISIBLE);
}
}
5、這個時候對於店鋪的打理已經基本進行完畢了,這個時候我們應該着手考慮一下餐廳的業務邏輯了,例如最關鍵的:找個好廚師~
public class User {
private String userName;
private String passWord;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
數據模型搞定之後,也就該完成針對於此數據模型進行處理的業務邏輯類了。在我們的例子中,只存在一個業務邏輯,既是驗證登錄。首先,我們依舊是首先定義下規範:public interface ILoginModel {
void doLogin(String userName, String passWord, ILoginListener loginListener);
}
接着,根據規範去定義具體的處理辦法:public class LoginModel implements ILoginModel {
@Override
public void doLogin(final String userName, final String passWord, final ILoginListener loginListener) {
new Thread(new Runnable() {
@Override
public void run() {
// 模擬耗時操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 驗證登錄
if (userName.equals("tsr") && passWord.equals("123")) {
User user = new User();
user.setUserName(userName);
user.setPassWord(passWord);
loginListener.loginSuccess(user);
} else {
loginListener.loginFailed();
}
}
}).start();
}
}
一切工作都在有條不紊的進行當中,唯一值得注意的是,在此我們定義了一個監聽接口,用以監聽登錄結果,並根據結果做出對應操作。public interface ILoginListener {
void loginSuccess(User user);
void loginFailed();
}
6、當我們工作至此處時,廚房部門已經被我們打點完畢了。此時,萬事俱備,只欠東風。我們只差一個關鍵的“店小二”了。
public class LoginPresenter {
private ILoginModel loginModel;
private ILoginView loginView;
private Handler mHandler;
public LoginPresenter(ILoginView loginView) {
this.loginModel = new LoginModel();
this.loginView = loginView;
mHandler = new Handler();
}
public void doLogin() {
loginView.showLoginLoading();
loginModel.doLogin(loginView.getUserNameInput(), loginView.getPassWordInput(), new ILoginListener() {
@Override
public void loginSuccess(User user) {
mHandler.post(new Runnable() {
@Override
public void run() {
loginView.hideLoginLoading();
loginView.toAppMainActivity();
}
});
}
@Override
public void loginFailed() {
mHandler.post(new Runnable() {
@Override
public void run() {
loginView.hideLoginLoading();
loginView.showLoginFailedInfo();
}
});
}
});
}
public void clearInputValue() {
loginView.clearInputValue();
}
}
7、到了這裏你的餐廳完全已經做好開始營業的準備了!唯一的工作,記得爲你的“店小二”辦理入職手續。
public class LoginActivity extends Activity implements ILoginView {
private LoginPresenter mPresenter;
private EditText userName, passWord;
private Button login, clearInput;
private ProgressBar loginProgress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Init presenter
mPresenter = new LoginPresenter(this);
// Init view
initView();
}
private void initView() {
userName = (EditText) this.findViewById(R.id.user_name);
passWord = (EditText) this.findViewById(R.id.password);
login = (Button) this.findViewById(R.id.btn_login);
clearInput = (Button) this.findViewById(R.id.btn_clear);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.doLogin();
}
});
clearInput.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.clearInputValue();
}
});
loginProgress = (ProgressBar) this.findViewById(R.id.login_loading);
}
// implements interface method
@Override
public String getUserNameInput() {
return userName.getEditableText().toString();
}
@Override
public String getPassWordInput() {
return passWord.getEditableText().toString();
}
@Override
public void toAppMainActivity() {
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
}
@Override
public void showLoginFailedInfo() {
Toast.makeText(this, "Sorry,Plase check your input!", Toast.LENGTH_LONG).show();
}
@Override
public void clearInputValue() {
userName.setText("");
passWord.setText("");
}
@Override
public void showLoginLoading() {
loginProgress.setVisibility(View.VISIBLE);
}
@Override
public void hideLoginLoading() {
loginProgress.setVisibility(View.INVISIBLE);
}
}
至此,我們已經完全採用M-V-P的架構模式來搭建完成了“登錄驗證”的小項目。
- Activity現在只編寫最基本的功能代碼,如:加載佈局,實例化控件,綁定監聽等。
- 將與用戶發生交互的界面操作,都抽象到View接口中,然後由Activity實現接口,進而具體實現。
- 需要對用戶的行爲進行處理或反饋的方法(例如上面我們的代碼中登錄與清楚按鈕的點擊事件處理),都抽離出來放到Presenter當中。
- 需要對針對於數據模型進行操作的方法(例如將用戶輸入的數據進行持久化存儲或需要在服務器進行處理等)都放在業務邏輯類,即Model當中。
- Presenter從View接收數據,調用Model進行處理;從Model獲取處理的結果,調用View反饋到界面。