l转自:http://www.lvjiyong.com/books/OODesigner/孙亚民目录第一部分综述4第1章本书会讨论什么内容5第2章系统的分层结构82.1.简述82.2.设计的原则和评判标准92.3.应用服务层的内容102.4.数据实体的表示112.5.数据的存取方式152.6.业务逻辑的处理182.7.业务服务的提供202.8" />

构建面向对象的应用软件系统框架

系统 1954 0
<style type="text/css"> <!-- body { margin-top: 0px; } --> </style>
l
转自:http://www.lvjiyong.com/books/OODesigner/
孙亚民
目录
第一部分 综述 4
第1章本书会讨论什么内容 5
第2章系统的分层结构 8
2.1.简述 8
2.2.设计的原则和评判标准 9
2.3.应用服务层的内容 10
2.4.数据实体的表示 11
2.5.数据的存取方式 15
2.6.业务逻辑的处理 18
2.7.业务服务的提供 20
2.8.层的部署和层间交互 20
2.9.剪裁和取舍 21
2.10.小结 21
第二部分 应用服务层的设计 23
第3章数据和对象 24
3.1数据的形态 24
3.2对象/关系型映射 26
3.3对象的状态 28
Transient 28
Persistent-new 29
Persistent-dirty 29
Persistent-clean 29
Persistent-deleted 29
第4章 O/R Mapping的一般做法 31
第5章设计一个O/R Mapping框架 40
5.1封装数据库访问层 40
5.2设计映射 48
5.3 对继承的支持 55
5.4设计对象操纵框架 61
5.5实现对象操纵框架 66
第6章面向方面编程 71
6.1 AOP概念 71
6.2 Websharp AOP的使用 73
6.2.1.使用AOP实现松散耦合 73
6.2.2.使用AOP组合两个业务逻辑 76
6.3 Websharp AOP的实现 76
6.3.1 AspectObject抽象类 78
6.3.2 IAspect接口 78
6.3.3 AspectManagedAttribute 78
6.3.4 定义AspectProxy类 80
6.3.5 其他一些辅助类 80
6.3.6 配置文件 80
6.4 关于AOP和过滤器 81
6.5 小结 82
第7章接口 83
第8章事务处理 86
8.1 事务的基本概念 86
8.2 实际开发中可用的事务处理方式 88
第9章性能优化 101
第三部分 用户界面层设计 102
第10章 界面层的功能划分 103
第11章 界面设计模式 104
11.1 MVC模式 104
11.2 页面控制器 107
第12章 动态代码生成和编译技术 108
12.1 Emit 108
12.2 CodeDom 108
第13章 远程过程访问的客户端整合 111
Web Service 111
.Net Remoting 112
Websharp Service Locator的主要接口 114
Websharp Service Locator的配置文件 114
如何使用Websharp Service Locator 116
LocalAssemblyLocator 的Hello World例子 116
Hello World 的WebServiceLocator例子 118
Websharp Service Locator的实现 120
目前的进展 120
将来的目标 120
小结 120
第14章 智能客户端 122
小结 128
第四部分 系统建模过程 129
第15章 简述 130
第16章 用例模型——系统需求的获取 131
第17章 分析模型——开发者的视野 135
第18章 系统设计——实现方案 141


