通常,如果你是一名面向对象的开发者,或多或少都了解和接触过大名鼎鼎的“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反馈到界面。