通过浏览博客园的文章发现,很多朋友对分层架构特别感兴趣,刚好我刚做完的毕业设计就是专门研究.NET平台上分层架构的(题目叫“基于.NET平台的分层架构与设计模式应用研究”)。通过做这篇论文,我对分层架构有了一定的了解,所以,就萌发了想写一个文章系列,详述一下分层架构。然而,论文的理论性太强,不适合在网上发布,尤其不适合初学者理解,所以,我想在这个文章系列中,少讲理论,而是通过做一个完整的案例来讨论分层架构的基本方法,这样会直观很多。希望在这个文章系列的写作过程中,能和朋友们一起 学习 ,一起进步。
为了让朋友们把主要精力放在理解分层架构而不是案例本身,我准备选择一个相对简单的留言本系统作为Demo,这个系统的名字就叫做NGuestBook。
初步计划将这个文章系列分为以下几篇:
1.综述
2.系统需求分析及数据库设计
3.架构概要设计
4.实体类的实现
5.接口的设计与实现
6.依赖注入及IoC的设计与实现
7.数据访问层的第一种实现——Access+动态生成SQL语言
8.数据访问层的第二种实现——SQLServer+存储过程
9.数据访问层的第三种实现——基于NBear框架的ORM实现
10.业务逻辑层的实现
11.表示层的实现
12.使用 asp.net AJAX框架对表示层进行改进
13.总结
当然,以上只是初步计划,在写文章的过程中可能会根据具体情况适当调整,但是内容大体就是这些。
这个文章系列不会对所用到的技术进行详细讲解,具体请参考相关文献,阅读文章前最好能对以下技术有一个了解:
1.C#语言
2.ASP.NET
3.设计模式
4.关系数据库基础知识
5.软件架构基本原则与软件工程基础知识
6.基于NBear框架的ORM技术
7.JavaScript,Ajax
8.ASP.NET AJAX框架(特别是客户端编程)
9.HTML,CSS,标准化布局
另外,本文章系列是基于.NET framework2.0框架平台进行讨论,3.5平台的新特性(如LINQ、ASP.NET MVC等)不会讨论,IDE使用Visual Studio 2005,数据库会用到SQLServer2005 Express和Access2003。
在实际的项目中,需求分析和数据库的设计是很重要的一个环节,这个环节会直接影响项目的开发过程和质量。实际中,这个环节不但需要系统分析师、软件工程师等计算机方面的专家,还需要相关领域的领域专家参与才能完成。
但是,在这个文章系列中,所要使用的Demo仅仅是一个例子,而且其业务极为简单,因此,这里并不是真正的需求分析和数据库设计,而是将Demo的需求和数据库罗列至此,使朋友们对Demo有一个大体的了解,方便后续文章中开发过程的理解。
需求分析:
这个项目是一个留言本,其业务极为简单,现将其描述如下。
1.任何访问者可以进行留言,留言完成后,不会立即显示正文,而是要经过管理员验证后才可显示。
2.任何访问者可以对留言发表评论,未通过验证的留言不可以评论。
3.管理员可以对留言进行回复(这个回复不同于评论,是直接显示在正文下面,而且是一个留言只能有一个回复),并可对留言与评论实行删除,以及对留言进行通过验证操作。
4.管理员分为超级管理员和普通管理员。超级管理员只有一个,负责对普通管理员实行添加、删除操作。普通管理员可偶多个,负责对留言的管理,并可以修改自己的登录密码。
这个项目的用例图如下:
设计数据表之前,首先进行实体和关系的识别与确定。
通过需求分析,可以观察得出,本项目的实体有:管理员(不包括超级管理员),留言,评论。本项目的关系有:留言与评论间的一对多关系。
进一步,数据库各表的设计如下:
管理员表(TAdmin)
ID int 管理员ID NotNull 主键,自增
Name varchar(20) 登录名 NotNull
Password varchar(50) 登录密码 NotNull 使用MD5加密
留言表(TMessage)
ID int 留言ID NotNull 主键,自增
GuestName varchar(20) 留言者用户名 NotNull
GuestEmail varchar(100) 留言者E-mail Null
Content text 留言内容 NotNull
Time datetime 发表留言时间 NotNull
Reply text 回复 Null
但是,在这个文章系列中,所要使用的Demo仅仅是一个例子,而且其业务极为简单,因此,这里并不是真正的需求分析和数据库设计,而是将Demo的需求和数据库罗列至此,使朋友们对Demo有一个大体的了解,方便后续文章中开发过程的理解。
需求分析:
这个项目是一个留言本,其业务极为简单,现将其描述如下。
1.任何访问者可以进行留言,留言完成后,不会立即显示正文,而是要经过管理员验证后才可显示。
2.任何访问者可以对留言发表评论,未通过验证的留言不可以评论。
3.管理员可以对留言进行回复(这个回复不同于评论,是直接显示在正文下面,而且是一个留言只能有一个回复),并可对留言与评论实行删除,以及对留言进行通过验证操作。
4.管理员分为超级管理员和普通管理员。超级管理员只有一个,负责对普通管理员实行添加、删除操作。普通管理员可偶多个,负责对留言的管理,并可以修改自己的登录密码。
这个项目的用例图如下:
数据库设计:
设计数据表之前,首先进行实体和关系的识别与确定。
通过需求分析,可以观察得出,本项目的实体有:管理员(不包括超级管理员),留言,评论。本项目的关系有:留言与评论间的一对多关系。
进一步,数据库各表的设计如下:
管理员表(TAdmin)
ID int 管理员ID NotNull 主键,自增
Name varchar(20) 登录名 NotNull
Password varchar(50) 登录密码 NotNull 使用MD5加密
留言表(TMessage)
ID int 留言ID NotNull 主键,自增
GuestName varchar(20) 留言者用户名 NotNull
GuestEmail varchar(100) 留言者E-mail Null
Content text 留言内容 NotNull
Time datetime 发表留言时间 NotNull
Reply text 回复 Null
IsPass varchar(10) 是否通过验证 NotNull
评论表(TComment)
ID int 评论ID NotNull 主键,自增
Content text 评论内容 NotNull
Time datetime 发表评论时间 NotNull
MessageID int 所属留言的ID 外键
架构概要设计
本文主要是对将要实现的架构进行一个总体的描述,使朋友们对这个架构有个宏观上的认识。这篇文章理论性的东西会偏多一点,从下篇开始,将进行实际项目的开发。这篇文章的许多内容摘自我的毕业论文。
架构基本原则:
这里,将描述一些在这个架构设计中的基本原则,其中很多都是经典的设计原则,不过针对分层架构的特点,用我自己的语言进行了描述。其中也有我自己提出的原则。
逐层调用原则及单向调用原则
现在约定将N层架构的各层依次编号为1、2、…、K、…、N-1、N,其中层的编号越大,则越处在上层。那么,我们设计的架构应该满足以下两个原则:
1.第K(1 2.如果P层依赖Q层,则P的编号一定大于Q。
其中第一个原则,保证了依赖的逐层性,及整个架构的依赖是逐层向下的,而不能跨层依赖。第二个原则,则保证了依赖的单向性,及只能上层依赖底层,而不能底层反过来依赖上层。
针对接口编程,而不是针对实现编程
这里所指的接口,不是特指编程语言中的具体语言元素(如C#中由Interface定义的语言接口),而是指一种抽象的,在语义层面上起着接合作用语义体。它的具体实现,可能是接口,可能是抽象类,甚至可能是具体类。
我认为,从不同的视角,接口可以有以下两种定义:
1.接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。体现了自然界“如果你是……则必须能……”的理念。
2.接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上,因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同。
具体到N层架构中,针对接口编程的意义在部分上是这样的:
现仍约定将N层架构的各层依次编号为1、2、…、K、…、N-1、N,其中层的编号越大,则越处在上层,那么第K层不应该依赖具体一个K-1层,而应该依赖一个K-1层的接口,即在第K层中不应该有K-1层中的某个具体类。
依赖倒置原则
在软件设计原则中,有一种重要的思想叫做依赖倒置。它的核心思想是:不能让高层组件依赖底层组件,而且,不管高层组件和底层组件,两者都应依赖于抽象。
那么,这个原则和我们上面的原则是否矛盾呢?其实并不矛盾。
因为这个原则定义中的“依赖”是指“具体依赖”,而上面定义中的依赖全部指“抽象依赖”。我对这两种依赖的定义如下:
具体依赖——如果P层中有一个或一个以上的地方实例化了Q层中某个具体类,则说P层具体依赖于Q层。
抽象依赖——如果P层没有实例化Q层中的具体类,而是在一个或一个以上的地方实例化了Q层中某个接口,则说P层抽象依赖于Q层,也叫接口依赖于Q层。
从这两个定义可以看到,所谓的依赖倒置原则,正是上面提到针对接口编程,而不是针对实现编程,两者在本质上是统一的。
综上所述,可以看出,本课题设计的分层架构,应该是这样一种架构:
1.N层架构的各层依次编号为1、2、…、K、…、N-1、N,其中层的编号越大,则越处在上层。
2.架构中仅存在一种依赖,即第K层接口依赖第K-1层,其中1
封装变化原则
封装变化的原则定义为:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混杂在一起。
开放-关闭原则
开发-关闭原则定义为:对扩展开放,对修改关闭。
具体到N层架构中,可以描述为:当某一层有了一个新的具体实现时,它应该可以在不修改其他层的情况下,与此新实现无缝连接,顺利交互。
单一归属原则
在这个架构中,任何一个操作类都应该有单一的职责,属于单独的一层,而不能同时担负两种职责或属于多个层次(实体类及辅助类可以被多个层使用,但它们不属于任何一个层,而是独立存在)。
层次划分:
目前,典型的分层架构是三层架构,即自底向上依次是数据访问层、业务逻辑层和表示层。
这种经典架构经历了时间的考验和实践的多次检验,被认为是合理、有效的分层设计,所以,在本文中,将沿袭这种经典架构,使用数据访问层、业务逻辑层和表示层的三层架构体系。
职责划分:
目前,在典型的三层架构中,对层次各自的职责划分并没有一个统一的规范,综合现有的成功实践和.NET平台的特殊性,在本文中将三层架构的职责划分如下:
数据访问层——负责与数据源的交互,即数据的插入、删除、修改以及从数据库中读出数据等操作。对数据的正确性和有效性不负责,对数据的用途不了解,不负担任何业务逻辑。
业务逻辑层——负责系统领域业务的处理,负责逻辑性数据的生成、处理及转换。对流入的逻辑性数据的正确性及有效性负责,对流出的逻辑性数据及用户性数据不负责,对数据的呈现样式不负责。
表示层——负责接收用户的输入、将输出呈现给用户以及访问安全性验证。对流入的数据的正确性和有效性负责,对呈现样式负责,对流出的数据正确性不负责,但负责在数据不正确时给出相应的异常信息。
模块划分及交互设计:
综合以上分析,可在宏观上将整个系统分为一下几个模块:
实体类模块——一组实体类的集合,负责整个系统中数据的封装及传递。
数据访问层接口族——一组接口的集合,表示数据访问层的接口。
业务逻辑层接口族——一组接口的集合,表示业务逻辑层的接口。
数据访问层模块——一组类的集合,完成数据访问层的具体功能,实现数据访问层接口族。
业务逻辑层模块——一组类的集合,完成业务逻辑层的具体功能,实现业务逻辑层接口族。
表示层模块——程序及可视元素的集合,负责完成表示层的具体功能。
IoC容器模块——负责依赖注入的实现。
辅助类模块——完成全局辅助性功能。
各模块见交互关系如下:
这篇中理论比较多,但是它是整个架构的基础,可以帮助朋友们对将要实现的项目架构及要遵循的原则有一个整体的了解。当然,在后续文章中,将主要讨论Demo项目的实际开发过程,那时,这些思想和理论性的东西将得到体现。
架构基本原则:
这里,将描述一些在这个架构设计中的基本原则,其中很多都是经典的设计原则,不过针对分层架构的特点,用我自己的语言进行了描述。其中也有我自己提出的原则。
逐层调用原则及单向调用原则
现在约定将N层架构的各层依次编号为1、2、…、K、…、N-1、N,其中层的编号越大,则越处在上层。那么,我们设计的架构应该满足以下两个原则:
1.第K(1
其中第一个原则,保证了依赖的逐层性,及整个架构的依赖是逐层向下的,而不能跨层依赖。第二个原则,则保证了依赖的单向性,及只能上层依赖底层,而不能底层反过来依赖上层。
针对接口编程,而不是针对实现编程
这里所指的接口,不是特指编程语言中的具体语言元素(如C#中由Interface定义的语言接口),而是指一种抽象的,在语义层面上起着接合作用语义体。它的具体实现,可能是接口,可能是抽象类,甚至可能是具体类。
我认为,从不同的视角,接口可以有以下两种定义:
1.接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。体现了自然界“如果你是……则必须能……”的理念。
2.接口是在一定粒度视图上同类事物的抽象表示。注意这里我强调了在一定粒度视图上,因为“同类事物”这个概念是相对的,它因为粒度视图不同而不同。
具体到N层架构中,针对接口编程的意义在部分上是这样的:
现仍约定将N层架构的各层依次编号为1、2、…、K、…、N-1、N,其中层的编号越大,则越处在上层,那么第K层不应该依赖具体一个K-1层,而应该依赖一个K-1层的接口,即在第K层中不应该有K-1层中的某个具体类。
依赖倒置原则
在软件设计原则中,有一种重要的思想叫做依赖倒置。它的核心思想是:不能让高层组件依赖底层组件,而且,不管高层组件和底层组件,两者都应依赖于抽象。
那么,这个原则和我们上面的原则是否矛盾呢?其实并不矛盾。
因为这个原则定义中的“依赖”是指“具体依赖”,而上面定义中的依赖全部指“抽象依赖”。我对这两种依赖的定义如下:
具体依赖——如果P层中有一个或一个以上的地方实例化了Q层中某个具体类,则说P层具体依赖于Q层。
抽象依赖——如果P层没有实例化Q层中的具体类,而是在一个或一个以上的地方实例化了Q层中某个接口,则说P层抽象依赖于Q层,也叫接口依赖于Q层。
从这两个定义可以看到,所谓的依赖倒置原则,正是上面提到针对接口编程,而不是针对实现编程,两者在本质上是统一的。
综上所述,可以看出,本课题设计的分层架构,应该是这样一种架构:
1.N层架构的各层依次编号为1、2、…、K、…、N-1、N,其中层的编号越大,则越处在上层。
2.架构中仅存在一种依赖,即第K层接口依赖第K-1层,其中1
封装变化原则
封装变化的原则定义为:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混杂在一起。
开放-关闭原则
开发-关闭原则定义为:对扩展开放,对修改关闭。
具体到N层架构中,可以描述为:当某一层有了一个新的具体实现时,它应该可以在不修改其他层的情况下,与此新实现无缝连接,顺利交互。
单一归属原则
在这个架构中,任何一个操作类都应该有单一的职责,属于单独的一层,而不能同时担负两种职责或属于多个层次(实体类及辅助类可以被多个层使用,但它们不属于任何一个层,而是独立存在)。
层次划分:
目前,典型的分层架构是三层架构,即自底向上依次是数据访问层、业务逻辑层和表示层。
这种经典架构经历了时间的考验和实践的多次检验,被认为是合理、有效的分层设计,所以,在本文中,将沿袭这种经典架构,使用数据访问层、业务逻辑层和表示层的三层架构体系。
职责划分:
目前,在典型的三层架构中,对层次各自的职责划分并没有一个统一的规范,综合现有的成功实践和.NET平台的特殊性,在本文中将三层架构的职责划分如下:
数据访问层——负责与数据源的交互,即数据的插入、删除、修改以及从数据库中读出数据等操作。对数据的正确性和有效性不负责,对数据的用途不了解,不负担任何业务逻辑。
业务逻辑层——负责系统领域业务的处理,负责逻辑性数据的生成、处理及转换。对流入的逻辑性数据的正确性及有效性负责,对流出的逻辑性数据及用户性数据不负责,对数据的呈现样式不负责。
表示层——负责接收用户的输入、将输出呈现给用户以及访问安全性验证。对流入的数据的正确性和有效性负责,对呈现样式负责,对流出的数据正确性不负责,但负责在数据不正确时给出相应的异常信息。
模块划分及交互设计:
综合以上分析,可在宏观上将整个系统分为一下几个模块:
实体类模块——一组实体类的集合,负责整个系统中数据的封装及传递。
数据访问层接口族——一组接口的集合,表示数据访问层的接口。
业务逻辑层接口族——一组接口的集合,表示业务逻辑层的接口。
数据访问层模块——一组类的集合,完成数据访问层的具体功能,实现数据访问层接口族。
业务逻辑层模块——一组类的集合,完成业务逻辑层的具体功能,实现业务逻辑层接口族。
表示层模块——程序及可视元素的集合,负责完成表示层的具体功能。
IoC容器模块——负责依赖注入的实现。
辅助类模块——完成全局辅助性功能。
各模块见交互关系如下:
这篇中理论比较多,但是它是整个架构的基础,可以帮助朋友们对将要实现的项目架构及要遵循的原则有一个整体的了解。当然,在后续文章中,将主要讨论Demo项目的实际开发过程,那时,这些思想和理论性的东西将得到体现。
实体类的设计与实现
实体类是现实实体在计算机中的表示。它贯穿于整个架构,负担着在各层次及模块间传递数据的职责。一般来说,实体类可以分为“贫血实体类”和“充血实体类”,前者仅仅保存实体的属性,而后者还包含一些实体间的关系与逻辑。我们在这个Demo中用的实体类将是“贫血实体类”。
大多情况下,实体类和数据库中的表(这里指实体表,不包括表示多对多对应的关系表)是一一对应的,但这并不是一个限制,在复杂的数据库设计中,有可能出现一个实体类对应多个表,或者交叉对应的情况。在本文的Demo中,实体类和表是一一对应的,并且实体类中的属性和表中的字段也是对应的。
在看实体类的代码前,先看一下系统的工程结构。
如上图所示,在初始阶段,整个系统包括6个工程,它们的职责是这样的:
Web——表示层
Entity——存放实体类
Factory——存放和依赖注入及IoC相关的类
IBLL——存放业务逻辑层接口族
IDAL——存放数据访问层接口族
Utility——存放各种工具类及辅助类
这只是一个初期架构,主要是将整个系统搭一个框架,在后续开发中,将会有其他工程被陆陆续续添加进来。
我们的实体类将放在Entity工程下,这里包括三个文件:AdminInfo.cs,MessageInfo.cs,CommentInfo.cs,分别是管理员实体类、留言实体类和评论实体类。具体代码如下:
AdminInfo.cs:
AdminInfo
1using System;
2
3namespace NGuestBook.Entity
4{
5 /**
6 /// 实体类-管理员
7 ///
8 [Serializable]
9 public class AdminInfo
10 {
11 private int id;
12 private string name;
13 private string password;
14
15 public int ID
16 {
17 get { return this.id; }
18 set { this.id = value; }
19 }
20
21 public string Name
22 {
23 get { return this.name; }
24 set { this.name = value; }
25 }
26
27 public string Password
28 {
29 get { return this.password; }
30 set { this.password = value; }
31 }
32 }
33}
34
MessageInfo.cs:
MessageInfo
1using System;
2
3namespace NGuestBook.Entity
4{
5 /**
6 /// 实体类-留言
7 ///
8 [Serializable]
9 public class MessageInfo
10 {
11 private int id;
12 private string guestName;
13 private string guestEmail;
14 private string content;
15 private DateTime time;
16 private string reply;
17 private string isPass;
18
19 public int ID
20 {
21 get { return this.id; }
22 set { this.id = value; }
23 }
24
25 public string GuestName
26 {
27 get { return this.guestName; }
28 set { this.guestName = value; }
29 }
30
31 public string GuestEmail
32 {
33 get { return this.guestEmail; }
34 set { this.guestEmail = value; }
35 }
36
37 public string Content
38 {
39 get { return this.content; }
40 set { this.content = value; }
41 }
42
43 public DateTime Time
44 {
45 get { return this.time; }
46 set { this.time = value; }
47 }
48
49 public string Reply
50 {
51 get { return this.reply; }
52 set { this.reply = value; }
53 }
54
55 public string IsPass
56 {
57 get { return this.isPass; }
58 set { this.isPass = value; }
59 }
60 }
61}
62
CommentInfo.cs:
CommentInfo
1using System;
2
3namespace NGuestBook.Entity
4{
5 /**
6 /// 实体类-评论
7 ///
8 [Serializable]
9 public class CommentInfo
10 {
11 private int id;
12 private string content;
13 private DateTime time;
14 private int message;
15
16 public int ID
17 {
18 get { return this.id; }
19 set { this.id = value; }
20 }
21
22 public string Content
23 {
24 get { return this.content; }
25 set { this.content = value; }
26 }
27
28 public DateTime Time
29 {
30 get { return this.time; }
31 set { this.time = value; }
32 }
33
34 public int Message
35 {
36 get { return this.message; }
37 set { this.message = value; }
38 }
39 }
40}
41
大家可以看出,实体类的代码很简单,仅仅是负责实体的表示和数据的传递,不包含任何逻辑性内容。
大多情况下,实体类和数据库中的表(这里指实体表,不包括表示多对多对应的关系表)是一一对应的,但这并不是一个限制,在复杂的数据库设计中,有可能出现一个实体类对应多个表,或者交叉对应的情况。在本文的Demo中,实体类和表是一一对应的,并且实体类中的属性和表中的字段也是对应的。
在看实体类的代码前,先看一下系统的工程结构。
如上图所示,在初始阶段,整个系统包括6个工程,它们的职责是这样的:
Web——表示层
Entity——存放实体类
Factory——存放和依赖注入及IoC相关的类
IBLL——存放业务逻辑层接口族
IDAL——存放数据访问层接口族
Utility——存放各种工具类及辅助类
这只是一个初期架构,主要是将整个系统搭一个框架,在后续开发中,将会有其他工程被陆陆续续添加进来。
我们的实体类将放在Entity工程下,这里包括三个文件:AdminInfo.cs,MessageInfo.cs,CommentInfo.cs,分别是管理员实体类、留言实体类和评论实体类。具体代码如下:
AdminInfo.cs:
AdminInfo
1using System;
2
3namespace NGuestBook.Entity
4{
5 /**
6 /// 实体类-管理员
7 ///
8 [Serializable]
9 public class AdminInfo
10 {
11 private int id;
12 private string name;
13 private string password;
14
15 public int ID
16 {
17 get { return this.id; }
18 set { this.id = value; }
19 }
20
21 public string Name
22 {
23 get { return this.name; }
24 set { this.name = value; }
25 }
26
27 public string Password
28 {
29 get { return this.password; }
30 set { this.password = value; }
31 }
32 }
33}
34
MessageInfo.cs:
MessageInfo
1using System;
2
3namespace NGuestBook.Entity
4{
5 /**
6 /// 实体类-留言
7 ///
8 [Serializable]
9 public class MessageInfo
10 {
11 private int id;
12 private string guestName;
13 private string guestEmail;
14 private string content;
15 private DateTime time;
16 private string reply;
17 private string isPass;
18
19 public int ID
20 {
21 get { return this.id; }
22 set { this.id = value; }
23 }
24
25 public string GuestName
26 {
27 get { return this.guestName; }
28 set { this.guestName = value; }
29 }
30
31 public string GuestEmail
32 {
33 get { return this.guestEmail; }
34 set { this.guestEmail = value; }
35 }
36
37 public string Content
38 {
39 get { return this.content; }
40 set { this.content = value; }
41 }
42
43 public DateTime Time
44 {
45 get { return this.time; }
46 set { this.time = value; }
47 }
48
49 public string Reply
50 {
51 get { return this.reply; }
52 set { this.reply = value; }
53 }
54
55 public string IsPass
56 {
57 get { return this.isPass; }
58 set { this.isPass = value; }
59 }
60 }
61}
62
CommentInfo.cs:
CommentInfo
1using System;
2
3namespace NGuestBook.Entity
4{
5 /**
6 /// 实体类-评论
7 ///
8 [Serializable]
9 public class CommentInfo
10 {
11 private int id;
12 private string content;
13 private DateTime time;
14 private int message;
15
16 public int ID
17 {
18 get { return this.id; }
19 set { this.id = value; }
20 }
21
22 public string Content
23 {
24 get { return this.content; }
25 set { this.content = value; }
26 }
27
28 public DateTime Time
29 {
30 get { return this.time; }
31 set { this.time = value; }
32 }
33
34 public int Message
35 {
36 get { return this.message; }
37 set { this.message = value; }
38 }
39 }
40}
41
大家可以看出,实体类的代码很简单,仅仅是负责实体的表示和数据的传递,不包含任何逻辑性内容。
接口的设计与实现
接下来,将进行接口的设计。这里包括数据访问层接口和业务逻辑层接口。在分层架构中,接口扮演着非常重要的角色,它不但直接决定了各层中的各个操作类需要实现何种操作,而且它明确了各个层次的职责。接口也是系统实现依赖注入机制不可缺少的部分。
本项目的接口设计将按如下顺序进行:
1.首先由前文的需求分析,列出主要的UI部分。
2.分析各个UI需要什么业务逻辑支持,从而确定业务逻辑层接口。
3.分析业务逻辑层接口需要何种数据访问操作,从而确定数据访问层接口。
另外,为保证完全的面向对象特性,接口之间的数据传递主要靠实体类或实体类集合,禁止使用DataTable等对象传递数据。
由需求分析,列出主要UI
需求分析部分,请参看基于.NET平台的分层架构实战(二)——需求分析与数据库设计 。有需求分析,可以列出系统中主要应包括以下UI:
UI01——主页面,列出全部的留言及相应评论,支持分页显示。留言按发表时间逆序显示,评论紧跟在相应留言下。管理员可以通过相应链接对留言执行通过验证、删除、回复以及对评论进行删除操作。游客可通过相应连接进入发表留言评论页面。
UI02——发表留言页面,供游客发表新留言。
UI03——发表评论页面,供游客发表评论。
UI04——回复留言页面,供管理员回复留言。
UI05——管理员登录页面。
UI06——管理员修改个人密码的页面。
UI07——超级管理员登录后的页面,主要提供管理员列表。可以通过相应链接将指定管理员删除。
UI08——添加新管理员的页面。
UI09——操作成功完成后的跳转提示页面。
UI10——系统出现异常时显示友好出错信息的页面。
由UI识别业务逻辑操作
UI01:按分页取得留言,按指定留言取得全部评论,将指定留言通过验证,将指定留言删除,将指定评论删除
UI02:添加新留言
UI03:添加新评论
UI04:回复留言
UI05:管理员登录
UI06:修改管理员密码
UI07:取得全部管理员信息,删除管理员
UI08:添加新管理员
经过整理,可得以下接口操作:
IAdminBLL:Add(添加管理员),Remove(删除管理员),ChangePassword(修改管理员密码),Login(管理员登录),GetAll(取得全部管理员)
IMessageBLL:Add(添加留言),Remove(删除留言),Revert(回复留言),Pass(将留言通过验证),GetByPage(按分页取得留言)
ICommentBLL:Add(添加评论),Remove(删除评论),GetByMessage(按留言取得全部评论)
这三个接口文件都放在IBLL工程下,具体代码如下:
IAdminBLL.cs:
IAdminBLL
1using System;
2using System.Collections.Generic;
3using System.Text;
4using NGuestBook.Entity;
5
6namespace NGuestBook.IBLL
7{
8 /**
9 /// 业务逻辑层接口-管理员
10 ///
11 public interface IAdminBLL
12 {
13 /**
14 /// 添加管理员
15 ///
16 /// 新管理员实体类
17 ///是否成功
18 bool Add(AdminInfo admin);
19
20 /**
21 /// 删除管理员
22 ///
23 /// 欲删除的管理员的ID
24 ///是否成功
25 bool Remove(int id);
26
27 /**
28 /// 修改管理员密码
29 ///
30 /// 欲修改密码的管理员的ID
31 /// 新密码
32 ///是否成功
33 bool ChangePassword(int id,string password);
34
35 /**
36 /// 管理员登录
37 ///
38 /// 管理员登录名
39 /// 管理员密码
40 ///如果登录成功,则返回相应管理员的实体类,否则返回null
41 AdminInfo Login(string name,string password);
42
43 /**
44 /// 取得全部管理员信息
45 ///
46 ///管理员实体类集合
47 IList GetAll();
48 }
49}
IMessageBLL.cs:
IMessageBLL
1using System;
2using System.Collections.Generic;
3using System.Text;
4using NGuestBook.Entity;
5
6namespace NGuestBook.IBLL
7{
8 /**
9 /// 业务逻辑层接口-留言
10 ///
11 public interface IMessageBLL
12 {
13 /**
14 /// 添加留言
15 ///
16 /// 新留言实体类
17 ///是否成功
18 bool Add(MessageInfo message);
19
20 /**
21 /// 删除留言
22 ///
23 /// 欲删除的留言的ID
24 ///是否成功
25 bool Remove(int id);
26
27 /**
28 /// 回复留言
29 ///
30 /// 要回复的留言的ID
31 /// 回复信息
32 ///是否成功
33 bool Revert(int id, string reply);
34
35 /**
36 /// 将留言通过验证
37 ///
38 /// 通过验证的留言的ID
39 ///是否成功
40 bool Pass(int id);
41
42 /**
43 /// 按分页取得留言信息
44 ///
45 /// 每页显示几条留言
46 /// 当前页码
47 ///留言实体类集合
48 IList GetByPage(int pageSize,int pageNumber);
49 }
50}
ICommentBLL.cs
ICommentBLL
1using System;
2using System.Collections.Generic;
3using System.Text;
4using NGuestBook.Entity;
5
6namespace NGuestBook.IBLL
7{
8 /**
9 /// 业务逻辑层接口-评论
10 ///
11 public interface ICommentBLL
12 {
13 /**
14 /// 添加评论
15 ///
16 /// 新评论实体类
17 ///是否成功
18 bool Add(CommentInfo comment);
19
20 /**
21 /// 删除评论
22 ///
23 /// 欲删除的评论的ID
24 ///是否成功
25 bool Remove(int id);
26
27 /**
28 /// 取得指定留言的全部评论
29 ///
30 /// 指定留言的ID
31 ///评论实体类集合
32 IList GetByMessage(int messageId);
33 }
34}
由业务逻辑确定数据访问操作
IAdminBLL需要的数据访问操作:插入管理员,删除管理员,更新管理员信息,按ID取得管理员信息,按登录名与密码取得管理员,取得全部管理员
IMessageBLL需要的数据访问操作:插入留言,删除留言,更新留言信息,按ID取得留言信息,按分页取得留言
ICommentBLL需要的数据访问操作:插入评论,删除评论,按留言取得全部评论
另外,添加管理员时需要验证是否存在同名管理员,所以需要添加一个“按登录名取得管理员”。
本项目的接口设计将按如下顺序进行:
1.首先由前文的需求分析,列出主要的UI部分。
2.分析各个UI需要什么业务逻辑支持,从而确定业务逻辑层接口。
3.分析业务逻辑层接口需要何种数据访问操作,从而确定数据访问层接口。
另外,为保证完全的面向对象特性,接口之间的数据传递主要靠实体类或实体类集合,禁止使用DataTable等对象传递数据。
由需求分析,列出主要UI
需求分析部分,请参看基于.NET平台的分层架构实战(二)——需求分析与数据库设计 。有需求分析,可以列出系统中主要应包括以下UI:
UI01——主页面,列出全部的留言及相应评论,支持分页显示。留言按发表时间逆序显示,评论紧跟在相应留言下。管理员可以通过相应链接对留言执行通过验证、删除、回复以及对评论进行删除操作。游客可通过相应连接进入发表留言评论页面。
UI02——发表留言页面,供游客发表新留言。
UI03——发表评论页面,供游客发表评论。
UI04——回复留言页面,供管理员回复留言。
UI05——管理员登录页面。
UI06——管理员修改个人密码的页面。
UI07——超级管理员登录后的页面,主要提供管理员列表。可以通过相应链接将指定管理员删除。
UI08——添加新管理员的页面。
UI09——操作成功完成后的跳转提示页面。
UI10——系统出现异常时显示友好出错信息的页面。
由UI识别业务逻辑操作
UI01:按分页取得留言,按指定留言取得全部评论,将指定留言通过验证,将指定留言删除,将指定评论删除
UI02:添加新留言
UI03:添加新评论
UI04:回复留言
UI05:管理员登录
UI06:修改管理员密码
UI07:取得全部管理员信息,删除管理员
UI08:添加新管理员
经过整理,可得以下接口操作:
IAdminBLL:Add(添加管理员),Remove(删除管理员),ChangePassword(修改管理员密码),Login(管理员登录),GetAll(取得全部管理员)
IMessageBLL:Add(添加留言),Remove(删除留言),Revert(回复留言),Pass(将留言通过验证),GetByPage(按分页取得留言)
ICommentBLL:Add(添加评论),Remove(删除评论),GetByMessage(按留言取得全部评论)
这三个接口文件都放在IBLL工程下,具体代码如下:
IAdminBLL.cs:
IAdminBLL
1using System;
2using System.Collections.Generic;
3using System.Text;
4using NGuestBook.Entity;
5
6namespace NGuestBook.IBLL
7{
8 /**
9 /// 业务逻辑层接口-管理员
10 ///
11 public interface IAdminBLL
12 {
13 /**
14 /// 添加管理员
15 ///
16 /// 新管理员实体类
17 ///
18 bool Add(AdminInfo admin);
19
20 /**
21 /// 删除管理员
22 ///
23 /// 欲删除的管理员的ID
24 ///
25 bool Remove(int id);
26
27 /**
28 /// 修改管理员密码
29 ///
30 /// 欲修改密码的管理员的ID
31 /// 新密码
32 ///
33 bool ChangePassword(int id,string password);
34
35 /**
36 /// 管理员登录
37 ///
38 /// 管理员登录名
39 /// 管理员密码
40 ///
41 AdminInfo Login(string name,string password);
42
43 /**
44 /// 取得全部管理员信息
45 ///
46 ///
47 IList
48 }
49}
IMessageBLL.cs:
IMessageBLL
1using System;
2using System.Collections.Generic;
3using System.Text;
4using NGuestBook.Entity;
5
6namespace NGuestBook.IBLL
7{
8 /**
9 /// 业务逻辑层接口-留言
10 ///
11 public interface IMessageBLL
12 {
13 /**
14 /// 添加留言
15 ///
16 /// 新留言实体类
17 ///
18 bool Add(MessageInfo message);
19
20 /**
21 /// 删除留言
22 ///
23 /// 欲删除的留言的ID
24 ///
25 bool Remove(int id);
26
27 /**
28 /// 回复留言
29 ///
30 /// 要回复的留言的ID
31 /// 回复信息
32 ///
33 bool Revert(int id, string reply);
34
35 /**
36 /// 将留言通过验证
37 ///
38 /// 通过验证的留言的ID
39 ///
40 bool Pass(int id);
41
42 /**
43 /// 按分页取得留言信息
44 ///
45 /// 每页显示几条留言
46 /// 当前页码
47 ///
48 IList
49 }
50}
ICommentBLL.cs
ICommentBLL
1using System;
2using System.Collections.Generic;
3using System.Text;
4using NGuestBook.Entity;
5
6namespace NGuestBook.IBLL
7{
8 /**
9 /// 业务逻辑层接口-评论
10 ///
11 public interface ICommentBLL
12 {
13 /**
14 /// 添加评论
15 ///
16 /// 新评论实体类
17 ///
18 bool Add(CommentInfo comment);
19
20 /**
21 /// 删除评论
22 ///
23 /// 欲删除的评论的ID
24 ///
25 bool Remove(int id);
26
27 /**
28 /// 取得指定留言的全部评论
29 ///
30 /// 指定留言的ID
31 ///
32 IList
33 }
34}
由业务逻辑确定数据访问操作
IAdminBLL需要的数据访问操作:插入管理员,删除管理员,更新管理员信息,按ID取得管理员信息,按登录名与密码取得管理员,取得全部管理员
IMessageBLL需要的数据访问操作:插入留言,删除留言,更新留言信息,按ID取得留言信息,按分页取得留言
ICommentBLL需要的数据访问操作:插入评论,删除评论,按留言取得全部评论
另外,添加管理员时需要验证是否存在同名管理员,所以需要添加一个“按登录名取得管理员”。
.NET平台依赖注入机制及IoC的设计与实现
我们设计的分层架构,层与层之间应该是松散耦合的。因为是单向单一调用,所以,这里的“松散耦合”实际是指上层类不能具体依赖于下层类,而应该依赖于下层提供的一个接口。这样,上层类不能直接实例化下层中的类,而只持有接口,至于接口所指变量最终究竟是哪一个类,则由依赖注入机制决定。
之所以这样做,是为了实现层与层之间的“可替换”式设计,例如,现在需要换一种方式实现数据访问层,只要这个实现遵循了前面定义的数据访问层接口,业务逻辑层和表示层不需要做任何改动,只需要改一下配置文件系统即可正常运行。另外,基于这种结构的系统,还可以实现并行开发。即不同开发人员可以专注于自己的层次,只有接口被定义好了,开发出来的东西就可以无缝连接。
在J2EE平台上,主要使用Spring框架实现依赖注入。这里,我们将自己做一个依赖注入容器。
依赖注入的理论基础是Abstract Factory设计模式,这里结合具体实例简单介绍一下。
上图以数据访问层为例,展示了Abstract Factory模式的应用。如图,现假设有针对Access和SQLServer两种数据库的数据访问层,它们都实现了数据访问层接口。每个数据访问层有自己的工厂,所有工厂都实现自IDALFactory接口。而客户类(这里就是业务逻辑层类)仅与工厂接口、数据访问层接口耦合,而与具体类无关,这样,只要通过配置文件确定实例化哪个工厂,就可以得到不同的数据访问层。
然而,这种设计虽然可行,但是代码比较冗余,因为这样需要为数据访问层的每一个实现编写一个工厂,业务逻辑层也一样。在以前,我们毫无办法,但是,.NET平台引入的反射机制,给我们提供了一种解决方案。使用反射,每个层只需要一个工厂,然后通过从配置文件中读出程序集的名称,动态加载相应类。另外,为了提高依赖注入机制的效率,这里引入缓存机制。下面来看具体实现。
配置
首先,需要在Web工程的Web.config文件的 节点下添加如下两个项:
这两个配置选项分别存储要应用的数据访问和也业务逻辑层的程序集名称。value目前是空,是因为目前还没有各个层次的具体实现。
实现缓存操作辅助类
为实现缓存操作,我们将缓存操作封装成一个辅助类,放在Utility工程下,具体代码如下:
之所以这样做,是为了实现层与层之间的“可替换”式设计,例如,现在需要换一种方式实现数据访问层,只要这个实现遵循了前面定义的数据访问层接口,业务逻辑层和表示层不需要做任何改动,只需要改一下配置文件系统即可正常运行。另外,基于这种结构的系统,还可以实现并行开发。即不同开发人员可以专注于自己的层次,只有接口被定义好了,开发出来的东西就可以无缝连接。
在J2EE平台上,主要使用Spring框架实现依赖注入。这里,我们将自己做一个依赖注入容器。
依赖注入的理论基础是Abstract Factory设计模式,这里结合具体实例简单介绍一下。
上图以数据访问层为例,展示了Abstract Factory模式的应用。如图,现假设有针对Access和SQLServer两种数据库的数据访问层,它们都实现了数据访问层接口。每个数据访问层有自己的工厂,所有工厂都实现自IDALFactory接口。而客户类(这里就是业务逻辑层类)仅与工厂接口、数据访问层接口耦合,而与具体类无关,这样,只要通过配置文件确定实例化哪个工厂,就可以得到不同的数据访问层。
然而,这种设计虽然可行,但是代码比较冗余,因为这样需要为数据访问层的每一个实现编写一个工厂,业务逻辑层也一样。在以前,我们毫无办法,但是,.NET平台引入的反射机制,给我们提供了一种解决方案。使用反射,每个层只需要一个工厂,然后通过从配置文件中读出程序集的名称,动态加载相应类。另外,为了提高依赖注入机制的效率,这里引入缓存机制。下面来看具体实现。
配置
首先,需要在Web工程的Web.config文件的
|
实现缓存操作辅助类
为实现缓存操作,我们将缓存操作封装成一个辅助类,放在Utility工程下,具体代码如下:
using System; using System.Web; using System.Web.Caching; namespace NGuestBook.Utility { /** /// 辅助类,用于缓存操作 /// public sealed class CacheAccess { /** /// 将对象加入到缓存中 /// /// 缓存键 /// 缓存对象 /// 缓存依赖项 public static void SaveToCache(string cacheKey, object cacheObject, CacheDependency dependency) { Cache cache = HttpRuntime.Cache; cache.Insert(cacheKey, cacheObject, dependency); } /** /// 从缓存中取得对象,不存在则返回null /// /// 缓存键 /// public static object GetFromCache(string cacheKey) { Cache cache = HttpRuntime.Cache; return cache[cacheKey]; } } } 基于.NET平台的分层架构实战(转载)
封装依赖注入代码 因为很多依赖注入代码非常相似,为了减少重复性代码,我们将可复用的代码先封装在一个类中。具体代码如下(这个类放在Factory工程下):
下面使用两个辅助类,实现数据访问层工厂和业务逻辑层工厂。
|