本书会讨论什么内容
从软件工程说起。提起这个概念,往往令人想起CMM、RUP、印度模式等。管理的因素,在软件开发过程中起着非常重要的作用,然而,软件工程并 非只指软件开发的管理工作,而是一个范围很广的综合性学科。在软件工程中,大约一半的内容是专业性很强的,涉及到软件分析、设计甚至编码的技术。所谓的结 构化、面向对象,都在软件工程的范畴内。“软件工程范围极为广泛。软件工程的某些方面属于数学或计算机科学,其他方面可归入经济学、管理学或心理学中。”
软件业一直在探讨,如何使软件实现如同传统产业一样的大规模生产。软件工程的提出,便是为了实现这个愿望。然而,虽然软件工程至今已经有了很大的发展,软件的大规模工业化生产仍然没有实现。原因何在?
从软件的本质属性来说,软件的复杂性是软件的本质属性,在这个属性没有改变之前,软件便不会实现同传统产业一样的工厂化生产。
从软件生产的介质来说,传统产业生产都是有形的物质产品,人的生产活动都受制于生产资料这些物质介质;然而,软件生产的介质,却是无形的人类的 思维。物质资料的生产,受制于物质本身的属性,不容易为人类的思维所左右,并且容易被大量复制,这使得工业化大生成为可能。而人类的思维,却是如此的容易 变化,更关键的是不能被复制,甚至同一个人,不同时期思维的复制都不可能,这使得软件这个纯粹依赖人的思维活动的生产实现大规模工业化生产是如此的困难。 实际上,不仅仅是软件产业,凡是主要生产介质是人本身的活动的产业,都很难实现工业化生产,如咨询、演艺等。
从生产过程来看,对于传统产业来说,产品的设计和生产是分开的。在设计阶段,主要的工作是人的思维,因此,在这个阶段,同软件一样,不是批量生 产的。而在生产阶段,主要的对象便是物质资料,并且一切标准已经制定,只需要在流水线上大量复制。对于传统产业来说,设计和生产的界限是如此的明确,并 且,生产和设计的比重是如此的悬殊。然而,对于软件产业来说,软件的生产过程便是设计的过程,纯粹的生产过程几乎不存在(或许,光盘的复制算是),这使得 软件的生产形态同传统产业必然存在区别。
对于软件的开发过程来说,从业务模型、需求分析、系统架构、系统分析和设计、到最后代码实现,越往前,抽象层次越高,可控性越小,越往后,越接 近实际,可控性越大,因此,在软件开发中,核心团队的作用是如此巨大,一个软件产品的成败,核心团队的核心人员的作用在很大程度上是决定性的。对于软件开 发来说,如果软件开发要实现工业化生产,必定是从后向前推进,从编码开始。印度模式或许给出了这么一个例子。
因此,我们在软件工程的路上,只是在不断的向工程化的目标迈进,但是,要达到这个目标,可能会花很长的时间。技术上的每一次进步,都使我们向这 个目标迈进了一步。在软件工程的发展过程中,技术进步起了非常大,甚至可以说是决定性的作用。随着采用的技术的不同,所采用的管理方法也在不断变化。软件 工程技术的很多方面,也是为管理做准备的。优秀的软件开发技术的采用,能够弥补我们在工程化方面的不足,从而使得软件开发更加可控,软件质量更加有保障。
本书不准备讨论软件工程过程的问题,而只是对软件工程中软件技术的一个方面——系统框架设计,做一些探讨。
现在,很多开发人员都已经意识到这很重要的一点,那就是,在开发一个应用软件系统的时候,一个好的系统框架是非常重要的。从底层开始构建应用程序,是一件吃力不讨好的事情,而没有框架的应用程序,则很难想象会是一个好的应用程序。
除了对于开发的直接帮助,一个好的框架对于公司的知识管理也是非常有意义的。想象一下,我们经常在讨论,现在是一个知识经济的时代,尤其对于软件公司来说,知识(拥有这些知识的员工)就是公司最大的财富。那么,怎么来进行有效的知识管理呢?
首先,应当明确,知识管理,一个重要的目的,就是要把员工对公司最重要的知识沉淀下来。公司的每个员工头脑里都有很多的知识,这些知识对于员工 来说是很重要的,但是其重要性同公司并不是完全一致的。某些知识,对于某个员工来说是最重要的,但是对于公司可能并不需要。知识管理需要做的,是把员工对 公司最重要的知识累积起来。
其次,知识管理必须有一个载体。如果知识管理没有载体,那么,公司的知识就存在于员工的头脑之中,一旦这个员工离职,那么,知识也就离去了,没 有办法沉淀。如果只是把公司做过的项目的文档作为载体,那么,这个载体就过于零碎了。实际上,如果公司有一个统一的框架,那么,这个框架就是一个很好的知 识管理的载体。因为,这个框架,必定是集中了公司所有软件项目的共同点的,集中了对于公司最重要的知识的精华,能够为公司所有的项目服务。另外,随着框架 的不断被使用,框架本身也会随之升级优化。对于一个新成员的加入,他只要理解掌握了这个框架,就可以很好的融入团队中来;而人员的离去,也已经把自己对公 司最重要的知识留在了这个框架中。可以说,在这里,框架承担了一个知识管理平台的作用。一个最好的例子就是微软的Windows。这是微软所有知识的最集 中的平台。
软件,从本质上来说,就是现实世界在计算机中的模拟。在考虑应用软件系统架构的时候,实际上,考虑的问题主要在于:处理什么?怎么处理?如何使用?因此,应用软件系统,需要关注的方面,概括起来,主要包括以下三个大类:
处理的对象,也就是数据。
处理的方式,也就是我们的系统如何来处理系统的逻辑。
如何进行交互,这个交互包括用户(使用者),以及外部系统。
在应用软件系统中,数据是处理的基本对象,程序总是以一定的数据结构来表现数据,并且,在使用面向对象语言开发的系统中,数据总是以类和对象的 形式表现出来。另外一方面,数据总是需要存储,对于大部分应用软件系统来说,通常会采用关系型数据库来保存数据。这样,由于数据在程序和数据库中表现格式 的不一致,就必然要求在两者之间进行映射。这个映射,在面向对象设计语言和关系型数据库之间,通常称为对象/关系型映射,即O/R Mapping。
目前,在O/R Mapping部分,在Java平台下,已经有多种可以选择的方案,例如J2EE架构中的Entity Bean,轻量级的JDO,以及开源项目的Hibernate等,由于微软的.Net框架推出时间不长,成熟的O/R Mapping框架并不多见。O/R Mapping框架的选择或者设计是构建应用软件系统的最基本的工作。本书将讨论构建O/R Mapping框架的一些基本理论、概念和方法。
系统的业务逻辑处理,是应用软件系统的核心部分,如何合理的构建业务逻辑、如何提供业务逻辑层的服务,以及表现层如何访问业务逻辑提供的功能, 也是应用软件系统需要重点关注的问题。在这个方面,业界已经发展了很多可供选择的范式,如契约式设计、SOA架构(面向服务的架构)等。这些方法指明了设 计的方向,同时也需要我们在实际开发中加以应用。
在业务逻辑确定后,随后而来的问题就是,如何向客户端来提供业务逻辑服务,或者说,客户端如何访问这些服务。在多层应用软件系统中,客户端和业 务逻辑在物理上可能存在于不同的机器上,也可能存在于同一台机器,但至少,在逻辑上,是存在于两个不同部分,这就涉及到一个问题:这两个层之间如何进行通 信?还会涉及到远程过程调用的问题。
当然,现在我们已经有多种技术来远程过程调用,包括Webservice、.Net Remoting、Corba、甚至EJB等。如此多的实现技术,带来的很大的灵活性,但同时也带来了问题,其中一个就是,有多少种服务端技术,就得有多 少种相应的客户端访问技术。甚至,在某些分布式应用系统中,应用逻辑使用不同的技术开发,存在于不同的机器上,有的存在于客户机本机,有的使用.Net Remoting开发,存在于局域网内,有的使用因特网上的Web Service,有的时候,我们希望相同的业务逻辑能够支持不同的客户端。
在这种情况下,我们需要一个一致的服务访问编程模型,以统合不同的服务访问模式,简化系统的开发和部署。一个统一的远程过程调用框架的前景是如 此的诱人,以至于每一种方法都试图一统天下,但出于种种原因,最终都没有一家能够做到,最新的Web Service就力图做到这一点。实际上,每一种方法的出现,最终都会带来一个副作用,那就是,可供选择的多了一点,混乱也就又多了一点。在实际的开发过 程中,我们也需要一个统一的访问方式来解决这个问题。本书将讨论一些可用的方案。
为了更加清晰的进行表述,文章会附加一些程序代码。因为在讲到具体的技术的时候,本书会对各种可用的技术进行比较,因此,本书的代码可能会使用 不同的语言,通常是Java和C#,不过,在给出代码的时候,一般都会指明所用的语言。在大部分情况下,如果不说明具体的语言,那么就是C#(因为我比较 喜欢这门语言)。因为Java和C#的语法是如此的相像,我想,对有经验的程序员来说,这应该不会造成阅读上的麻烦。

