您的位置:首页 > 运维架构 > 网站架构

对Android-MVP架构模式的理解与初尝试

2015-11-23 14:35 851 查看
通常,如果你是一名面向对象的开发者,或多或少都了解和接触过大名鼎鼎的“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”,即反馈给用户;

这样加以对应,是否会对于理解MVP模式有所帮助?

话已至此,对于所谓的MVP,我们基本已经有了一定的了解,接着,就根据一个简单的例子,来加深自己的印象和理解。
以我在另一篇描述MVP模式的博客里看到的需求为例,假设我们现在有一个“验证登录”的需求,要实现以下效果:



接着,开始着手搭建项目,编写代码。

1、餐厅开业之前,我们要先搞定门市。OK,所以第一步,我们完成登录界面的布局文件的编写。

2、门市搞定了,为了生意,接着我们进行“装修”。没问题,那我们接着进行View的编写工作,完成装修(加载布局,实例化控件,绑定监听事件)。于是这时的LoginActivity应该是这样的:
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.门市的装修工作终于完成了,我们送了一口气。这个时候,为了将来餐馆能够有条不紊的顺利营业。此时,也许我们就应该开始着手考虑,当我们的餐馆正式开始营业后,可能与消费产生哪些交互行为?从而定下一些好的营业行为规范了!
  
  对应于程序中来说,既是我们该View中需要与用户发生交互的一切行为。所以,对应我们“验证登录”的需求来说,接口的定义可能就类似于:
public interface ILoginView {

// 获取用户输入用户名
String getUserNameInput();

// 获取用户输入密码
String getPassWordInput();

// 登录成功,跳转界面
void toAppMainActivity();

// 登录失败,提示用户
void showLoginFailedInfo();

// 清空输入记录
void clearInputValue();

// 提示正在登录
void showLoginLoading();

// 隐藏正在登录提示
void hideLoginLoading();
}


4、规范我们已经定下了,通知小秘,去把经营规范打印出来,给我挂在餐馆里最显眼的地方!
所以我们此时要做的也是一样,既然View的接口已经声明完毕,此时我们就应该让Activity去实现接口,去落实老板定下的规范。
所以此时的Activity类经过完善,变成了下面这样:
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、这个时候对于店铺的打理已经基本进行完毕了,这个时候我们应该着手考虑一下餐厅的业务逻辑了,例如最关键的:找个好厨师~
没问题,也就是说此时我们应该开始着手于Model层的打架工作了。
在这里,对应于验证登录的业务需求,我们首先建立我们的数据模型:
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、当我们工作至此处时,厨房部门已经被我们打点完毕了。此时,万事俱备,只欠东风。我们只差一个关键的“店小二”了。
是的,当View层和Model层的搭建工作都已经完成,我们就只差一个Presenter来联系它们了。

你可以试着这样考虑,既然Presenter将作为View和Model的链接枢纽,那么肯定它是能够同时访问二者的。
这对应于程序来说,也就是,在Presenter类里,将必然存在View和Model的一个实例,从而你才能够调用他们的方法,完成交互。

而同时,就好比你作为餐馆老板招聘一名店小二,你肯定会明确的做出要求:既店小二开始上班以后,他的工作内容是什么。
所以在Presenter类里,你还应该根据实际,定义相应的行为。
就如同,餐馆老板在餐厅营业应为里规定了有“点餐”这个营业行为,那么当有顾客进行了“点餐”这个行为时,就应该调用“店小二”执行“收、取菜单”的动作。
对应我们本例当中,既是当有用户点击了“登录”按钮时,View就知道调用Presenter里的对应方法,再由Presenter去调用model里对应的方法。

所以,我们最终的Presenter类的定义可能如下:
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、到了这里你的餐厅完全已经做好开始营业的准备了!唯一的工作,记得为你的“店小二”办理入职手续。
对应到我们的程序来说,既是将Presenter的实例添加到View当中,并且在各个按钮的监听事件里,安排Presenter进行对应的行为。
于是,最终的Activity变成了:
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的架构模式来搭建完成了“登录验证”的小项目。
对此我们加以总结,其实可以得出:对于MVP模式的构建,其规律为:

Activity现在只编写最基本的功能代码,如:加载布局,实例化控件,绑定监听等。
将与用户发生交互的界面操作,都抽象到View接口中,然后由Activity实现接口,进而具体实现。
需要对用户的行为进行处理或反馈的方法(例如上面我们的代码中登录与清楚按钮的点击事件处理),都抽离出来放到Presenter当中。
需要对针对于数据模型进行操作的方法(例如将用户输入的数据进行持久化存储或需要在服务器进行处理等)都放在业务逻辑类,即Model当中。
Presenter从View接收数据,调用Model进行处理;从Model获取处理的结果,调用View反馈到界面。

来看一下最终的代码结构变成了什么样,是不是条理有变得更加清晰:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android mvc mvp