初识MVC模式
第一次认识到MVC模式是从Microsoft MFC框架所采用的“文档-视图”模型开始的。第一次接触到这个概念让我兴奋不已,很长时间困扰我的程序框架问题似乎迎刃而解了。而后我翻阅了GOF一书中对MVC模式的描述,增进了对这个模式的一些理解。应该说MVC框架是程序设计领域的常青树,也是GOF模式中最为重要的模式之一。这一经典的模式被广泛的使用,有太多的程序构架在这一框架之下,从早期的卓面Application到现在流行的Web。并因各自的需求不同,MVC有了很多的变种。了解MVC是每个程序设计人员的必修课,最好能够达到熟练运用的程度。
我并不打算详细介绍这一模式,因为细节比较复杂,我口舌拙笨也不容易说清楚,大家应该参阅一下有关模式的书籍,任何一本都比我要讲的清楚。所以此处就一带而过。MVC模式是Model-View-Controller的缩写,中文译为“模型-视图-控制器”。MVC的核心思想是分离。Model就是对实体类的抽象;View就是Model在屏幕上的表示;Controller就是协调者。可能有朋友发现Controller的描述多少有些含糊,不要着急,这个一会还要谈到。大概因为太过有名,MVC模式的每个实现都出处很大,但他们却都叫做MVC!!搞得初学者一头雾水。往往滥用,最后搞得M.V.C.三者之间的协调很混乱。这其实并不是他们的错,理清思路的关键还是刚刚提到的一个词“分离”。尽管MVC实现不同,但是思想是一致的。
MVC模式的利与弊
先谈优点:
1)将M.V.C.分离可以让不同的专家负责不同的模块,一般情况下,M部分由熟悉数据库,网络传输的专家来负责;V则交给对UI有研究的专家。这对于项目的管理者而言是多么的诱人,分工意味着可以提高效率并可以按照传统的责任划分来处理软件开发过程。对开发者而言也可以专心于一个领域。这样做的前提是接口要明确,MVC的分离思想正为其提供了基础。
2)一旦V的部分发生变化,可以迅速的重构而不必引起整个工程的返工。如今的软件表现层的部分变化实在是太快了…
3)M的部分,因为足够抽象,可以方便的重复利用,符合OO的思想。另一方面我们可以利用JUnit等单元测试工具对M进行测试,保证工程质量。
谈完了优点再来看看缺点:
1)利用MVC模式(也包括近代的其他一些模式)暗示我们通过多产生一些类,来提高程序的可读性与健壮性。附带来的缺点就是类的数量的膨胀。说句笑话,MVC就好像是发面时用的速效粉一样,是最为方便的代码膨胀剂,相信大家都深有体会:)
2)MVC虽然定义了M.V.C.个个部件的含义,但并不具体,而且没有非常明确的固定三者之间的联系。所以一直以来除了View没有争论外,其他方面都有很多争论,大家都想把自己的理解作为正解。尤其是“Model到底是屏幕数据的集合还是实体数据”、“控制器的作用”是两个经常争论的问题。前面提过MVC变种很多,这也给初学者留下了不少的陷阱。后面结合实例将会分析几种常见的做法。
3)MVC的实现成本偏高。但请注意是这是相对的,一般而言项目越大,越可以看出其优势。
常见的MVC模式实践
下面将会介绍在midp平台几种常见的实践,最后是我习惯的做法
M―V形式(或者MC―V、M―VC)
这也是在j2me中一种惯用的方法,精炼的说这种方法是以屏幕为组织单位的,因而很适合RAD工具的开发思路。一个屏幕及其控制被抽象成一个VC类,而这个类中有一个私有的Model对象来代表屏幕上要用到的数据元素。屏幕对象并不保存任何的实体数据,这些数据被组织在了Model对象中。大概因为屏幕对象很直观,控制器的作用也不明晰(它绝大部分的功能被view或是model取代,具体取决于你的实现),所以也常常称呼为model-view模式。形式如下:
class MyFrame extend Frame{
private Model model;
private StringItem name;
MyFrame(Model model){
this.model=model;
name=new StringItem(model.getName());//请求模型的数据
append(name);
}
}
class Model{
private String name="M-C pattern";
public String getName(){//这是一个服务接口
return name;
}
}
上面看到的是个典型的M―V模型,我们可以理解这种以屏幕为核心的分离的含义。Model组织起屏幕的数据,view向Model索要其希望显示的数据,注意这一操作一定要通过预先协商好的接口访问,而不是直接操作。如果出现复杂的事务逻辑(用户选择的某种操作),有人将其放在Model端,也有人放在View端,但一般上放在Model端,这时Model带有严重的Controller的色彩。
这种形式的优点是非常的直观,也有限的分离了显示和数据。如果常看j2medev.com站长Mingjava的文章,可以看到大部分他写的例子都是这种模式。并且这种模式也常常用于RAD工具。
这种模式的缺点是它与RAD工具一样鼓励你从屏幕开始思考问题,这往往让你陷入RAD的陷阱――不先考虑事务的流程,而是从用户接口直接下手去分析问题,这往往扼杀了你的全局构思。
Sun blueprints: Smart Ticket中使用的MVC模式
著名的蓝图程序Smart Ticket中使用了MVC模式,并且这一模式帮助Sun的程序员在MIDP2发布时,快速的将Smart Ticket的view部分从MIDP1.0 更新到MIDP2.0。
Sun针对MIDP的特点,设计并改进了这一模式,在SUN的解决方法中是一个很标准的方法,只是 Controller变成了一个巨大的事务处理器,所有由UI对象收集到的用户的需求都转发给Controller处理。Controller内部保存了一组常量。在一个dispose(int id)形式的方法里一个巨大的switch case语句根据比较不同的常量,处理不同的请求。这种技术有时也将Controller称为处理器,或者屏幕导航器。这种模式的提出者主要是要集中处理j2me里频繁的画面导航。
很多人都觉得,在j2me中将Controller改造成巨大的事务处理器是一个很好的方法。我对此持保留意见。
iFeedback 中简化的MVC
为了大大减少类的数量,iFeedback的作者,将MVC封装到一个类中,用不同的方法来代表对这三者的分离,这种举动证明对减少类的数量又很大帮助。
public abstract class MVCComponent implements CommandListener {
// Set from outside at beginning
public static Display display;
// Returns the screen object from the derived class
public abstract Displayable getScreen();
public Displayable prepareScreen () throws Exception {
if ( getScreen() == null ) {
initModel();
createView();
} else {
updateView();
}
getScreen().setCommandListener ( (CommandListener) this );
return getScreen ();
}
public void showScreen() {
try {
display.setCurrent( prepareScreen() );
} catch (Exception e) {
e.printStackTrace();
Alert a = new Alert("Error in showing screen");
a.setTimeout(Alert.FOREVER);
display.setCurrent(a);
}
}
// Initialize. If a data member is not backed by RMS, make sure
// it is uninitilzed (null) before you put in values.
protected abstract void initModel () throws Exception;
protected abstract void createView () throws Exception;
protected abstract void updateView () throws Exception;
public abstract void commandAction(Command c, Displayable s);
}
因为都在一个类里面,你在也不必被MVC三者之间的关系操心了,这种退化的做法,是对MIDP有限资源的妥协。
我的习惯做法
下面结合我对MVC的理解和大家交流一下。我使用的是一种UML标准的做法,最大程度上对的体现分离的思想。首先和大家交流一下词汇表:
View代表屏幕
View通过预先商定好的接口向Controller索要数据,View同时收集用户的输入,View并不处理这些输入,而是根据不同的输入回调Controller不同的方法。通常View的子类使用UI后缀。
Controller 控制器
提供View调用的接口,负责和model交流。控制器和View共同担负起和用户交流的作用。
Model 泛指一系列的实体对象
需要注意的是我理解的Model并不是屏幕数据的组织单位。Model代表一系列的实体对象。由Controller跟Model交流。我觉得RAD工具中常常将Model代表屏幕数据的集合正式导致MVC概念混乱的一个原因。RAD工具中Model,大体相当于这里的Controller所起的作用。
控制器并不总是联系着Model,有时只是依赖关系。并且Controller往往通过Model的对应的生命期类来获得Model对象。在这种形式中,层层隔离,View与Controller紧密相连,而Model有很高的独立性,可以很好的重用。
一般的结合UML设计的过程,对MVC的各个类有相应的命名习惯。
View 称为Boundary类(边界类) 以UI结尾
Controller 称为 Controller类(控制类) 以Workflow结尾
Model 称为Entity类(实体类) 以Entity结尾或者没有尾缀
Model对应的Lifecycl