贫血模式和工厂模式,实体类,工具类以及三层架构
最近在做一个项目,用到了这些技术,所以稍微整理了一下,希望能对和我一样菜鸟级的任务有所帮助
- 三层架构
微软公司推荐的.NET分层式结构一般分为三层架构,如图所示:
表示层(WC) |
业务逻辑层(BLL) |
数据访问层(DAL) |
|
(1)数据访问层:有时候也称持久层,其功能主要是负责数据库的访问。简单地说就
是实现对数据表的 insert(增)、delete(删)、update(改)、select(查)的操作。
(2)业务逻辑层:是整个系统的核心,它与这个系统的业务(领域)有关。以陇原网上商城城为例,业务逻辑层的相关设计均和商城信息管理系统特有的逻辑相关,如果涉及数据库
访问,则调用数据访问层。
(3)表示层:是系统的 UI 部分,负责使用者与整个系统的交互。在这一层中,理想
的状态是不应该包括系统的业务逻辑。表示层中的逻辑代码仅与界面元素有关。
分层结构的优势:
(1):开发人员可以只关注整个结构中的其中某一层。
(2):可以很容易地用新的实现来替换原有层次的实现。
(3):可以降低层与层之间的依赖。
(4):有利于标准化。
(5):有利于各逻辑的复用。
概括来说,分层设计可以达到如下目的:分散关注、松散耦合、逻辑复用、标准定义。
一个好的分层结构可以使得开发人员的分工更加明确。一旦定义好各层次之间的接口,负
责不同逻辑设计的开发人员就可以分散关注、齐头并进。例如 UI 人员只需考虑用户界面的
体验与操作,业务领域的开发人员可以关注业务逻辑的设计,从而数据库设计人员也不必
为繁琐的用户交互而头痛了。每个开发人员的任务得到了确认,开发速度就可以迅速地提
高了。
松散耦合的好处是显而易见的。如果一个系统没有分层,那么各自的逻辑都紧紧纠缠
在一起,彼此间相互依赖,谁都是不可替换的。一旦发生改变,则牵一发而动全身,对项
目的影响极为严重。降低层与层之间的依赖性,既可以良好地保证未来的发展,在复用性
上也有明显优势。每个功能模块一旦定义好统一的接口,就可以被各个模块所调用,而不
用为相同的功能进行重复地开发。
分层结构也具有一些缺陷:
(1)降低了系统的性能。如果不采用分层式结构,很多业务可以直接访问数据库,以
此获取相应的数据,如今却必须通过中间层来完成。
(2)有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中
需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据
访问中都增加相应的代码。
- 实体类
在实体类中只定义了类的字段和属性,为什么不在里面定义方法?假如定义方法,是
定义业务逻辑层方法还是数据访问层方法?还是两者都需要定义?如果它们的方法都在实
体类中定义,那该如何实现分层?而且也不想在业务逻辑层调用数据访问层的对象。其实
只需要定义接口就可以了。方法定义在接口中,这样就不用担心破坏分层架构。也可以在
访问下一层的时候不用定义该类的对象,只需要一个接口就可以。
这就是实体类,抽象地说它只负责实体的表示和数据的传递,没有其他的功能需要。
- 工具类
工具类只是对本系统所用到的工具的一个封装,其实它和分层架构没有多大的关系,
在陇原商城要用到的工具类有三个,一个是缓存,一个是加密,另外一个是常用操作。
缓存可以提高系统的性能。ASP.NET 一共提供了三种可由 Web 应用程序使用的缓存:
输出缓存:它缓存请求所生成的动态响应。
片段缓存:它缓存请求所生成的各部分。
数据缓存:它以编程的方式缓存任意对象。
下面是我的商城所用的缓存类,可能有问题,希望大家指出:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Caching; //
namespace FlowerShop.Utility
{
public sealed class CacheAccess
{
/// <summary>
/// 存到缓存中
/// </summary>
/// <param name="cacheKey">用于引用该项的缓存键</param>
/// <param name="cacheObject">要插入缓存中的对象</param>
/// <param name="dependency">插入的对象的文件依赖项或缓存键依赖项。当任何依赖项更改时,该对象即无效,并从缓存中移除。如果没有依赖项,则此参数包含null。</param>
public static void SaveToCache(string cacheKey, object cacheObject, CacheDependency dependency)
{
Cache cache = HttpRuntime.Cache;
cache.Insert(cacheKey, cacheObject, dependency);
}
/// <summary>
/// 从缓存中取出
/// </summary>
/// <param name="cacheKey">键</param>
/// <returns>值</returns>
public static object GetFromCache(string cacheKey)
{
Cache cache = HttpRuntime.Cache;
return cache[cacheKey];
}
}
}
将该方法声明为 static 的好处是可以不用声明一个对象,可以直接通过类名调用该方
法。在该方法中,参数 cacheKey 代表用于引用该项的缓存键,cacheObject 表示要插入缓
存中的对象,dependency 表示所插入的对象的文件依赖项或缓存键依赖项。当任何依赖项
更改时,该对象即无效,并从缓存中移除。如果没有依赖项,则此参数为 null。
Cache cache = HttpRuntime.Cache;表示获取当前应用程序的
System.Web.Caching.Cache;
cache.Insert(cacheKey, cacheObject, dependency);表示将对象 cacheObject 插入
到键为 cacheKey 中。
这个类是从缓存中取出对象。首先还是获得一个缓存类的对象,然后通过传入的键值,
返回相应的对象。
- 工厂模式
工厂是分层架构的核心之处,工厂的理解对于理解三层架构有很大的帮助,如果想更
多的掌握工厂的知识,可以参考有关设计模式的书籍。
设计分层架构是为了实现层与层之间的可替换。如果现在需要换一种方式实现数据访
问层,但是又不想在表示层和业务逻辑层修改任何代码,而是最多只改一下配置文件就可
以。这该如何实现?此外,系统可以进行并行开发,即想让表示层的人与业务逻辑层的人
和数据访问层的人同时开发,这样开发的效率会大大提高。前面实现的接口就是让上层类
不能直接依赖于下层,即上层类不能直接实例化下层中的类。如果实现了下层的类又会怎
样?举个例子:在业务逻辑层定义了一个类 A,在数据访问层中定义了一个类 B,在 A 中定
义了一个 B 的对象,完成一种操作,但是突然想换一种方式实现数据访问层,那么需不需
要修改业务逻辑层的代码呢?答案是当然需要,因为在 A 中定义了 B 的一个对象,这也就
是定义接口的原因。定义了接口,上层类就不能具体依赖于下层类,而只依赖于下层提供
的一个接口,这就是所谓的松散耦合。
在工厂类中需要用到工厂类和依赖注入。工厂的概念属于设计模式的知识。工厂类顾
名思义,是指专门定义的一个类,目的是用这个类来负责创建其他类的实例。该类根据传
入的参数,动态决定应该创建哪个产品类。依赖注入又名 IOC(Inversion of Control),
中文为控制反转。许多应用都是由两个或更多个类通过彼此合作来实现业务逻辑,这使得
每个对象都需要与它合作的对象的引用,如果这个获取过程要靠自身实现,那么将导致代
码高度耦合且难以测试。所以应用依赖注入以后,如果对象 A 需要调用另一个对象 B,在对
象 B 被创建的时候,由依赖注入将对象 B 的引用传给 A 来调用,即将 B 的接口返回给 A,A
可以直接通过 B 的接口实现相应的操作。
在.NET 中怎样实现根据传入参数动态决定应该创建哪个产品类?这如果在以前实现
起来会很复杂,但是.NET 平台的反射机制提供了解决方案。应该怎样利用工厂类和依赖注
入机制来实现分层架构呢?首先考虑分层的要求,分层是在别的层次不改动的情况下来实
现可以更换不同层次的实现,那这是不是说传入工厂类的参数如果是某个层次的某个实现,
那么就可以通过反射来动态地调用该层次的实现,如果实现了动态加载不同的实现,但是
能保证动态加载后,程序仍能稳定运行吗?或者说应该怎样让程序可以稳定地运行下去。
这还是需要接口,前面定义了业务逻辑层和数据访问层的接口,每一层的实现,无论该层
有多少种实现都只是重写方法而已,所以用接口能够实现无缝的结合。依赖注入只是一种
机制,目的是减少耦合度,通俗地来讲就是避免用 new 来实现类,而是通过接口来实现。
- 贫血模式和充血模式的简单区别
贫血模型:是指领域对象里只有get和set方法,或者包含少量的CRUD方法,所有的业务逻辑都不包含在内而是放在Business Logic层。
优点是系统的层次结构清楚,各层之间单向依赖,Client->(Business Facade)->Business Logic->Data Access(ADO.NET)。当然Business Logic是依赖Domain Object的。似乎现在流行的架构就是这样,当然层次还可以细分。
该模型的缺点是不够面向对象,领域对象只是作为保存状态或者传递状态使用,所以就说只有数据没有行为的对象不是真正的对象。在Business Logic里面处理所有的业务逻辑,在POEAA(企业应用架构模式)一书中被称为Transaction Script模式。
充血模型:层次结构和上面的差不多,不过大多业务逻辑和持久化放在Domain Object里面,Business Logic只是简单封装部分业务逻辑以及控制事务、权限等,这样层次结构就变成Client->(Business Facade)->Business Logic->Domain Object->Data Access。
它的优点是面向对象,Business Logic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。
缺点是如何划分业务逻辑,什么样的逻辑应该放在Domain Object中,什么样的业务逻辑应该放在Business Logic中,这是很含糊的。即使划分好了业务逻辑,由于分散在Business Logic和Domain Object层中,不能更好的分模块开发。熟悉业务逻辑的开发人员需要渗透到Domain Logic中去,而在Domian Logic又包含了持久化,对于开发者来说这十分混乱。 其次,因为Business Logic要控制事务并且为上层提供一个统一的服务调用入口点,它就必须把在Domain Logic里实现的业务逻辑全部重新包装一遍,完全属于重复劳动。
如果技术能够支持充血模型,那当然是最完美的解决方案。不过现在的.NET框架并没有ORM工具(不算上开源的NHibernate,Castle之类),没有ORM就没有透明的持久化支持,在Domain Object层会对Data Access层构成依赖,如果脱离了Data Access层,Domain Object的业务逻辑就无法进行单元测试,这也是很致命的。如果有像Spring的动态注入和Hibernate的透明持久化支持,那么充血模型还是能够实现的。
选择了.NET平台开发,就是选择了开发高效、功能强大应用简单,最适合的模型就是贫血模型,或表数据入口,既有编译器和语言平台很好的支持,也符合微软提倡的开发模式。如果跟潮流在项目中使用充血模型,现有的ORM工具都很复杂难用,光是维护大量的映射文件都成问题。说贫血不够OO,它的Domain Object不够丰富,能把项目做好了,有一定的扩展能力和伸缩行就行了,也不一定要讲究优雅的面向对象。正如SOA,讲究松耦合、高内聚,服务端给客户端提供的服务,也就是一系列的方法调用加上DTO而已,不管服务的内部是不是面向对象的实现,至少它暴露出来的是过程式的服务。
初心商城:初心商城
作者:韩迎龙(Kencery) MVC/.NET群:159227188如果您认为这篇文章还不错或者有所收获,您可以通过右边的“打赏”功能 打赏一杯咖啡,本页版权归作者和博客园所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利