对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应该是这样的:
3.门市的装修工作终于完成了,我们送了一口气。这个时候,为了将来餐馆能够有条不紊的顺利营业。此时,也许我们就应该开始着手考虑,当我们的餐馆正式开始营业后,可能与消费产生哪些交互行为?从而定下一些好的营业行为规范了!
对应于程序中来说,既是我们该View中需要与用户发生交互的一切行为。所以,对应我们“验证登录”的需求来说,接口的定义可能就类似于:
4、规范我们已经定下了,通知小秘,去把经营规范打印出来,给我挂在餐馆里最显眼的地方!
所以我们此时要做的也是一样,既然View的接口已经声明完毕,此时我们就应该让Activity去实现接口,去落实老板定下的规范。
所以此时的Activity类经过完善,变成了下面这样:
5、这个时候对于店铺的打理已经基本进行完毕了,这个时候我们应该着手考虑一下餐厅的业务逻辑了,例如最关键的:找个好厨师~
没问题,也就是说此时我们应该开始着手于Model层的打架工作了。
在这里,对应于验证登录的业务需求,我们首先建立我们的数据模型:
6、当我们工作至此处时,厨房部门已经被我们打点完毕了。此时,万事俱备,只欠东风。我们只差一个关键的“店小二”了。
是的,当View层和Model层的搭建工作都已经完成,我们就只差一个Presenter来联系它们了。
你可以试着这样考虑,既然Presenter将作为View和Model的链接枢纽,那么肯定它是能够同时访问二者的。
这对应于程序来说,也就是,在Presenter类里,将必然存在View和Model的一个实例,从而你才能够调用他们的方法,完成交互。
而同时,就好比你作为餐馆老板招聘一名店小二,你肯定会明确的做出要求:既店小二开始上班以后,他的工作内容是什么。
所以在Presenter类里,你还应该根据实际,定义相应的行为。
就如同,餐馆老板在餐厅营业应为里规定了有“点餐”这个营业行为,那么当有顾客进行了“点餐”这个行为时,就应该调用“店小二”执行“收、取菜单”的动作。
对应我们本例当中,既是当有用户点击了“登录”按钮时,View就知道调用Presenter里的对应方法,再由Presenter去调用model里对应的方法。
所以,我们最终的Presenter类的定义可能如下:
7、到了这里你的餐厅完全已经做好开始营业的准备了!唯一的工作,记得为你的“店小二”办理入职手续。
对应到我们的程序来说,既是将Presenter的实例添加到View当中,并且在各个按钮的监听事件里,安排Presenter进行对应的行为。
于是,最终的Activity变成了:
至此,我们已经完全采用M-V-P的架构模式来搭建完成了“登录验证”的小项目。
对此我们加以总结,其实可以得出:对于MVP模式的构建,其规律为:
Activity现在只编写最基本的功能代码,如:加载布局,实例化控件,绑定监听等。
将与用户发生交互的界面操作,都抽象到View接口中,然后由Activity实现接口,进而具体实现。
需要对用户的行为进行处理或反馈的方法(例如上面我们的代码中登录与清楚按钮的点击事件处理),都抽离出来放到Presenter当中。
需要对针对于数据模型进行操作的方法(例如将用户输入的数据进行持久化存储或需要在服务器进行处理等)都放在业务逻辑类,即Model当中。
Presenter从View接收数据,调用Model进行处理;从Model获取处理的结果,调用View反馈到界面。
来看一下最终的代码结构变成了什么样,是不是条理有变得更加清晰:
到了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反馈到界面。
来看一下最终的代码结构变成了什么样,是不是条理有变得更加清晰:
相关文章推荐
- 分享微信开发Html5轻游戏中的几个坑
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件