系统的分层结构
2 .1.简述
我们在解决一个复杂的问题的时候,通常使用的一个技巧就是分解,把复杂的问题分解成为若干个简单的问题,逐步地、分别地解决这几个小问题,最后 就把整个问题解决掉。在设计一个复杂的软件系统的时候,同样的,为了简化问题,我们也通常使用的一个技术就是分层,每个层完成自身的功能,最后,所有的层 整合起来构成一个完整的系统。
分层是计算机技术中的常用方法,一个典型的例子就是TCP/IP技术的OSI七层模型。在应用软件开发中,典型的就是N层应用软件模型。N层的应用软件系统,由于其众多的优点,已经成为典型的软件系统架构,也已经为广大开发人员所熟知。
在一个典型的三层应用软件系统中,应用系统通常被划分成以下三个层次:数据库层、应用服务层和用户界面层。如下图(图2.1)所示:
图2.1
其中,应用服务层集中了系统的业务逻辑的处理,因此,可以说是应用软件系统中的核心部分。软件系统的健壮性、灵活性、可重用性、可升级性和可维护性,在很大程度上取决于应用服务层的设计。因此,如何构建一个良好架构的应用服务层,是应用软件开发者需要着重解决的问题。
为了使应用服务层的设计达到最好的效果,我们通常还需要对应用服务层作进一步的职能分析和层次细分。很多开发者在构建应用服务层的时候,把数据 库操纵、业务逻辑处理甚至界面显示夹杂在一起,或者,把业务逻辑处理等同于数据库操纵,等等,这些,都是有缺陷的做法。我们将就在这个方面进行设计时可采 用的方案进行一些探讨。
在一个分布式应用系统中,整个系统会部署在不同的物理设备上,如上面所示的三层体系,用户界面和应用服务器可能在不同的设备上,这就涉及到不同 机器之间的通信问题,也就是层间的通信和交互问题。我们已经有了很多可以用于分布式远程访问的技术,如CORBA,在Java平台上,我们还有Java RMI、EJB,在Windows平台上,从DCOM到COM+,再到.Net下的Web Service和.Net Remoting等。如何选用合适的远程访问技术,也是我们在系统框架中需要考虑的问题。[6]
  为了使讨论更具有针对性,本文也会讨论一些比较流行的系统架构,例如J2EE架构,以及JDO,然后,我们会讨论Websharp在这个方面的一些设计理念。
2 .2.设计的原则和评判标准
  同软件工程的原则一样,应用服务层的设计,必须遵循的最重要的原则就是高内聚和低耦合[7]。软件分层的本来目的,就是提高软件的可维护性 和可重用性,而高内聚和低耦合正是达成这一目标必须遵循的原则。尽量降低系统各个部分之间的耦合度,是应用服务层设计中需要重点考虑的问题。
  内聚和耦合,包含了横向和纵向的关系。功能内聚和数据耦合,是我们需要达成的目标。横向的内聚和耦合,通常体现在系统的各个模块、类之间的关系,而纵向的耦合,体现在系统的各个层次之间的关系。
  系统的框架,通常包含了一系列规范、约定和支撑类库、服务。
  对于如何判断一个软件的系统框架的优劣,笔者认为,可以从以下几个方面来评判:
  ◆ 系统的内聚和耦合度
  这是保证一个系统的架构是否符合软件工程原则的首要标准。
  ◆ 层次的清晰和简洁性
  系统每个部分完成功能和目标必须是明确的,同样的功能,应该只在一个地方实现。如果某个功能可以在系统不同的地方实现,那么,将会给后来的开发和维护带来问题。
  系统应该简单明了,过于复杂的系统架构,会带来不必要的成本和维护难度。在尽可能的情况下,一个部分应该完成一个单独并且完整的功能。
  ◆ 易于实现性
  如果系统架构的实现非常困难,甚至超出团队现有的技术能力,那么,团队不得不花很多的精力用于架构的开发,这对于整个项目来说,可能会得不偿失。简单就是美。
  ◆ 可升级和可扩充性
  一个系统框架,受设计时技术条件的限制,或者设计者本人对系统认识的局限,可能不会考虑到今后所有的变化。但是,系统必须为将来可能的变化做好准备,能够在今后,在目前已有的基础上进行演进,但不会影响原有的应用。接口技术,是在这个方面普遍应用的技巧。
  ◆ 是否有利于团队合作开发
  一个好的系统架构,不仅仅只是从技术的角度来看,而且,它还应该适用于团队开发模型,可以方便一个开发团队中各个不同角色的互相协作。例如,将Web页面和业务逻辑组件分开,可是使页面设计人员和程序员的工作分开来同步进行而不会互相影响。
  ◆ 性能
  性能对于软件系统来说是很重要的,但是,有的时候,为了能让系统得到更大的灵活性,可能不得不在性能和其他方面取得平衡。另外一个方面,由于硬件技术的飞速发展和价格的下降,性能的问题往往可以通过使用使用更好的硬件来获得提升。
