讨论Android开发中的MVC设计思想
最近闲着没事,总是想想做点什么。在时间空余之时给大家说说MVC设计思想在Android开发中的运用吧!
MVC设计思想在Android开发中一直都是一套比较好的设计思想。很多APP的设计都是使用这套方案完成架构设计的。
谈到MVC我想分为以下几个点分点突进。
1.什么是MVC框架。
2.MVC如何工作
3.MVC的缺点
4.Android之MVC设计模式。
一。什么是MVC框架.
MVC英文即Model-View-Controller,即把一个应用的输入、处理、输出流程按照Model、View、Controller的方式进行分离,这样一个应用被分成三个层——模型层、视图层、控制层。
模型
模型(Model):就是业务流程/状态的处理以及业务规则的制定。业务流程的处理过程对其它层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。业务模型的设计可以说是MVC最主要的核心。目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个顶级类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。
业务模型还有一个很重要的模型那就是数据模型。数据模型主要指实体对象的数据 保存(持续化)。比如将一张订单保存到数据库,从数据库获取订单。我们可以将这个模型单独列出,所有有关数据库的操作只限制在该模型中。
视图
视图(View)代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML、XML和
Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。比如一个订单的视图只接受来自模型的数据并显示给用户,以及将用户界面的输入数据和请求传递给控制和模型。
控制
控制(Controller)可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。例如,用户点击一个连接,控制层接受请求后, 并不处理业务信息,它只把用户的信息传递给模型,告诉模型做什么,选择符合要求的视图返回给用户。因此,一个模型可能对应多个视图,一个视图可能对应多个模型。
模型、视图与控制器的分离,使得一个模型可以具有多个显示视图。如果用户通过某个视图的控制器改变了模型的数据,所有其它依赖于这些数据的视图都应反映到这些变化。因此,无论何时发生了何种数据变化,控制器都会将变化通知所有的视图,导致显示的更新。这实际上是一种模型的变化-传播机制。模型、视图、控制器三者之间的关系和各自的主要功能。
二。MVC如何工作:
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。
视图视图是用户看到并与之交互的界面。对老式的Web应用程序来说,视图就是由HTML元素组成的界面,在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色,但一些新的技术已层出不穷,它们包括Macromedia Flash和象XHTML,XML/XSL,WML等一些标识语言和Web services.
如何处理应用程序的界面变得越来越有挑战性。MVC一个大的好处是它能为你的应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,不管这些数据是联机存储的还是一个雇员列表,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。
模型模型表示企业数据和业务规则。在MVC的三个部件中,模型拥有最多的处理任务。例如它可能用象EJBs和ColdFusion Components这样的构件对象来处理数据库。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
控制器控制器接受用户的输入并调用模型和视图去完成用户的需求。所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后确定用哪个视图来显示模型处理返回的数据。
现在我们总结MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。
为什么要使用 MVC
大部分Web应用程序都是用像ASP,PHP,或者CFML这样的过程化语言来创建的。它们将像数据库查询语句这样的数据层代码和像HTML这样的表示层代码混在一起。经验比较丰富的开发者会将数据从表示层分离开来,但这通常不是很容易做到的,它需要精心的计划和不断的尝试。MVC从根本上强制性的将它们分开。尽管构造MVC应用程序需要一些额外的工作,但是它给我们带来的好处是无庸质疑的。
首先,最重要的一点是多个视图能共享一个模型,正如我所提及的,现在需要用越来越多的方式来访问你的应用程序。对此,其中一个解决之道是使用MVC,无论你的用户想要Flash界面或是 WAP 界面;用一个模型就能处理它们。由于你已经将数据和业务规则从表示层分开,所以你可以最大化的重用你的代码了。
由于模型返回的数据没有进行格式化,所以同样的构件能被不同界面使用。例如,很多数据可能用HTML来表示,但是它们也有可能要用Macromedia Flash和WAP来表示。模型也有状态管理和数据持久性处理的功能,例如,基于会话的购物车和电子商务过程也能被Flash网站或者无线联网的应用程序所重用。
因为模型是自包含的,并且与控制器和视图相分离,所以很容易改变你的应用程序的数据层和业务规则。如果你想把你的数据库从MySQL移植到Oracle,或者改变你的基于RDBMS数据源到LDAP,只需改变你的模型即可。一旦你正确的实现了模型,不管你的数据来自数据库或是LDAP服务器,视图将会正确的显示它们。由于运用MVC的应用程序的三个部件是相互对立,改变其中一个不会影响其它两个,所以依据这种设计思想你能构造良好的松偶合的构件。
对我来说,控制器的也提供了一个好处,就是可以使用控制器来联接不同的模型和视图去完成用户的需求,这样控制器可以为构造应用程序提供强有力的手段。给定一些可重用的模型和视图,控制器可以根据用户的需求选择模型进行处理,然后选择视图将处理结果显示给用户。
三.MVC的缺点。
MVC的缺点是由于它没有明确的定义,所以完全理解MVC并不是很容易。使用MVC需要精心的计划,由于它的内部原理比较复杂,所以需要花费一些时间去思考。
你将不得不花费相当可观的时间去考虑如何将MVC运用到你的应用程序,同时由于模型和视图要严格的分离,这样也给调试应用程序带来了一定的困难。每个构件在使用之前都需要经过彻底的测试。一旦你的构件经过了测试,你就可以毫无顾忌的重用它们了。
根据我个人经验,由于我们将一个应用程序分成了三个部件,所以使用MVC同时也意味着你将要管理比以前更多的文件,这一点是显而易见的。这样好像我们的工作量增加了,但是请记住这比起它所能带给我们的好处是不值一提。
MVC并不适合小型甚至中等规模的应用程序,花费大量时间将MVC应用到规模并不是很大的应用程序通常会得不偿失。
MVC设计模式是一个很好创建软件的途径,它所提倡的一些原则,像内容和显示互相分离可能比较好理解。但是如果你要隔离模型、视图和控制器的构件,你可能需要重新思考你的应用程序,尤其是应用程序的构架方面。如果你肯接受MVC,并且有能力应付它所带来的额外的工作和复杂性,MVC将会使你的软件在健壮性,代码重用和结构方面提升一个新的台阶。
四。Android开发之MVC模式。
对于android,MVC模式简单的理解可以看成
1、业务bean
2、美工视图
3、Activity设计
业务bean和美工视图很简单的,我在这儿就不说了,重点说说业务逻辑层跟视图层如何协调通讯,共同合做的?
我们先来看看一端小代码:
代码下载地址:http://pan.baidu.com/s/1bbeOI
导入工程到eclipse后我们会发现有如下内容:
BaseDao:数据库层用于将数据保存到数据库的支持。
BaseModel: Model层统一的基类,该类可以用于两个Activity之间的参数传递,实现了Parcelable接口。
BaseService:业务逻辑层的统一基类。主要做的工作是:从服务器端获取数据,将服务器返回的数据解析出来。并将解析过后的数据传入Activity中。
ServiceListener: 推荐Activity/Fragment实现该接口。所谓的Service层跟Activity/Fragment之间如何协调工作,Service层如何将数据带到Activity/Fragment中就是利用该接口完成的
BaseDao.java:
这个类没什么好说的,就是用来对数据库的增删查改操作的业务类,打开一看,大致可以分为以下一点:
1 public class BaseDao { 2 public SQLiteDatabase db; 3 public BaseDao(SQLiteOpenHelper helper){ 4 db = helper.getWritableDatabase(); 5 } 6 public void execSql(String sql){ 7 db.execSQL(sql); 8 } 9 }
1.在构造方法中传递一个SQLiteOpenHelper对象,可以将该对象形象的理解为数据库文件。该类提供了一个方法用于执行SQL语句,可以利用该方法执行Sql语句,对数据库进行操作。
2.使用方法,可以直接继承该类,新增对数据库一些操作方法,本人建议一张表在一个类中完成对表所有操作,一张表对应一个类。
BaseService.java:
以下是BaseService的源码:该类理解上有一个难点(仅对初学者而言)。
1 public abstract class BaseService { 2 public ServiceListener listener; 3 private Integer dataInstruction; 4 private static FinalHttp http; 5 6 public void setDataInstruction(int dataInstruction) { 7 this.dataInstruction = dataInstruction; 8 } 9 //服务器返回的数据结果监听 10 protected OnResultListener result = new OnResultListener() { 11 @Override 12 public void onGetResult(final Object result, final int iError) throws Exception { 13 if(iError == 200){ 14 if(result instanceof String) 15 parserJson(result.toString(),dataInstruction); 16 else if(result instanceof File) 17 downloader((File) result,dataInstruction); 18 }else{ 19 throw new RuntimeException("服务器返回数据为空:"+result); 20 } 21 } 22 }; 23 /** 24 * 构造Service实例,用于向服务器、数据库发送请求或获取数据 25 * @param listener 回调接口监听,监听服务器返回的数据。常用于接收服务器返回的数据 26 * @param dataInstruction 当前Service请求的标志性常量 27 */ 28 public BaseService(ServiceListener listener, int dataInstruction) { 29 this.listener = listener; 30 this.dataInstruction = dataInstruction; 31 } 32 /** 33 * Service层重写该方法解析服务器返回的数据,解析完了之后需要手动调用 34 * listener.handlerIntent()方法将解析之后的数据带到V视图层使用 35 * @param json 36 * @param dataInstruction 可以利用该参数判断返回的数据是哪个方法调用的该方法,用于区分多个JSON 37 */ 38 public abstract void parserJson(String json, int dataInstruction); 39 public void downloader(File file, Integer dataInstruction){ 40 if(listener != null){ 41 listener.handlerIntent(file, dataInstruction); 42 } 43 } 44 45 46 47 48 //****************************************************兼容FinalHttp工具类********************************************** 49 /** 50 * 发送Post请求 51 */ 52 public void sendPostServer(String url,AjaxParams params){ 53 if(http == null) 54 http = new FinalHttp(); 55 http.post(url, params, new AjaxCallBack<Object>() { 56 @Override 57 public void onSuccess(Object t) throws Exception { 58 result.onGetResult(t.toString(), 200); 59 } 60 @Override 61 public void onFailure(Throwable t, int errorNo, String strMsg) throws Exception { 62 result.onGetResult(strMsg, 400); 63 } 64 }); 65 } 66 /** 67 * 发送get请求 68 */ 69 public void sendGetServer(String url){ 70 if(http == null) 71 http = new FinalHttp(); 72 http.get(url, new AjaxCallBack<Object>(){ 73 @Override 74 public void onSuccess(Object t) throws Exception { 75 result.onGetResult(t.toString(), 200); 76 } 77 @Override 78 public void onFailure(Throwable t, int errorNo, String strMsg) throws Exception { 79 result.onGetResult(strMsg, 400); 80 } 81 }); 82 } 83 /** 84 * 下载 85 */ 86 public void download(String url,String target){ 87 if(http == null) 88 http = new FinalHttp(); 89 http.download(url, target, new AjaxCallBack<File>() { 90 @Override 91 public void onSuccess(File t) throws Exception { 92 result.onGetResult(t, 200); 93 } 94 @Override 95 public void onFailure(Throwable t, int errorNo, String strMsg) throws Exception { 96 result.onGetResult(strMsg, 400); 97 } 98 }); 99 } 100 public void download(String url,String target,AjaxParams params){ 101 if(http == null) 102 http = new FinalHttp(); 103 http.download(url, params, target, new AjaxCallBack<File>() { 104 @Override 105 public void onSuccess(File t) throws Exception { 106 result.onGetResult(t, 200); 107 } 108 @Override 109 public void onFailure(Throwable t, int errorNo, String strMsg) throws Exception { 110 result.onGetResult(strMsg, 400); 111 } 112 }); 113 } 114 }
我来说说:如何充该类中看出Service层跟Activity/Fragment之间通讯的呢?
1.首先我们看看该类的构造方法:
1 public BaseService(ServiceListener listener, int dataInstruction) { 2 this.listener = listener; 3 this.dataInstruction = dataInstruction; 4 }
在构造方法中有两个参数:ServiceListener和一个数据标志性常量dataInstruction
ServiceListener:上层Activity/Fragment实现了该接口。将该接口的子类对象传递到该构造中
dataInstruction :说白了,这个参数的意义并不是很大,重点用来区分每个请求服务器返回的数据是不一样的那么怎么区分呢,就是靠它区分!
在使用之时直接定义常量,在parseJson方法中判断该常量的值来区分返回的数据分别对应的是哪个请求。
2.我们再来看一看还有一段代码:
1 //服务器返回的数据结果监听 2 protected OnResultListener result = new OnResultListener() { 3 @Override 4 public void onGetResult(final Object result, final int iError) throws Exception { 5 if(iError == 200){ 6 if(result instanceof String) 7 parserJson(result.toString(),dataInstruction); 8 else if(result instanceof File) 9 downloader((File) result,dataInstruction); 10 }else{ 11 throw new RuntimeException("服务器返回数据为空:"+result); 12 } 13 } 14 };
该对象是一个业务调度器。就是讲服务器返回的数据传到指定的方法中去处理。
如果你懂Afinal框架可以结合下面作出的兼容该框架的一些方法理解该对象。它实际上就是将发送请求和处理服务器返回的数据分开写在两个方法中。发送请求用别的方法。解析服务器返回的数据用一个方法处理。
3.我们在总整体看看这个类,我们似乎把一个重点给忘了,就是Service层到底是如何将数据传到Activity/Fragment中的呢,发送请求我们有了,解析数据我知道在那个方法中解析了。但是这些不能解决关键问题?
我们观察该类发现还有一段小代码要研究:(重点理解注释的部分说明)
1 /** 2 * Service层重写该方法解析服务器返回的数据,解析完了之后需要手动调用 3 * listener.handlerIntent()方法将解析之后的数据带到V视图层使用 4 * @param json 5 * @param dataInstruction 可以利用该参数判断返回的数据是哪个方法调用的该方法,用于区分多个JSON 6 */ 7 public abstract void parserJson(String json, int dataInstruction); 8 public void downloader(File file, Integer dataInstruction){ 9 if(listener != null){ 10 listener.handlerIntent(file, dataInstruction); 11 } 12 }
通过以上注释可以看出:原来完成Service层跟Activity/Fragment之间的通讯是我们自己完成的。就是在解析完服务器返回的数据之后通过调用在构造方法中传递进来的ServiceListener的子类对象的handlerIntent()方法来完成的。否者就无法实现通讯 和共同协调合作。
ServiceListener.java:这个类没什么好说的,直接使用Activity/Fragment实现该接口实现一些方法就好了,在创建BaseService的子类对象时把this(activity/Fragment的对象引用变量)传到BaseService子类的构造方法中即可。
至于该接口中的一些方法,这里取决于你解析后的是一个集合还是一个业务Bean,它会根据数据的不一样调用与之对应的方法。集合调用带有集合的方法,单个业务Bean就调用带有单个业务Bean的方法。。。。。