1.MVC 设计模式
MVC架构是"Model-View-Controller"的缩写,中文翻译为"模型-视图-控制器"。
MVC与J2EE架构的对应关系是:View处于Web Tier或者说是Client Tier,通常是JSP/Servlet,即页面显示部分。Controller也处于Web Tier,通常用Servlet来实现,即页面显示的逻辑部分实现。Model处于Middle Tier,通常用服务端的javaBean或者EJB实现,即业务逻辑部分的实现。
1.视图(View)代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML、XML和Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。比如一个订单的视图只接受来自模型的数据并显示给用户,以及将用户界面的输入数据和请求传递给控制和模型。
2.
模型(Model):就是业务流程/状态的处理以及业务规则的制定。业务流程的处理过程对其它层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。业务模型的设计可以说是MVC最主要的核心。目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个顶级类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。
业务模型还有一个很重要的模型那就是数据模型。数据模型主要指实体对象的数据 保存(持续化)。比如将一张订单保存到数据库,从数据库获取订单。我们可以将这个模型单独列出,所有有关数据库的操作只限制在该模型中
3.控制(Controller)可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。例如,用户点击一个连接,控制层接受请求后, 并不处理业务信息,它只把用户的信息传递给模型,告诉模型做什么,选择符合要求的视图返回给用户。因此,一个模型可能对应多个视图,一个视图可能对应多个模型。
模型、视图与控制器的分离,使得一个模型可以具有多个显示视图。如果用户通过某个视图的控制器改变了模型的数据,所有其它依赖于这些数据的视图都应反映到这些变化。因此,无论何时发生了何种数据变化,控制器都会将变化通知所有的视图,导致显示的更新。这实际上是一种模型的变化-传播机制。模型、视图、控制器三者之间的关系和各自的主要功能,如图1所示。
----------------------------------------------------------------------------------------------------------------------------------------------------------
2.Model1/Model2
我们在开发Web应用时经常提到的一个概念是Model1/Model2,那么到底它是什么意思呢?其实它是对采用JSP技术构成Web应用的不同模型的描述。下面对这个概念做一个简单的介绍。
Model 1
在使用JAVA技术建立Web应用的实例中,由于JSP技术的发展,很快这种便于掌握和可实现快速开发的技术就成了创建Web应用的主要技术。JSP页面中可以非常容易地结合业务逻辑(jsp:useBean)、服务端处理过程(jsp:let)和HTML(),在JSP页面中同时实现显示,业务逻辑和流程控制,从而可以快速地完成应用开发。现在很多的Web应用就是由一组JSP页面构成的。这种以JSP为中心的开发模型我们可以称之为Model1。
当然这种开发模式在进行快速和小规模的应用开发时,是有非常大的优势,但是从工程化的角度考虑,它也有一些不足之处:
应用的实现一般是基于过程的,一组JSP页面实现一个业务流程,如果要进行改动,必须在多个地方进行修改。这样非常不利于应用扩展和更新。
由于应用不是建立在模块上的,业务逻辑和表示逻辑混合在JSP页面中没有进行抽象和分离。所以非常不利于应用系统业务的重用和改动。
考虑到这些问题在开发大型的Web应用时必须采用不同的设计模式――这就是Model2
Model 2
Model 2表示的是基于MVC模式的框架。MVC是Model-View-Controller的简写。“Model”代表的是应用的业务逻辑(通过JavaBean,EJB组件实现),“View”是应用的表示面(由JSP页面产生),“Controller”是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。从而弥补了Model1的不足。
Model2具有组件化的优点从而更易于实现对大规模系统的开发和管理,但是开发StrutsMVC系统比简单的JSP开发要复杂许多,它需要更多的时间学习和掌握。同时新东西的引入会带来新的问题(这让我想起来关于“自动计算”的一篇文章,中间提到为了降低系统的复杂度,却导致更高的复杂度)。
必须基于StrutsMVC组件的方式重新思考和设计应用结构。原来通过建立一个简单的JSP页面就能实现的应用现在变成了多个步骤的设计和实现过程。
所有的页面和组件必须在Struts MVC框架中实现,所以必须进行附加地开发工作。
StrutsMVC本身就是一个非常复杂的系统,所以采用StrutsMVC实现Web应用时,最好选一个现成的MVC框架,在此之下进行开发,从而取得事半功倍的效果。现在有很多可供使用的MVC框架,由于Struts有完整的文档并且相对来讲比较简单,所以用它开发MVC系统还是比较方便地。
---------------------------------------------------------------------------------------------------------------------------------------------------------
3.两种设计模式
(1)
l
简单工厂模式定义