2 .3.应用服务层的内容
  应用服务层,通常也被称为业务逻辑层,因为这一层,是应用软件系统业务逻辑处理集中的部分。然而,我将这一层称为应用服务层,而不称业务逻辑层,因为,这一层需要处理的不仅仅是业务逻辑,还包含了其他方面的内容。
  从完整的角度来说,应用服务层需要处理以下内容:
  ◆ 数据的表示方式
  数据,是软件处理的对象。从某种程度上来说,"软件,就是数据结构加算法"的说法,是有一定意义的。在面向对象的系统中,数据是用类来表示 的,代表了现实世界实体对象在软件系统中的抽象。考虑所谓的MVC模式,这个部分的类属于M--实体类的范畴。由于应用软件通常会使用数据库,数据库中的 数据,可以看成是对象的持久化保存。由于数据库一般是关系型的,因此,这个部分,还需要考虑类(对象)同关系型数据的映射,即通常所说的O-R MAP问题。
  ◆ 数据的存取方式
  如同上述所说,软件系统处理的实体对象数据需要持久化保存数据库中,因此,我们必须处理系统同数据库的交互,以及数据的存取和转换方式的问题。
  ◆ 业务逻辑的组织方式
  在面向对象的系统中,业务逻辑表现为对象之间的交互。有了上述的实体对象,以及对象的保存策略,就可以将这些对象组合起来,编写我们的业务 逻辑处理程序。在业务逻辑的处理中,必须保证处理的正确性和完整性,这将会涉及到事务处理。通常,我们也会把业务逻辑封装成组件的形式,以得到最大的可重 用性。
  ◆ 业务服务的提供方式
  在我们完成系统的功能后,如何向客户提供服务,是我们需要考虑的问题。这里的客户,不仅仅是指软件的使用者,也包括调用的界面、其他程序 等。例如,在一个基于Web的ASP.Net或JSP系统中,业务逻辑功能的客户便是这些ASP.Net页面或JSP页面。业务逻辑组件应该通过什么方 式,直接的,或间接的,向这些客户提供服务,是这一层需要完成的任务。
  ◆ 层的部署和层间交互
  对于一个多层的应用软件系统来说,尤其是大型的应用软件系统,通常需要把不同的部分部署在不同的逻辑或物理设备上。特别是一些基于Web的 应用软件系统,其部署工作将涉及到Web服务器、组件服务器、数据库服务器等不同的服务设备。在进行应用软件架构的设计的时候,必须考虑各种不同的部署方 案。 当系统需要进行分布式访问的时候,如何统一和简化分布式系统的开发,便成了系统框架需要考虑的内容。
  综上所述,一个完整的基于Web的应用软件系统,其架构可以用图2.2来表示(Websharp的应用软件系统架构):

图2.2
对于以上各个方面来说,每个问题都可以有很多种策略和方案,但是,在一个系统中,应该尽可能的统一这些策略和方案。也就是说,在一个系统,或者 一个项目中,应该统一每个解决每个问题所采用的方法。软件的开发方法是灵活的,可以用不同的方法解决相同的问题,这会诱使开发人员采用他们认为能够表现自 己的方法,但是,从整个系统来看,这将会是灾难性的。我们应该尽可能统一,就是,采用统一的数据表示方式、统一的数据存取方式、统一的业务逻辑处理方式 等。
下面,将就这些部分的设计策略和可用方案进行一些比较详细的论述。
2 .4.数据实体的表示
  应用软件系统,从本质上来说,是计算机对现实世界的模拟。现实世界中的实体对象,在软件系统中,表现为需要处理的数据。在面向对象的系统中,这是通过“类"和”对象"来表示的。
  参考著名的“MVC”模式[8],类可以分成实体类(M)、控制类(C)、和边界类(V),分别代表了实体对象、控制和界面显示。系统中需要处理的数据,在面向对象的系统中,属于实体类部分。
  在考虑数据实体层的设计策略的时候,需要把握以下要点:
  ◆ 一致的数据表示方式。在一个系统中,数据的表示方式必须尽可能统一,同时,在处理单个数据和多个数据的时候,处理方式尽可能一致。
  ◆ 因为数据通常是需要存储到数据库中,因此,良好的映射方法是必需的。
  ◆ 处理好对象的粒度,即所谓的粗粒度对象、细粒度对象。
  一般例子
  考虑一个现实的例子,一个仓库中的产品(Product),在系统中可以使用如下定义:
public class Product
{
public string Name; //名称
public decimal Price;//价格
public int Count;//数量
}
可以按照如下方法使用Product类:
Product p=new Product();
//……处理Product
  这是一个包含了三个属性的Product类的定义。为了便于说明,在这里,我们尽量将问题简化了。
  又例如,一张入库单可以使用如下定义:
public class Form
{
public string ID; //入库单编号
public DateTime AddTime; //入库时间
public FormDetail[] FormDetails; //入库单明细
}
public class FormDetail
{
public Product InProduct; //入库产品
public int Count; //入库数量
}
  对于处理单个对象,通常采用上述的方法,但是,当我们需要处理相同类的一组对象,也就是处理一个对象集合的时候,就会有一些小小的麻烦。
  如前所述,我们希望在处理单个对象和对象集合的时候,处理的方式尽量统一,这对于软件开发的意义是很大的。常用的处理对象集合的方法有:
  ◆数组表示的方法
  例如,上面的例子中当一张入库单包含多条入库单明细的时候采用的方法。为了灵活性,也可以使用容器来,如Java中的Vector或C#的 ArrayList(C#)。只是,在处理对象的时候,需要一个类型转换的操作。这个问题,在支持泛型的语言中不会存在,如使用C++的标准库的容器类。
