构建面向对象的应用软件系统框架.docx
《构建面向对象的应用软件系统框架.docx》由会员分享,可在线阅读,更多相关《构建面向对象的应用软件系统框架.docx(142页珍藏版)》请在冰豆网上搜索。
构建面向对象的应用软件系统框架
构建面向对象的应用软件系统框架
孙亚民
第一部分综述
第1章
本书会讨论什么内容
从软件工程说起。
提起这个概念,往往令人想起CMM、RUP、印度模式等。
管理的因素,在软件开发过程中起着非常重要的作用,然而,软件工程并非只指软件开发的管理工作,而是一个范围很广的综合性学科。
在软件工程中,大约一半的内容是专业性很强的,涉及到软件分析、设计甚至编码的技术。
所谓的结构化、面向对象,都在软件工程的范畴内。
“软件工程范围极为广泛。
软件工程的某些方面属于数学或计算机科学,其他方面可归入经济学、管理学或心理学中。
”
软件业一直在探讨,如何使软件实现如同传统产业一样的大规模生产。
软件工程的提出,便是为了实现这个愿望。
然而,虽然软件工程至今已经有了很大的发展,软件的大规模工业化生产仍然没有实现。
原因何在?
从软件的本质属性来说,软件的复杂性是软件的本质属性,在这个属性没有改变之前,软件便不会实现同传统产业一样的工厂化生产。
从软件生产的介质来说,传统产业生产都是有形的物质产品,人的生产活动都受制于生产资料这些物质介质;然而,软件生产的介质,却是无形的人类的思维。
物质资料的生产,受制于物质本身的属性,不容易为人类的思维所左右,并且容易被大量复制,这使得工业化大生成为可能。
而人类的思维,却是如此的容易变化,更关键的是不能被复制,甚至同一个人,不同时期思维的复制都不可能,这使得软件这个纯粹依赖人的思维活动的生产实现大规模工业化生产是如此的困难。
实际上,不仅仅是软件产业,凡是主要生产介质是人本身的活动的产业,都很难实现工业化生产,如咨询、演艺等。
从生产过程来看,对于传统产业来说,产品的设计和生产是分开的。
在设计阶段,主要的工作是人的思维,因此,在这个阶段,同软件一样,不是批量生产的。
而在生产阶段,主要的对象便是物质资料,并且一切标准已经制定,只需要在流水线上大量复制。
对于传统产业来说,设计和生产的界限是如此的明确,并且,生产和设计的比重是如此的悬殊。
然而,对于软件产业来说,软件的生产过程便是设计的过程,纯粹的生产过程几乎不存在(或许,光盘的复制算是),这使得软件的生产形态同传统产业必然存在区别。
对于软件的开发过程来说,从业务模型、需求分析、系统架构、系统分析和设计、到最后代码实现,越往前,抽象层次越高,可控性越小,越往后,越接近实际,可控性越大,因此,在软件开发中,核心团队的作用是如此巨大,一个软件产品的成败,核心团队的核心人员的作用在很大程度上是决定性的。
对于软件开发来说,如果软件开发要实现工业化生产,必定是从后向前推进,从编码开始。
印度模式或许给出了这么一个例子。
因此,我们在软件工程的路上,只是在不断的向工程化的目标迈进,但是,要达到这个目标,可能会花很长的时间。
技术上的每一次进步,都使我们向这个目标迈进了一步。
在软件工程的发展过程中,技术进步起了非常大,甚至可以说是决定性的作用。
随着采用的技术的不同,所采用的管理方法也在不断变化。
软件工程技术的很多方面,也是为管理做准备的。
优秀的软件开发技术的采用,能够弥补我们在工程化方面的不足,从而使得软件开发更加可控,软件质量更加有保障。
本书不准备讨论软件工程过程的问题,而只是对软件工程中软件技术的一个方面——系统框架设计,做一些探讨。
现在,很多开发人员都已经意识到这很重要的一点,那就是,在开发一个应用软件系统的时候,一个好的系统框架是非常重要的。
从底层开始构建应用程序,是一件吃力不讨好的事情,而没有框架的应用程序,则很难想象会是一个好的应用程序。
除了对于开发的直接帮助,一个好的框架对于公司的知识管理也是非常有意义的。
想象一下,我们经常在讨论,现在是一个知识经济的时代,尤其对于软件公司来说,知识(拥有这些知识的员工)就是公司最大的财富。
那么,怎么来进行有效的知识管理呢?
首先,应当明确,知识管理,一个重要的目的,就是要把员工对公司最重要的知识沉淀下来。
公司的每个员工头脑里都有很多的知识,这些知识对于员工来说是很重要的,但是其重要性同公司并不是完全一致的。
某些知识,对于某个员工来说是最重要的,但是对于公司可能并不需要。
知识管理需要做的,是把员工对公司最重要的知识累积起来。
其次,知识管理必须有一个载体。
如果知识管理没有载体,那么,公司的知识就存在于员工的头脑之中,一旦这个员工离职,那么,知识也就离去了,没有办法沉淀。
如果只是把公司做过的项目的文档作为载体,那么,这个载体就过于零碎了。
实际上,如果公司有一个统一的框架,那么,这个框架就是一个很好的知识管理的载体。
因为,这个框架,必定是集中了公司所有软件项目的共同点的,集中了对于公司最重要的知识的精华,能够为公司所有的项目服务。
另外,随着框架的不断被使用,框架本身也会随之升级优化。
对于一个新成员的加入,他只要理解掌握了这个框架,就可以很好的融入团队中来;而人员的离去,也已经把自己对公司最重要的知识留在了这个框架中。
可以说,在这里,框架承担了一个知识管理平台的作用。
一个最好的例子就是微软的Windows。
这是微软所有知识的最集中的平台。
软件,从本质上来说,就是现实世界在计算机中的模拟。
在考虑应用软件系统架构的时候,实际上,考虑的问题主要在于:
处理什么?
怎么处理?
如何使用?
因此,应用软件系统,需要关注的方面,概括起来,主要包括以下三个大类:
1、处理的对象,也就是数据。
2、处理的方式,也就是我们的系统如何来处理系统的逻辑。
3、如何进行交互,这个交互包括用户(使用者),以及外部系统。
在应用软件系统中,数据是处理的基本对象,程序总是以一定的数据结构来表现数据,并且,在使用面向对象语言开发的系统中,数据总是以类和对象的形式表现出来。
另外一方面,数据总是需要存储,对于大部分应用软件系统来说,通常会采用关系型数据库来保存数据。
这样,由于数据在程序和数据库中表现格式的不一致,就必然要求在两者之间进行映射。
这个映射,在面向对象设计语言和关系型数据库之间,通常称为对象/关系型映射,即O/RMapping。
目前,在O/RMapping部分,在Java平台下,已经有多种可以选择的方案,例如J2EE架构中的EntityBean,轻量级的JDO,以及开源项目的Hibernate等,由于微软的.Net框架推出时间不长,成熟的O/RMapping框架并不多见。
O/RMapping框架的选择或者设计是构建应用软件系统的最基本的工作。
本书将讨论构建O/RMapping框架的一些基本理论、概念和方法。
系统的业务逻辑处理,是应用软件系统的核心部分,如何合理的构建业务逻辑、如何提供业务逻辑层的服务,以及表现层如何访问业务逻辑提供的功能,也是应用软件系统需要重点关注的问题。
在这个方面,业界已经发展了很多可供选择的范式,如契约式设计、SOA架构(面向服务的架构)等。
这些方法指明了设计的方向,同时也需要我们在实际开发中加以应用。
在业务逻辑确定后,随后而来的问题就是,如何向客户端来提供业务逻辑服务,或者说,客户端如何访问这些服务。
在多层应用软件系统中,客户端和业务逻辑在物理上可能存在于不同的机器上,也可能存在于同一台机器,但至少,在逻辑上,是存在于两个不同部分,这就涉及到一个问题:
这两个层之间如何进行通信?
还会涉及到远程过程调用的问题。
当然,现在我们已经有多种技术来远程过程调用,包括Webservice、.NetRemoting、Corba、甚至EJB等。
如此多的实现技术,带来的很大的灵活性,但同时也带来了问题,其中一个就是,有多少种服务端技术,就得有多少种相应的客户端访问技术。
甚至,在某些分布式应用系统中,应用逻辑使用不同的技术开发,存在于不同的机器上,有的存在于客户机本机,有的使用.NetRemoting开发,存在于局域网内,有的使用因特网上的WebService,有的时候,我们希望相同的业务逻辑能够支持不同的客户端。
在这种情况下,我们需要一个一致的服务访问编程模型,以统合不同的服务访问模式,简化系统的开发和部署。
一个统一的远程过程调用框架的前景是如此的诱人,以至于每一种方法都试图一统天下,但出于种种原因,最终都没有一家能够做到,最新的WebService就力图做到这一点。
实际上,每一种方法的出现,最终都会带来一个副作用,那就是,可供选择的多了一点,混乱也就又多了一点。
在实际的开发过程中,我们也需要一个统一的访问方式来解决这个问题。
本书将讨论一些可用的方案。
为了更加清晰的进行表述,文章会附加一些程序代码。
因为在讲到具体的技术的时候,本书会对各种可用的技术进行比较,因此,本书的代码可能会使用不同的语言,通常是Java和C#,不过,在给出代码的时候,一般都会指明所用的语言。
在大部分情况下,如果不说明具体的语言,那么就是C#(因为我比较喜欢这门语言)。
因为Java和C#的语法是如此的相像,我想,对有经验的程序员来说,这应该不会造成阅读上的麻烦。
第2章
系统的分层结构
2.1.简述
我们在解决一个复杂的问题的时候,通常使用的一个技巧就是分解,把复杂的问题分解成为若干个简单的问题,逐步地、分别地解决这几个小问题,最后就把整个问题解决掉。
在设计一个复杂的软件系统的时候,同样的,为了简化问题,我们也通常使用的一个技术就是分层,每个层完成自身的功能,最后,所有的层整合起来构成一个完整的系统。
分层是计算机技术中的常用方法,一个典型的例子就是TCP/IP技术的OSI七层模型。
在应用软件开发中,典型的就是N层应用软件模型。
N层的应用软件系统,由于其众多的优点,已经成为典型的软件系统架构,也已经为广大开发人员所熟知。
在一个典型的三层应用软件系统中,应用系统通常被划分成以下三个层次:
数据库层、应用服务层和用户界面层。
如下图(图2.1)所示:
图2.1
其中,应用服务层集中了系统的业务逻辑的处理,因此,可以说是应用软件系统中的核心部分。
软件系统的健壮性、灵活性、可重用性、可升级性和可维护性,在很大程度上取决于应用服务层的设计。
因此,如何构建一个良好架构的应用服务层,是应用软件开发者需要着重解决的问题。
为了使应用服务层的设计达到最好的效果,我们通常还需要对应用服务层作进一步的职能分析和层次细分。
很多开发者在构建应用服务层的时候,把数据库操纵、业务逻辑处理甚至界面显示夹杂在一起,或者,把业务逻辑处理等同于数据库操纵,等等,这些,都是有缺陷的做法。
我们将就在这个方面进行设计时可采用的方案进行一些探讨。
在一个分布式应用系统中,整个系统会部署在不同的物理设备上,如上面所示的三层体系,用户界面和应用服务器可能在不同的设备上,这就涉及到不同机器之间的通信问题,也就是层间的通信和交互问题。
我们已经有了很多可以用于分布式远程访问的技术,如CORBA,在Java平台上,我们还有JavaRMI、EJB,在Windows平台上,从DCOM到COM+,再到.Net下的WebService和.NetRemoting等。
如何选用合适的远程访问技术,也是我们在系统框架中需要考虑的问题。
[6]
为了使讨论更具有针对性,本文也会讨论一些比较流行的系统架构,例如J2EE架构,以及JDO,然后,我们会讨论Websharp在这个方面的一些设计理念。
2.2.设计的原则和评判标准
同软件工程的原则一样,应用服务层的设计,必须遵循的最重要的原则就是高内聚和低耦合[7]。
软件分层的本来目的,就是提高软件的可维护性和可重用性,而高内聚和低耦合正是达成这一目标必须遵循的原则。
尽量降低系统各个部分之间的耦合度,是应用服务层设计中需要重点考虑的问题。
内聚和耦合,包含了横向和纵向的关系。
功能内聚和数据耦合,是我们需要达成的目标。
横向的内聚和耦合,通常体现在系统的各个模块、类之间的关系,而纵向的耦合,体现在系统的各个层次之间的关系。
系统的框架,通常包含了一系列规范、约定和支撑类库、服务。
对于如何判断一个软件的系统框架的优劣,笔者认为,可以从以下几个方面来评判:
◆系统的内聚和耦合度
这是保证一个系统的架构是否符合软件工程原则的首要标准。
◆层次的清晰和简洁性
系统每个部分完成功能和目标必须是明确的,同样的功能,应该只在一个地方实现。
如果某个功能可以在系统不同的地方实现,那么,将会给后来的开发和维护带来问题。
系统应该简单明了,过于复杂的系统架构,会带来不必要的成本和维护难度。
在尽可能的情况下,一个部分应该完成一个单独并且完整的功能。
◆易于实现性
如果系统架构的实现非常困难,甚至超出团队现有的技术能力,那么,团队不得不花很多的精力用于架构的开发,这对于整个项目来说,可能会得不偿失。
简单就是美。
◆可升级和可扩充性
一个系统框架,受设计时技术条件的限制,或者设计者本人对系统认识的局限,可能不会考虑到今后所有的变化。
但是,系统必须为将来可能的变化做好准备,能够在今后,在目前已有的基础上进行演进,但不会影响原有的应用。
接口技术,是在这个方面普遍应用的技巧。
◆是否有利于团队合作开发
一个好的系统架构,不仅仅只是从技术的角度来看,而且,它还应该适用于团队开发模型,可以方便一个开发团队中各个不同角色的互相协作。
例如,将Web页面和业务逻辑组件分开,可是使页面设计人员和程序员的工作分开来同步进行而不会互相影响。
◆性能
性能对于软件系统来说是很重要的,但是,有的时候,为了能让系统得到更大的灵活性,可能不得不在性能和其他方面取得平衡。
另外一个方面,由于硬件技术的飞速发展和价格的下降,性能的问题往往可以通过使用使用更好的硬件来获得提升。
2.3.应用服务层的内容
应用服务层,通常也被称为业务逻辑层,因为这一层,是应用软件系统业务逻辑处理集中的部分。
然而,我将这一层称为应用服务层,而不称业务逻辑层,因为,这一层需要处理的不仅仅是业务逻辑,还包含了其他方面的内容。
从完整的角度来说,应用服务层需要处理以下内容:
◆数据的表示方式
数据,是软件处理的对象。
从某种程度上来说,"软件,就是数据结构加算法"的说法,是有一定意义的。
在面向对象的系统中,数据是用类来表示的,代表了现实世界实体对象在软件系统中的抽象。
考虑所谓的MVC模式,这个部分的类属于M--实体类的范畴。
由于应用软件通常会使用数据库,数据库中的数据,可以看成是对象的持久化保存。
由于数据库一般是关系型的,因此,这个部分,还需要考虑类(对象)同关系型数据的映射,即通常所说的O-RMAP问题。
◆数据的存取方式
如同上述所说,软件系统处理的实体对象数据需要持久化保存数据库中,因此,我们必须处理系统同数据库的交互,以及数据的存取和转换方式的问题。
◆业务逻辑的组织方式
在面向对象的系统中,业务逻辑表现为对象之间的交互。
有了上述的实体对象,以及对象的保存策略,就可以将这些对象组合起来,编写我们的业务逻辑处理程序。
在业务逻辑的处理中,必须保证处理的正确性和完整性,这将会涉及到事务处理。
通常,我们也会把业务逻辑封装成组件的形式,以得到最大的可重用性。
◆业务服务的提供方式
在我们完成系统的功能后,如何向客户提供服务,是我们需要考虑的问题。
这里的客户,不仅仅是指软件的使用者,也包括调用的界面、其他程序等。
例如,在一个基于Web的ASP.Net或JSP系统中,业务逻辑功能的客户便是这些ASP.Net页面或JSP页面。
业务逻辑组件应该通过什么方式,直接的,或间接的,向这些客户提供服务,是这一层需要完成的任务。
◆层的部署和层间交互
对于一个多层的应用软件系统来说,尤其是大型的应用软件系统,通常需要把不同的部分部署在不同的逻辑或物理设备上。
特别是一些基于Web的应用软件系统,其部署工作将涉及到Web服务器、组件服务器、数据库服务器等不同的服务设备。
在进行应用软件架构的设计的时候,必须考虑各种不同的部署方案。
当系统需要进行分布式访问的时候,如何统一和简化分布式系统的开发,便成了系统框架需要考虑的内容。
综上所述,一个完整的基于Web的应用软件系统,其架构可以用图2.2来表示(Websharp的应用软件系统架构):
图2.2
对于以上各个方面来说,每个问题都可以有很多种策略和方案,但是,在一个系统中,应该尽可能的统一这些策略和方案。
也就是说,在一个系统,或者一个项目中,应该统一每个解决每个问题所采用的方法。
软件的开发方法是灵活的,可以用不同的方法解决相同的问题,这会诱使开发人员采用他们认为能够表现自己的方法,但是,从整个系统来看,这将会是灾难性的。
我们应该尽可能统一,就是,采用统一的数据表示方式、统一的数据存取方式、统一的业务逻辑处理方式等。
下面,将就这些部分的设计策略和可用方案进行一些比较详细的论述。
2.4.数据实体的表示
应用软件系统,从本质上来说,是计算机对现实世界的模拟。
现实世界中的实体对象,在软件系统中,表现为需要处理的数据。
在面向对象的系统中,这是通过“类"和”对象"来表示的。
参考著名的“MVC”模式[8],类可以分成实体类(M)、控制类(C)、和边界类(V),分别代表了实体对象、控制和界面显示。
系统中需要处理的数据,在面向对象的系统中,属于实体类部分。
在考虑数据实体层的设计策略的时候,需要把握以下要点:
◆一致的数据表示方式。
在一个系统中,数据的表示方式必须尽可能统一,同时,在处理单个数据和多个数据的时候,处理方式尽可能一致。
◆因为数据通常是需要存储到数据库中,因此,良好的映射方法是必需的。
◆处理好对象的粒度,即所谓的粗粒度对象、细粒度对象。
一般例子
考虑一个现实的例子,一个仓库中的产品(Product),在系统中可以使用如下定义:
publicclassProduct
{
publicstringName;//名称
publicdecimalPrice;//价格
publicintCount;//数量
}
可以按照如下方法使用Product类:
Productp=newProduct();
//……处理Product
这是一个包含了三个属性的Product类的定义。
为了便于说明,在这里,我们尽量将问题简化了。
又例如,一张入库单可以使用如下定义:
publicclassForm
{
publicstringID;//入库单编号
publicDateTimeAddTime;//入库时间
publicFormDetail[]FormDetails;//入库单明细
}
publicclassFormDetail
{
publicProductInProduct;//入库产品
publicintCount;//入库数量
}
对于处理单个对象,通常采用上述的方法,但是,当我们需要处理相同类的一组对象,也就是处理一个对象集合的时候,就会有一些小小的麻烦。
如前所述,我们希望在处理单个对象和对象集合的时候,处理的方式尽量统一,这对于软件开发的意义是很大的。
常用的处理对象集合的方法有:
◆数组表示的方法
例如,上面的例子中当一张入库单包含多条入库单明细的时候采用的方法。
为了灵活性,也可以使用容器来,如Java中的Vector或C#的ArrayList(C#)。
只是,在处理对象的时候,需要一个类型转换的操作。
这个问题,在支持泛型的语言中不会存在,如使用C++的标准库的容器类。
◆ObjectCollection方法。
这个方法同上面的方法类似,不同之处在于,为每个实体类设计一个Collection类。
例如,可以为FormDetail设计一个FormDetailsCollection类(C#):
publicclassFormDetailsCollection:
ArrayList
{
publicvoidAdd(FormDetaildetail)
{
base.Add(detail);
}
publicnewFormDetailthis[intnIndex]
{
get
{
return(FormDetail)base[nIndex];
}
}
}
这么做的好处在于,在操作集合中的对象时,不必进行类型转换的操作。
◆数据集的表示方法。
采用这种方法,通常是直接把从数据库查询中获取的数据集(Recordset)作为数据处理对象。
这种方法在ASP应用程序中是非常常见的做法。
这种做法简单,初学者很容易掌握,但是他不是一种面向对象的方法,弊病也很多。
EJB的方法
在J2EE体系中,对实体对象的处理的典型方法是EntityBean。
J2EE中使用EntityBean来表示数据,以及封装数据的持久化储存(同数据库的交互)。
由于EntityBean比较消耗资源,而且采用的是远程调用的方式来访问,因此,在需要传递大量数据,或者在不同的层次之间传递数据的时候,往往还会采用一些诸如"值对象"(ValueObject)的设计模式来提升性能。
关于J2EE中的设计模式的更多内容,可以参考《J2EE核心模式》一书。
[9]
JDO的方法
相对于J2EE这个昂贵的方法来说,JDO提供了一个相对"轻量级"的方案。
在JDO中,你可以采用一般的做法,编写实体类,然后,通过一些强化器对这些类进行强化,以使其符合JDO的规范,最后,你可以通过PersistenceManager来实现对象的持久化储存。
[10]
无论是EJB还是JDO,在同数据库进行映射的时候,都选用了XML配置文件的方式。
这是一种灵活的方式。
由于XML强大的表达能力,我们可以很好的用它来描述代码中的实体类和数据库之间的映射关系,并且,不用在代码中进行硬编码,这样,在情况发生变化的时候,有可能只需要修改配置文件,而不用去修改程序的源代码。
关于EJB和JDO的配置文件的更多的信息,各位可以参考相关的文档,这里不再赘述了。
然而,使用XML配置文件的方式并不是唯一的方法,在微软提供的一些案例中,如Duwamish示例[11],就没有采用这种方式。
至于开发人员在开发过程中具体采用哪种方式,是需要根据具体情况进行权衡和取舍的。
Websharp的方法
Websharp在数据的表现上,充分利用了.NetFramework类库中DataSet和特性(Attribute)的功能。
我们设计了一个EntityData类,这个类继承了DataSet,并增加了一些属性和方法。
在Websharp中,当表示一个实体类的时候,需要定义一个抽象类,这个抽象类继承PersistenceCapable。
例如,一个Schdule类可以表示如下:
[TableMap("Schdule","GUID")]
[WebsharpEntityInclude(typeof(Schdule))]
publicabstractclassSchdule:
PersistenceCapable
{
[ColumnMap("GUID",DbType.String,"")]
publicabstractstringGUID{get;set;}
[ColumnMap("UserID",DbType.String,"")]
publicabstractstringUserID{get;set;}
[ColumnMap("StartTime",DbType.Dat