创建模式包括:
Factory Method | 工厂方法模式 |
Abstract Factory | 抽象工厂模式 |
Builder | 构造器模式 |
Protoype | 原型模式 |
Singleton | 单例模式 |
1.简单工厂模式(Simple Factory Pattern)
严格来说,简单工厂模式并不属于GoF23种经典设计模式之一,但是简单工厂作为一个常用的编程习惯,在实际程序开发中经常被使用到,能为我们简单而有效的解决一些问题。同时,作为后续其他创建模式的引导,有必要先讲一下。
目的:
通过一个外界参数来选择不同类的实例。
场景:
在《星际争霸》中,Jim Raynor(游戏剧情中的人族英雄)现在担任一个Terran突击小分队的队长,在与Zerg的战斗中,执行侦查和歼灭落单敌人的任务。根据当前的敌军的兵种状况,Terran后勤部门能提供两种战斗部队:Marine(机枪兵)和Firebat(喷火兵),分别能对Zerg的Hydralisk(刺蛇)和Zergling(小狗)造成极大的伤害。但是如果顺序反过来,则喷火兵无法战胜刺蛇,机枪兵也无法战胜小狗。
分析:
先介绍两种战斗兵种的UML图和代码,本文后面讨论的“产品”就是指代他们。
图1.1 战斗部队(产品)的UML图
最初,Raynor直接向后勤部门申请了3个机枪兵和2个喷火兵的固定部队来面对战场的情况(战场用一个随机数字来模拟遇到的敌军)。如果用这种方式进行战斗的话……
结果只能考验运气,运气不好的时候,下场那是相当的凄凉……
图 1.2
显然,以固定的业务逻辑应付不断的业务变化是不可能完成任务的。为了掌握主动,夺取胜利,我们必须根据不同的敌人来选择不同的战士,现在,代码变成这样:
这样的代码,看起来似乎能达到我们的目的,但是我们写代码,追求的不应当仅仅是解决一个具体问题,而是追求优雅和高质量的代码。当前,代码中一个很明显的问题就是,我们无法分辨Raynor到底是一个战队小队指挥官还是后勤部长?一个战场指挥官的职责是关心士兵如何战斗,而不是去关心士兵装备的是机枪还是喷火器,那是后勤部长的职责!如果将这两项职责混合到同一个人(同一段代码)中,日后无论是遇到新的敌人(业务逻辑的改变),还是有了新的士兵或者士兵要装备新的装备(产品的增加和产品变化)都需要修改这段代码。
在长期的生产实践之中的之中,有许多被实践证明有益的设计原则,其中有两条应该在写类似代码的时候想起:“简单职责原则:一个类负责一件事情,一个类应该只有一个能引起他变化的因素。”和“变化点隔离原则:如果一块代码不同地方都有可能经常发生改变,则说明它们应该分开封装,同时避免超大类、超大方法的产生。”
基于上面两个原则,我们应该把指挥官和后勤部长分开,各自完成自己的任务,代码变成这样:
现在的运行结果:
图 1.3
总结:
从上面一个如何构建5人突击小队的编码过程中,我们看到简单工厂模式的优点:隔离产品创建者和使用者,明确各自的职责,降低耦合度。简单工厂模式的应用场景:当使用者知道工厂需要的传入参数,并且对工厂如何生产出产品的逻辑并不关心。
简单工厂模式能为我们处理一些很经常遇到的代码场景,但同时要引用上文的一句话“以固定的业务逻辑应付不断的业务变化是不可能完成任务的”,设计模式也是如此,任何一个模式都不能解决所有的问题。现在游戏才刚刚开始,随着星际争霸战争过程的发展,后面将会遇到越来越复杂的场面。
同时,Raynor使用了设计模式,在小规模战斗中表现出色,升级为更高级军官,掌握更多的战斗部队,面对更多不同的敌人时候,简单工厂的缺点就逐渐显露出来:譬如只能根据事先考虑到的敌人来只能创建事先考虑到的部队、譬如随着机械化部队的加入,要通过单一的一个工厂来生产产品就显得无能为力……且看下一章,如何使用工厂方法模式(Factory Method Pattern)来解决这些问题。
上一章介绍了简单工厂模式的作用和使用场景,它能为我们隔离开产品使用者与产品制造者的职责,使得这两部分代码脱离耦合。
在看到简单工厂模式带来的好处的同时,我们也可以预见,随着工厂产品的丰富,每增加一个产品,都将需要修改工厂方法。这是简单工厂的特性——只能处理可预见的情况决定的。然而,频繁的修改代码是我们所不愿意看到的,通过修改代码来增加产品(功能),也违反了设计模式中的“开闭原则” 。
开闭原则:即“开放-闭合原则”,软件架构应该设计成对于修改来说,代码实体是关闭的,而对于扩展来说,代码实体则是开放的。通俗一点讲,开闭原则是鼓励通过“新增”来代替“变化”。鼓励设计者将业务逻辑抽象化,将逻辑的本质设计成抽象类或者接口,逻辑的具体实践操作设计成具体实现类。
现实世界中,业务的本质是相对稳定的,而业务的具体实践则总会不断变化。在逻辑改变的时候,应当做到可以只替换具体实现类,而会不影响到代码的架构。开闭原则是OOAD中的核心原则,在实现功能需求的时候,新增的代码与修改的代码之间的比值,某种程度上可以作为量化衡量一个软件系统的成熟程度的标志。
为了解决简单工厂的这个弊端,需要引入本章的主题:工厂方法模式。工厂方法模式又称为工厂模式,也叫虚拟构造器(Virtual Constructor),工厂方法模式主要是对“工厂”的封装,提供一个创建者的接口,将产品实例化的步骤延迟到工厂的子类完成,让子类决定需要创建什么产品。
目的:
定义创建者的抽象接口,延迟产品实例化到其子类之中,创建的产品一般不再通过传入参数控制,而直接决定于使用了哪个实现类作为其子类。
场景:
随着游戏的发展,只靠机枪兵、喷火兵越来越难以获得战斗胜利了。这时候,机械化的重型武器登上了战争的舞台,战场上的主角,不再只是从兵营一个地方生产出来。司令部为了检验Raynor的设计模式是否能在机械化混合部队中发挥作用,特别举行了一次阅兵仪式……
为了简单起见,本次阅兵需要检阅仅两种部队:从兵营生产出来的机枪兵以及从重工厂生产出来的坦克。无论从哪里生产出来,他们都是Terran的部队,实现了ITerranArmy接口,对于本次阅兵来说,此接口只有一个showState()方法。
分析:
照例,我们先看一下这次需要涉及到的产品类图及代码:
图2.1 战斗部队及工厂的UML图
如果现在继续使用简单工厂来完成这个需求的话,随着各种兵种的登场,工厂类的代码将会出现一长串的if语句,而且每增加一种兵种,就必须修改一次工厂类。显然,根据上面的讨论,这样的代码是不可忍受的。
根据当前的实际情况,Raynor对简单工厂做出了一些扩展:首先建立后勤部,它不负责生产任何部队,但是任何部队的生产都是由后勤部直接领导,在程序语言中,我们把它叫做“抽象工厂”,在本次实例中,我们把它叫做“ITerranFactory”接口,无论是兵营还是重工厂,都是此接口的一个实现类,在程序语言中,我们把这些实现类叫做“具体工厂”。而场景描述中提到的ITerranArmy接口,在程序语言中则成为“抽象产品”,要生产的机枪兵、坦克都是此接口的实现,成为“具体产品”。
抽象工厂和具体工厂的实现代码如下:
在阅兵的时候,调用代码如下:
从代码上可以看出,我们需要什么产品,不再是靠外界传入参数决定,而是取决于抽象工厂使用的是具体哪个实现类。
值得指出的是,虽然示例代码中是直接通过new来创建具体工厂,但在现实编程中,大部分工厂方法模式都是配合这反射来使用的,既可以考虑把“SoldierFactory”、“MachineFactory”等具体实现类写在配置文件中,通过反射代替直接new一个具体工厂对象,这样当增加一个产品,就可以做到对工厂和使用者都不存在任何修改,只需要添加一个新的工厂类,并且配置到配置文件中即可,这就是开闭原则的一个体现。
最终运行结果如下:
总结:
通过上述阅兵的例子,我们看到工厂方法模式确实给系统架构带来强大而灵活的扩展能力。这种基于多态性设计的模式,使得产品的实例化延迟到具体子类之中,通过替换不同的实现子类就能达到新增和切换产品的目的。
由于本次阅兵效果良好,司令部决定让Raynor领导这些部队对Zerg进行一次大规模的协同作战。前面在简单工厂、工厂方法两种模式中,主要都是围绕这个体产品(Marine、Firebat、Tank等)的创建进行讨论,下一章中,我们将会通过一次“协同作战”看看各种具体对象之间存在互相依赖关系时,如何运用抽象工厂模式(Abstract Factory Pattern)来解决问题。
3.抽象工厂模式(Abstract Factory Pattern)
前面介绍的“简单工厂模式”和“工厂方法模式”,立足点都是避免显式的创建具体对象,封装创建对象时可能出现的变化点,这已经能比较好的解决单个对象创建的问题,但实际业务中,还经常出现需要一系列对象互相关联使用来完成任务的情况。对于存在关联、以来的产品来说,使用简单工厂或者工厂方法一个一个的创建其中的具体产品,经常无法达到理想的效果。譬如,“西装”和”短裤”都分别实现了“上衣”,“裤子”接口,分别套在身上一点问题都没有,但是实际上却不太可能穿着西装和短裤上街。
通过简单工厂或者工厂方法一个一个地单独创建产品然后再合并使用除了有可能出现上面“西服+短裤”的笑话外,用户在使用组合产品的时候,通常是不关心产品具体成员的创建过程的,因此也不应当把各个产品的创建过程揉合到具体逻辑之中。为了解决上面的问题,就GoF经典设计模式中就出现了本章的主角——抽象工厂模式。
目的:
创建一系列互相关联或者互相以来的组合产品。
场景:
随着游戏的发展,前期小打小闹的试探进攻时期已经过去。我们要考虑的问题也已经不再是一个机枪兵能否战胜一只小狗、一个火兵能否打赢一只刺蛇(见简单工厂模式)了。游戏中期要占得先机,关键不在一兵一卒,而在战术正确与否。
星际争霸多年的游戏发展中,出现过无数天马行空的战术和激动人心的战例。其中,在Terran在对抗Terran和Zerg时,有两种主流战术如下:
TvT:主力兵种使用坦克,维护兵种是SCV,空军选择Wraith(隐飞)
TvZ:主力兵种使用枪兵,维护兵种是Medic(护士),空军选择Science Vessel(科学球)
分析:
只要对手的种族一确定,我们的战术就定下来,也就是选择的兵种就定下了。出战的时候,都是框一圈兵A过去(-_-#),并不关心里面某个兵是什么时候、如何被生产出来的,关心的只是这组部队能不能优势互补,赢得战争。这符合抽象工厂的使用场景。现在,就通过抽象工厂模式,看看上述两种战术中的兵种组合是如何使用代码演绎的。
照例,我们先看一下这次需要涉及到的工厂和产品类图及代码:
图3.1 战斗部队及工厂的UML图
在上述代码中,“IAttackUnit、IAirUnit、IMedicalUnit”是抽象产品,实现这三个接口的“Tank、SCV、Marine……”等则是具体产品,关于抽象产品、具体产品等概念在前一章已经介绍过。现在我们继续看看如何通过工厂来为抽象产品选择具体的实现类的。
抽象工厂和具体工厂的实现代码如下:
客户端调用代码如下:
从代码上可以看出,每一个具体工厂的目的都不是创造单个的具体产品,而是过把整个产品家族(IAttackUnit、IAirUnit、IMedicalUnit)的所有具体产品都创造出来,这样各个具体产品之间的联系就通过一个具体工厂来体现。当需要新增一个产品家族,或者已有产品家族的构成成员发生变化的时候,影响也就局限在了某一个具体工厂之中。业务逻辑所依赖的都是抽象工厂和抽象产品,具体产品变化的影响就这样从业务逻辑中隔离开来。
这种通过依赖抽象类(或者接口)来削弱耦合、隔离变化点的方法,在设计模式中被总结为一个重要原则——依赖倒转原则:业务逻辑应该依赖于抽象类,而不应该依赖于具体实现类。“依赖倒转”中的“倒转”,指得就是人们直观思维之中,高层逻辑应当依赖与他下面的功能点分解,功能点分解又依赖于底层功能这种自上而下层层依赖的开发思想。这种开发思想中,任何底层的变化都有可能导致产生对上层的影响,同时,上层需求有改动的时候,又同样需要下层改动提供支持,这样当软件层次累计得越来越高的时候,变化就越来越可能引起上下层间的反复传递,导致严重的后果——牵一发而动全身。
面向对象中耦合关系有三种:零耦合、抽象耦合、具体耦合。依赖反转原则的本质意图就是将具体耦合削弱为抽象耦合。从下面两张图上可以看出,在互相依赖的模块中加入一层“抽象层”后,上层业务逻辑、下层具体实现的变化都受抽象类的隔离,变化就相对不容易在各层之间扩散。
图3.2 自上而下的依赖关系
图3.3 依赖反转
如果说开闭原则(见第二章)是设计模式追求的目标,那依赖反转原则就是设计模式的最基本、普遍的解决问题思路。从面向具体类编程到面向接口编程的转变,是一个程序员从代码实现者到设计者进化的标志。
最后,展示一下程序的最终运行结果:
图 3.2 两种兵种组合
总结:
经过三章关于“工厂模式”的介绍,我们对通过工厂来创建产品,隐藏具体实现和创建细节的做法已经有了一定的了解。对象的产生,除了直接使用new和使用工厂封装外,还有几种其他方式。在这边Terran部队建设如火如荼的时候,且看看那边Zerg的发展情况如何,看看其他几种创建模式是如何在Zerg中发挥作用的。