ObjectCollection方法。
这个方法同上面的方法类似,不同之处在于,为每个实体类设计一个Collection类。例如,可以为FormDetail设计一个FormDetailsCollection类(C#):
public class FormDetailsCollection: ArrayList
{
public void Add(FormDetail detail)
{
base.Add(detail);
}
public new FormDetail this[int nIndex]
{
get
{
return (FormDetail)base[nIndex];
}
}
}
  这么做的好处在于,在操作集合中的对象时,不必进行类型转换的操作。
  ◆数据集的表示方法。
  采用这种方法,通常是直接把从数据库查询中获取的数据集(Recordset)作为数据处理对象。这种方法在ASP应用程序中是非常常见的做法。这种做法简单,初学者很容易掌握,但是他不是一种面向对象的方法,弊病也很多。
  EJB的方法
  在J2EE体系中,对实体对象的处理的典型方法是Entity Bean。J2EE中使用Entity Bean来表示数据,以及封装数据的持久化储存(同数据库的交互)。由于Entity Bean比较消耗资源,而且采用的是远程调用的方式来访问,因此,在需要传递大量数据,或者在不同的层次之间传递数据的时候,往往还会采用一些诸如"值对 象"(Value Object)的设计模式来提升性能。关于J2EE中的设计模式的更多内容,可以参考《J2EE核心模式》一书。 [9]
  JDO的方法
  相对于J2EE这个昂贵的方法来说,JDO提供了一个相对"轻量级"的方案。在JDO中,你可以采用一般的做法,编写实体类,然后,通过一 些强化器对这些类进行强化,以使其符合JDO的规范,最后,你可以通过PersistenceManager来实现对象的持久化储存。 [10]
  无论是EJB还是JDO,在同数据库进行映射的时候,都选用了XML配置文件的方式。这是一种灵活的方式。由于XML强大的表达能力,我们 可以很好的用它来描述代码中的实体类和数据库之间的映射关系,并且,不用在代码中进行硬编码,这样,在情况发生变化的时候,有可能只需要修改配置文件,而 不用去修改程序的源代码。关于EJB和JDO的配置文件的更多的信息,各位可以参考相关的文档,这里不再赘述了。
  然而,使用XML配置文件的方式并不是唯一的方法,在微软提供的一些案例中,如Duwamish示例[11],就没有采用这种方式。至于开发人员在开发过程中具体采用哪种方式,是需要根据具体情况进行权衡和取舍的。
  Websharp的方法
Websharp在数据的表现上,充分利用了.Net Framework类库中DataSet和特性(Attribute)的功能。我们设计了一个EntityData类,这个类继承了DataSet,并增加了一些属性和方法。
在Websharp中,当表示一个实体类的时候,需要定义一个抽象类,这个抽象类继承PersistenceCapable。例如,一个Schdule类可以表示如下:
[TableMap("Schdule","GUID")]
[WebsharpEntityInclude(typeof(Schdule))]
public abstract class Schdule : PersistenceCapable
{
[ColumnMap("GUID",DbType.String,"")]
public abstract string GUID{get;set;}
[ColumnMap("UserID",DbType.String,"")]
public abstract string UserID{get;set;}
[ColumnMap("StartTime",DbType.DateTime)]
public abstract DateTime StartTime{get;set;}
[ColumnMap("EndTime",DbType.DateTime)]
public abstract DateTime EndTime{get;set;}
[ColumnMap("Title",DbType.String,"")]
public abstract string Title{get;set;}
[ColumnMap("Description",DbType.String,"")]
public abstract string Description{get;set;}
[ColumnMap("RemidTime",DbType.DateTime)]
public abstract DateTime RemidTime{get;set;}
[ColumnMap("AddTime",DbType.DateTime)]
public abstract DateTime AddTime{get;set;}
[ColumnMap("Status",DbType.Int16,0)]
public abstract short Status{get;set;}
}
类的TableMap 特性指明了同Schdule实体类相映射的数据库表,以及关键字,ColumnMap特性指明了同某个属性相映射的数据库表字段,以及数据类型和默认值。
在实际的应用中,定义了这样一个Schdule抽象类后,要获取一个实体对象,因为Schdule类是抽象的,所以你不可以直接使用new操作来初始化Schdule对象,应当通过如下方式取得:
Schdule schdule = EntityManager.CreateObject(typeof(Schdule)) as Schdule;
EntityManager会即时编译出一个Schdule的实现类,并且返回一个对象。
在这种方式下,实体类同数据库表的映射是通过Attribute来实现的。
可以使用另外一种方法来表示一个实体类。在这种方式下,需要编写一个XML映射文件,然后,可以使用如下方式取得一个实体对象:
EntityData schdule =EntityManager.GetEntityData("Schdule");
然后,可以通过如下方式来访问这个对象的属性:
string Title = schdule["Title"]
可以看到,这种方式同传统的方式有点不同。在这种方式下,数据的表现形式只有一个,那就是EntityData。其好处是明显的,不用为每个实 体都单独编写一个类,能够大大减少代码的编写量。其缺点也很明显,那就是不能利用编译器类型检测的功能,如果在调用对象的属性的时候,写错了属性的名称, 就可能出错,这需要更加仔细的测试工作。但是,这个问题可以通过工具生成代码来解决。   
2 .5.数据的存取方式
数据存取的目的,是持久化保存对象,以备后来的使用,如查询、修改、统计分析等。存取的对象,可以是数据库、普通文件、XML甚至其他任何方 式,只要保证数据能够长久保存,并且,不会受断电、系统重起等因素的影响。在这个部分,最理想的状况,自然是能够支持除了数据库以外的各种类型的存取方 式,或者,至少留有接口,能够比较方便的扩充。
因为数据库是最常用,也是最有效的数据存储方法,因此,支持数据库存储是最首先必须支持的。在不同的平台下,有不同的数据库访问的手段。例如, 在Java平台下,有JDBC,在Windows平台下,可以使用ADO、ADO.Net等。但是,这些手段还比较接近底层,在实际操纵数据库的时候,需 要编写大量的代码,并且,我们还需要通过手工的方式来完成将程序中的面向对象的数据存储到关系型数据库的工作。这么做,自然编程的效率不高,并且非常容易 出错。但是,不可否认,这也是一种可以选用的方式。
从另外一个方面来看,由于我们前面已经解决了数据的映射问题,因此,在数据的存取方面是非常有规律的,我们完全可以让这个工作通过框架来执行。 这样,我们一方面可以简化很多同数据库交互方面的代码编写工作量,能够减少出现Bug的几率,另一方面,由于框架封装了不同数据库之间的差异,使得我们在 编写程序的时候,不用考虑不同数据库之间的差异,而将这个工作交给框架去做,实现软件的后台数据库无关性。
在这个部分,以下两个部分的类会显得特别重要:
  ◆对象--关系映射的分析类,能够通过既定的方案完成对象--关系的映射,确定数据存取方案
  ◆数据库操纵类:根据映射关系,将数据准确的存储到数据库中,并且封装不同数据库之间的差异。
  这个部分的操作过程,可以用图(图2.3)大概的表示如下:
图2.3
  在J2EE中,这个部分比较典型的就是EntityBean中的CMP。由于在BMP中,同数据库的交互部分需要通过手工编写代码的方式来 实现,因此,很难享受到容器带来的便利,只是由于EJB2.0以前的标准,CMP的功能,包括映射能力、实体关系模式等方面的功能比较弱,所以,在很多时 候,我们不得不使用BMP。现在,EJB2.0,在这个方面的功能已经非常强大了,我们完全可以享受容器带来的便利,而将大部分精力放在实现更加复杂的业 务逻辑方面了。
在JDO中,您同样可以通过PersistenceManager来实现同样的目标,例如,您想把一个Customer对象保存到数据库中,可以采用类似于下面的代码:
Schdule schdule=new Schdule(……);
PersistenceManager PM=PMFactory.initialize(……);
Pm.persist(schdule);
代码同样非常简明和直观,没有一大堆数据库操纵的代码,也不容易发生差错。
Websharp的方案
同JDO类似,Websharp定义了PersistenceManager接口,这个接口的定义在后面的章节中会给出,这里,我们先看看其使用方式。
当我们有了某个实体对象后,需要保存到数据库中的时候,我们可以使用下面的代码来实现:
public bool AddSchdule(Schdule schdule)
{
PersistenceManager pm =
PersistenceManagerFactory.Instance().CreatePersistenceManager();
try
{
pm.PersistNewObject(schdule);
return true;
}
catch
{
return false;
}
finally
{
pm.Close();
}
}
在这里,我们不需要关心具体的数据库版本,框架会封装不同数据库之间的差异,保证数据可以正确的存储到不同的数据库中。
  在这个部分,另外需要注意的是,为了保证数据存储的完整性,应当考虑事务处理的功能。J2EE、JDO和Websharp都支持在数据存储的时候使用事务处理。
在Websharp中,通过Transaction接口,提供了基本的事务处理能力。上面的代码,如果需要使用事务处理,则可以修正如下:
public bool AddSchdule(Schdule schdule)
{
if(!CheckSchdule(schdule))
return false;
PersistenceManager pm =
PersistenceManagerFactory.Instance().CreatePersistenceManager();
Transaction trans = pm.CurrentTransaction;
trans.Begin();
try
{
pm.PersistNewObject(schdule);
trans.Commit();
return true;
}
catch
{
trans.Rollback();
return false;
}
finally
{
pm.Close();
}
}
关于事务处理的Transaction接口的更多内容,在后面的章节中会详细说明。
2 .6.业务逻辑的处理
  有了上面的工作,我们就可以把这些对象组合起来,编写我们的业务逻辑。在面向对象的系统中,业务逻辑表现为对象之间的交互。在一些简单的系统中,没有复杂的业务逻辑,只是一些数据的维护工作,那么,有了上面两个部分的工作,我们实际上可能已经忘成了大部分的工作。
  在这个部分,由于不同系统之间业务逻辑千差万别,基本上没有办法提供统一的模式。但是,应当注意的是,在同一个系统中,采用基本一致的策略是非常必要的,这有助于消除项目内部的不一致性,使项目更加可控。甚至于,这些策略可以扩展成公司部分、甚至所有项目的策略。
  值得指出的是,很多人在这个部分操纵数据库,把业务逻辑处理等同于数据库操作,这是不可取的。在业务逻辑处理中,处理的应该是对象,而不是直接同数据库打交道,这样,才能获得更好的系统结构。
  在业务逻辑处理部分,由框架提供一些支撑的服务是非常必要的。这其中,最重要的一点就是事务的处理。业务逻辑的处理过程,会涉及到多个对象之间的交互,以及多次同数据库的交互。为了保证处理过程的完整性,必须使用事务处理的方法。框架必须支持事务处理。
  事务处理的功能,基本上有两种选择:使用基于数据库连接的事务、使用外部事物处理服务[12]。
  使用基于数据库连接的事务,事务处理的性能相对比较高,但是,当系统涉及到多个数据库之间的交互时,基于数据库连接的事务便无能为力了。而使用专用的事务处理服务,能够适应更多的情况,并且,有测试表明,随着数据处理量的上升,两者之间的性能差异会逐渐减小。
  在J2EE中,容器提供了事务处理的能力。在.Net平台上,事务处理是通过Windows COM+服务来提供的。在Websharp中,如上面所讲,通过Transaction接口,提供了基本的事务处理能力,能够满足大部分事务处理的要求。 当Websharp提供的事务处理能力不能满足需求的时候,可以使用EnterpriseService。
  下面是一个简单的例子:
public bool AddSchdule(Schdule schdule,string[] otherPeoples)
{
if(!CheckSchdule(schdule))
return false;
PersistenceManager pm =
PersistenceManagerFactory.Instance().CreatePersistenceManager();
Transaction trans = pm.CurrentTransaction;
trans.Begin();
try
{
pm.PersistNewObject(schdule);
foreach(string otherPeople in otherPeoples)
{
Schdule s = EntityManager.CreateObject(typeof(Schdule)) as Schdule;
s.GUID = Guid.NewGuid().ToString();
s.UserID = otherPeople;
s.StartTime = schdule.StartTime;
s.EndTime = schdule.StartTime;
s.Title = schdule.Title;
s.Description = schdule.Description;
s.RemidTime = schdule.RemidTime;
s.AddTime = DateTime.Now;
s.Status = 0;
pm.PersistNewObject(s);
}
trans.Commit();
return true;
}
catch
{
trans.Rollback();
return false;
}
finally
{
pm.Close();
}
}
在业务逻辑这一层,另外一个需要关注的问题是所谓的AOP。关于AOP的内容,我们会在下面的章节中再讨论。
2 .7.业务服务的提供
  业务外观层(Business Facade)的目的,是隔离系统功能的提供者和使用者,更明确地说,是隔离业务逻辑的软件的用户界面(可以参见Facade设计模式)。这一层没有任何 需要处理的逻辑,只是作为后台逻辑处理和前端用户界面的缓冲区,以达到如下目的
  ◆将用户界面和系统业务逻辑处理分开,这样,当业务逻辑发生变化时,不用修改客户端程序,是一种支持变化的设计方法。
  ◆使同一个业务逻辑能够处理不同的客户端请求。例如,可以将Facade设计成Web Service,这样,可以同时为传统的WinForm客户端程序、Web程序以及其他外部系统提供服务,而使用相同的应用服务层,同时,也可以实现系统 的分布式部署。关于如何做到这一点,可以参见Ioffice的Demo程序。
  ◆作为系统不同模块之间的调用接口。一个系统通常会包含很多模块,这些模块相对独立,又可能互相调用。为了减少各个不同部分之间的耦合度,必须采用一定的设计方法,Facade设计模式就是非常有效的一种,也是业务外观层的基础。
  ◆有利于项目团队的分工协作。业务外观层作为一个访问接口,将界面设计人员和逻辑设计人员分开,使得系统的开发可以实现纵向的分工,不同的开发人员可以关注自己的领域而不会受到干扰。
  业务外观层的代码框架,在系统分析和设计完成后就可以完成,他需要提供的方法,就相当于在界面设计人员和逻辑设计人员之间签订了一个协议, 他虽然没有实现任何逻辑,但是,他的引入,能使系统的开发更加有条理,更加简明。套用《设计模式》上的一句话,就是,“任何问题,都可以通过引入一个中间 层来得到简化”。
2 .8.层的部署和层间交互
对于一个多层的应用软件系统来说,尤其是大型的应用软件系统,通常需要把不同的部分部署在不同的逻辑或物理设备上。特别是一些基于Web的应用软件系统, 其部署工作将涉及到Web服务器、组件服务器、数据库服务器等不同的服务设备。在进行应用软件架构的设计的时候,必须考虑各种不同的部署方案。
已经有了很多可以用于远程访问的服务,如此多的实现技术,带来的很大的灵活性,但同时也带来了文题,其中一个就是,有多少种服务端技术,就得有 多少种相应的客户端访问技术。甚至,在某些分布式应用系统中,应用逻辑使用不同的技术开发,存在于不同的机器上,有的存在于客户机本机,有的使用.Net Remoting开发,存在于局域网内,有的使用因特网上的Web Service,有的时候,我们希望相同的业务逻辑能够支持不同的客户端。
在这种情况下,我们需要一个一致的服务访问编程模型,以统合不同的服务访问模式,简化系统的开发和部署。Websharp中的Service Locator提供了这样一种能力,开发人员只需要定义服务访问接口,就可以使用一致的方式透明的访问这些服务,而不用理会这些服务之间的不同点。框架会 自动生成访问远程服务需要的代理。
使用WSL,你可以使用类似于如下的代码来访问远程服务,而不用关心远程服务的种类:
public interface ISecuritySystem
{
bool Login(string userID,string password);
void Logout();
bool IsLogin();
Suser CurrentUser();
}
……
//在需要调用服务的客户端:
ISecuritySystem ss = ServiceLocator.FindService(
"SecurityService",typeof(ISecuritySystem)) as ISecuritySystem;
关于WSL的更多内容,在后面会更加详细的讨论。
2 .9.剪裁和取舍
  以上四个层次,对于大型的应用软件系统来说,是非常必要的。但是,对于一些小型的应用软件系统,如果完全按照以上的层次来做,可能反而会影响工作效率。因此,针对不同的系统,可以对架构进行一定的剪裁。
  数据实体层和实体控制层,是每个应用软件系统所必需的,显然无法裁减。对于业务逻辑层和业务外观层,根据实体情况,可以进行如下裁减:
  ◆如果系统没有复杂的业务逻辑,而只是一些数据的操作,或者业务逻辑特别少,那么,可以省略业务逻辑层,而将相关的功能移至实体控制层。
  ◆如果不考虑多种客户端的情况,也不考虑分布式部署的问题,系统的模块又很少,不会产生模块间紧耦合的情况,那么,可以不使用业务外观层,而让用户界面程序直接访问业务功能。
  在上面的论述中,对于每个层次,都说明了可以选择的多种方案,每一种方案都有他的优点和缺点,在具体开发的过程中,需要根据具体情况加以取舍。
2 .10.小结
  应用软件系统架构,是软件工程的重要组成部分。设计一个好的框架,其目的很明确,那就是,在目前还没有"银弹"之前,尽最大的可能,提高软件开发的效率和软件质量,把不必要的工作和容易出错的工作,交给框架去处理。
  应用服务层,在软件系统中,是一个非常复杂的部分,乍看之下,没有任何规律可行,给人无从下手的感觉。我们的目标,就是尽量化无规律为有规 律,把有规律的东西提取出来,形成规范,从而减少今后的开发工作量。其方法,就是对系统进行合理的分层,这样,系统的层次清晰了,每个层次完成的功能就比 较单一,就意味着每个层次的都相对更有规律可循,这样,我们就可以把这些有规律的东西交给框架去执行,或者,开发一个辅助工具,来完成这部分的代码编写工 作。Websharp就提供了这样一个代码自动生成的工具。这个工具被设计成Visual Studio.Net集成开发环境的插件,在实际开发过程中,能够提供很多便利。这是系统层次清晰带来的另外一个好处。
对于一个软件公司来说,统一的系统框架的意义不仅仅在于软件开发的本身。一个统一的系统框架,也是公司知识管理的重要组成部分。公司如果有一个 或有限个数的明确的软件框架,那么,这些框架就可以成为凝结公司开发人员经验、智慧的载体,并且可以在不断的实践中加以充实和完善。由于公司的软件系统的 框架比较统一,那么当某个项目更换或增加开发人员的时候,后来的人也能够比较容易接手,这对于公司的开发管理是具有非常重要的意义的。

第二部分 应用服务层的设计

数据和对象
3 .1数据的形态
在应用软件系统中,首先要处理的对一个对象就是数据。应用软件系统,主要目标就是采集数据、处理数据、分析数据、察看数据。对于软件,诚如有一句名言所说:“软件,就是数据结构加算法”。
在软件中,数据有多种表现形态。
首先,在程序中,数据总是以某种数据结构的方式被表示出来,这种表示,通常被编译成二进制文件存在于硬盘上,并且在运行时刻在内存中被实例化。
这种数据结构有多种表达的方式,简单的情况下,他可能只是一个数字,或者一个字符串,用某个变量来描述。例如,为了表述某种商品的价格,可能使用如下的申明来表述这个数据:
double price = 100 ;
现实中要处理的数据总是比较复杂的,为了描述一个完整的信息,通常要组合多项简单的数据,例如,为了描述某种商品的信息,通常需要描述他的名称、价格、重量等。在传统的C语言中,可以使用结构来描述:
struct product
{
char* name;
double price;
double weight;
}
在面向对象的语言里,类似的数据结构,可以使用类来表述。上面的代码可以用Java语言表述如下:
public class Product
{
public String name;
public double price;
public double weight;
}
可以看出来,实际上两者的差别是非常小的。
对于更加复杂的数据结构,一个类可能引用到其它的类,例如,上面的Product,可能有一个Size属性,而这个Size属性,也有height和width构成,那么,整体的数据结构就可以描述如下:
public class Product
{
public String name;
public double price;
public double weight;
public Size size;
}
public class Size
{
public int height;
public int width;
}
数据的另外一种表现形态,就是永久化保存的形态。上面描述的数据的形态,是一种“瞬时”的数据,只有在程序运行的时候才存在于内存中,一旦程序结束,或者数据处理结束,数据就从内存中清楚。在很多情况下,需要把处理的数据保存到磁盘上,这时候,数据就进入永久化保存状态。
可以有多种保存数据的格式。可以把数据保存为普通文本文件存放在磁盘上,或者,也可把数据保存在XML文件中。在Java和C#中,也都提供了这样一种能力,就是可以把对象序列化后保存到磁盘上,然后,在需要的时候,可以反序列化成对象。
虽然有多种持久化保存数据的方案,然而其中使用关系型数据库来保存数据,无疑是最常用的办法和最可靠的办法。这就引伸出一个在面向对象的系统设计中的常见问题:对象/关系型映射(O/R Mapping)。
在考虑O/R Mapping的时候,有两个概念是经常会接触的,那就是VO和PO
所谓的VO,就是Value Object,这种对象,只包含了对象的数据,而没有状态,或者说,处于瞬时状态。VO可以用来在层之间传递数据
所谓PO,就是Persistent Object,就是持久化保存的对象,这种对象,一般是有状态的。O/R Mapping框架需要根据PO的状态,来执行相应的同数据库的交互。关于PO的状态,我们在后面再讨论
3 .2对象/关系型映射
对象关系型映射,最核心的要完成两个功能:对象和关系型之间的映射规则,以及两者之间的相互转换。
除了这两个基本的功能,一般的O/R Mapping产品还会加上一些额外的特性和功能,以加强产品的功能,为软件开发提供更多的方便和提高性能。一些常见的功能,例如缓存。
现在有一些典型的O/R Mapping框架可以参考和使用,比较著名的有EJB中的Entity Bean,JDO,Hibernate等,这些方案都是基于Java的。在Microsoft.Net平台下,相对来说可供选择的方案比较少,其中有一个 开放源代码的方案Websharp,可以从 www.websharp.org 下载。
我们在实际开发中,可以选择使用已有的解决方案和产品,也可以自己设计自己的O/R Mapping框架。当然,无论采用何种方式,都需要我们对O/R Mapping的基本原理和方法有一个基本的了解。
在支持OO的语言中,继承是语言的基本特征。O/R Mapping的框架,也需要对继承做出相应的支持。一般说来,有三种继承模式:ONE_INHERITANCE_TREE_ONE_TABLE、 ONE_INHERITANCE_PATH_ONE_TABLE和ONE_CLASS_ONE_TABLE。
ONE_INHERITANCE_TREE_ONE_TABLE:
一个继承树映射到一个表,即将具有相同父类的所有类都映射到一个数据库表中,这些类属性映射的集合组成这个表的列,在这种模式下,只需要对最底层的类进行映射。
如下面一个类结构:
在这个类结构中,父类Parent有两个子类Child1和Child2,父类有属性Property1和Property2,Child1新 增了一个属性Property3,而Child2新增了另外一个属性Property4,当采用 ONE_INHERITANCE_TREE_ONE_TABLE映射模式的时候,数据库中只有一张表,结构如下:
其中,Column1和Column2字段分别对应Parent类的Property1和Property2属性,Column3字段对应 Child1的Property3属性,而Column4字段对应Child2的Property4属性。Column3对于Child2和 Column4对于Child1是没有意义的。
采用这种映射模式,优点是比较简单,缺点是数据的冗余比较大。这个表要容纳所有的子类的字段,很多字段只是对某个类有意义,而对于其他类则没有意义,是纯粹多余的,并且,在继承树中新增一个继承节点的时候,往往导致表的字段的重新设计,这是一件非常麻烦的事情。
ONE_INHERITANCE_PATH_ONE_TABLE:
一个继承路径映射到一个表,即将一个类映射到一个数据库表中,这个类的所有属性(包括从父类继承来的)映射的集合组成这个表的列。
在这种模式下,对于上面的类结构,数据库的结构就是:
其中,表Child1和Child2分别对应于类Child1和Child2。
这种模式是非常常用的,也没有数据冗余,缺点是在搜索的时候比较麻烦。例如,当我要搜索符合某个条件的Parent对象的时候,需要同时搜索 Child1和Child2表,并且,当在继承树中新增一个继承节点的时候,需要新增一个表,搜索的范围也必须扩大,原来的程序可能不得不重新设计。
ONE_CLASS_ONE_TABLE:
一个类映射到一个表,即将每个类映射到对应的一个表,这个类的属性(不包括从父类继承来的非主键属性)映射组成这个表的列,在这种模式下,具有继承关系的类被映射到不同的表中,表之间通过主键关联。
在这种模式下,对于上面的类结构,数据库的结构就是:
表Parent作为Child1和Child2的父表,父子表之间通过主键Colimn1关联。
这种模式是非常容易理解的,因为和类图非常相像,缺点是在查询的时候,由于设计到表的关联,SQL语句会比较复杂,效率也会比较低。这个情况当继承数的深度增加的时候,会体现的比较明显。
如果一个类没有子类和父类,那么采用三种模式中的哪一种都是一样的效果。
3 .3对象的状态
为了很好的控制对象,以及在同后台存储交互时的行为,通常O/R Mapping框架需要维护PO对象的状态。在不同的框架中,对对象状态的定义不尽相同,不过,也都有一些共同点,某些方面可能只是名称的不同。通常的 O/R Mapping框架都需要以各种方式来直接或间接的处理PO的
分享到:
评论

构建面向对象的应用软件系统框架


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论