Android MVP模式
-
MVC模式 & 缺點(diǎn)
MVC讶迁,全稱Model-View-Controller,即模型-視圖-控制器鬓长。 具體如下:
- View:對(duì)應(yīng)于布局文件
- Model:業(yè)務(wù)邏輯和實(shí)體模型
- Controllor:對(duì)應(yīng)于Activity
缺點(diǎn)
MVC模式下實(shí)際上就是Activty與Model之間交互谒拴,View完全獨(dú)立出來(lái)了。
View對(duì)應(yīng)于布局文件涉波,其實(shí)能做的事情特別少英上,實(shí)際上關(guān)于該布局文件中的數(shù)據(jù)綁定的操作,事件處理的代碼都在Activity中啤覆,造成了Activity既像View又像Controller苍日,使得Activity變得臃腫。
-
MVP模式 & 優(yōu)點(diǎn)
MVP窗声,全稱 Model-View-Presenter相恃,即模型-視圖-層現(xiàn)器。具體如下:
- View 對(duì)應(yīng)于Activity笨觅,負(fù)責(zé)View的繪制以及與用戶交互
- Model 依然是業(yè)務(wù)邏輯和實(shí)體模型
- Presenter 負(fù)責(zé)完成View于Model間的交互
優(yōu)點(diǎn)
MVP模式通過(guò)Presenter實(shí)現(xiàn)數(shù)據(jù)和視圖之間的交互拦耐,簡(jiǎn)化了Activity的職責(zé)耕腾。同時(shí)即避免了View和Model的直接聯(lián)系,又通過(guò)Presenter實(shí)現(xiàn)兩者之間的溝通杀糯。
MVP模式減少了Activity的職責(zé)幽邓,簡(jiǎn)化了Activity中的代碼,將復(fù)雜的邏輯代碼提取到了Presenter中進(jìn)行處理火脉,模塊職責(zé)劃分明顯牵舵,層次清晰。與之對(duì)應(yīng)的好處就是倦挂,耦合度更低畸颅,更方便的進(jìn)行測(cè)試。
-
MVP & MVC 區(qū)別
MVC中是允許Model和View進(jìn)行交互的方援,而MVP中很明顯没炒,Model與View之間的交互由Presenter完成。還有一點(diǎn)就是Presenter與View之間的交互是通過(guò)接口的犯戏。
-
MVP模式 典例 —— 登錄案例
結(jié)構(gòu)圖
1.Model層
在本例中送火,M0del層負(fù)責(zé)對(duì)從登錄頁(yè)面獲取地帳號(hào)密碼進(jìn)行驗(yàn)證(一般需要請(qǐng)求服務(wù)器進(jìn)行驗(yàn)證,本例直接模擬這一過(guò)程)先匪。 從上圖的包結(jié)構(gòu)圖中可以看出种吸,Model層包含內(nèi)容:
①實(shí)體類bean
②接口,表示Model層所要執(zhí)行的業(yè)務(wù)邏輯
③接口實(shí)現(xiàn)類呀非,具體實(shí)現(xiàn)業(yè)務(wù)邏輯坚俗,包含的一些主要方法
下面以代碼的形式一一展開。
①實(shí)體類bean
public class User {
private String password;
private String username;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User{" +
"password='" + password + '\'' +
", username='" + username + '\'' +
'}';
}
}
封裝了用戶名岸裙、密碼猖败,方便數(shù)據(jù)傳遞。
②接口
public interface LoginModel {
void login(User user, OnLoginFinishedListener listener);
}
其中OnLoginFinishedListener 是presenter層的接口降允,方便實(shí)現(xiàn)回調(diào)presenter恩闻,通知presenter業(yè)務(wù)邏輯的返回結(jié)果,具體在presenter層介紹剧董。
③接口實(shí)現(xiàn)類
public class LoginModelImpl implements LoginModel {
@Override
public void login(User user, final OnLoginFinishedListener listener) {
final String username = user.getUsername();
final String password = user.getPassword();
new Handler().postDelayed(new Runnable() {
@Override public void run() {
boolean error = false;
if (TextUtils.isEmpty(username)){
listener.onUsernameError();//model層里面回調(diào)listener
error = true;
}
if (TextUtils.isEmpty(password)){
listener.onPasswordError();
error = true;
}
if (!error){
listener.onSuccess();
}
}
}, 2000);
}
}
實(shí)現(xiàn)Model層邏輯:延時(shí)模擬登陸(2s)幢尚,如果用戶名或者密碼為空則登陸失敗,否則登陸成功送滞。
2.View層
視圖:將Modle層請(qǐng)求的數(shù)據(jù)呈現(xiàn)給用戶侠草。一般的視圖都只是包含用戶界面(UI),而不包含界面邏輯犁嗅,界面邏輯由Presenter來(lái)實(shí)現(xiàn)。
從上圖的包結(jié)構(gòu)圖中可以看出晤碘,View包含內(nèi)容:
①接口褂微,上面我們說(shuō)過(guò)Presenter與View交互是通過(guò)接口功蜓。其中接口中方法的定義是根據(jù)Activity用戶交互需要展示的控件確定的。
②接口實(shí)現(xiàn)類宠蚂,將上述定義的接口中的方法在Activity中對(duì)應(yīng)實(shí)現(xiàn)具體操作式撼。
下面以代碼的形式一一展開。
①接口
public interface LoginView {
//login是個(gè)耗時(shí)操作求厕,我們需要給用戶一個(gè)友好的提示著隆,一般就是操作ProgressBar
void showProgress();
void hideProgress();
//login當(dāng)然存在登錄成功與失敗的處理,失敗給出提示
void setUsernameError();
void setPasswordError();
//login成功呀癣,也給個(gè)提示
void showSuccess();
}
上述5個(gè)方法都是presenter根據(jù)model層返回結(jié)果需要view執(zhí)行的對(duì)應(yīng)的操作美浦。
②接口實(shí)現(xiàn)類
即對(duì)應(yīng)的登錄的Activity,需要實(shí)現(xiàn)LoginView接口项栏。
public class LoginActivity extends AppCompatActivity implements LoginView, View.OnClickListener {
private ProgressBar progressBar;
private EditText username;
private EditText password;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
progressBar = (ProgressBar) findViewById(R.id.progress);
username = (EditText) findViewById(R.id.username);
password = (EditText) findViewById(R.id.password);
findViewById(R.id.button).setOnClickListener(this);
//創(chuàng)建一個(gè)presenter對(duì)象浦辨,當(dāng)點(diǎn)擊登錄按鈕時(shí),讓presenter去調(diào)用model層的login()方法沼沈,驗(yàn)證帳號(hào)密碼
presenter = new LoginPresenterImpl(this);
}
@Override
protected void onDestroy() {
presenter.onDestroy();
super.onDestroy();
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void setUsernameError() {
username.setError(getString(R.string.username_error));
}
@Override
public void setPasswordError() {
password.setError(getString(R.string.password_error));
}
@Override
public void showSuccess() {
progressBar.setVisibility(View.GONE);
Toast.makeText(this,"login success",Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
User user = new User();
user.setPassword(password.getText().toString());
user.setUsername(username.getText().toString());
presenter.validateCredentials(user);
}
}
View層實(shí)現(xiàn)Presenter層需要調(diào)用的控件操作流酬,方便Presenter層根據(jù)Model層返回的結(jié)果進(jìn)行操作View層進(jìn)行對(duì)應(yīng)的顯示。
3.Presenter層
Presenter是用作Model和View之間交互的橋梁列另。 從上圖的包結(jié)構(gòu)圖中可以看出芽腾,Presenter包含內(nèi)容:
①接口,包含Presenter需要進(jìn)行Model和View之間交互邏輯的接口页衙,以及上面提到的Model層數(shù)據(jù)請(qǐng)求完成后回調(diào)的接口晦嵌。
②接口實(shí)現(xiàn)類,即實(shí)現(xiàn)具體的Presenter類邏輯拷姿。
下面以代碼的形式一一展開惭载。
①接口
public interface OnLoginFinishedListener {
void onUsernameError();
void onPasswordError();
void onSuccess();
}
當(dāng)Model層得到請(qǐng)求的結(jié)果,需要回調(diào)Presenter層响巢,讓Presenter層調(diào)用View層的接口方法描滔。
public interface LoginPresenter {
void validateCredentials(User user);
void onDestroy();
}
登陸的Presenter 的接口,實(shí)現(xiàn)類為L(zhǎng)oginPresenterImpl踪古,完成登陸的驗(yàn)證含长,以及銷毀當(dāng)前view。
②接口實(shí)現(xiàn)類
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {
private LoginView loginView;
private LoginModel loginModel;
public LoginPresenterImpl(LoginView loginView) {
this.loginView = loginView;
this.loginModel = new LoginModelImpl();
}
@Override
public void validateCredentials(User user) {
if (loginView != null) {
loginView.showProgress();
}
loginModel.login(user, this);
}
@Override
public void onDestroy() {
loginView = null;
}
@Override
public void onUsernameError() {
if (loginView != null) {
loginView.setUsernameError();
loginView.hideProgress();
}
}
@Override
public void onPasswordError() {
if (loginView != null) {
loginView.setPasswordError();
loginView.hideProgress();
}
}
@Override
public void onSuccess() {
if (loginView != null) {
loginView.showSuccess();
}
}
}
由于presenter完成二者的交互伏穆,那么肯定需要二者的實(shí)現(xiàn)類(通過(guò)傳入?yún)?shù)拘泞,或者new)。
presenter里面有個(gè)OnLoginFinishedListener枕扫, 其在Presenter層實(shí)現(xiàn)陪腌,給Model層回調(diào),更改View層的狀態(tài), 確保 Model層不直接操作View層诗鸭。
核心流程總結(jié)
View與Model并不直接交互染簇,而是使用Presenter作為View與Model之間的橋梁。其中Presenter中同時(shí)持有View層的Interface的引用以及Model層的引用强岸,而View層持有Presenter層引用锻弓。當(dāng)View層某個(gè)界面需要展示某些數(shù)據(jù)的時(shí)候勇边,首先會(huì)調(diào)用Presenter層的引用竿滨,然后Presenter層會(huì)調(diào)用Model層請(qǐng)求數(shù)據(jù),當(dāng)Model層數(shù)據(jù)加載成功之后會(huì)調(diào)用Presenter層的回調(diào)方法通知Presenter層數(shù)據(jù)加載情況辽旋,最后Presenter層再調(diào)用View層的接口將加載后的數(shù)據(jù)展示給用戶妓盲。本例模式:
一般模式: