总体架构
.职责划分
目前在典型的三层架构中,对层次各自的职责划分并没有一个统一的规范,综合现有的成功实践和.NET平台的特殊性。
将三层架构的职责划分如下:
数据访问层(DAL)--负责与数据源的交互,即数据的插入、删除、修改以及从数据中读出数据等操作。对数据的正确性和
可用性不负责,数据的用途不了解,不负担任何业务逻辑。
业务逻辑层(BLL)--负责系统领域业务的处理,负责逻辑性数据的生成、处理及转换。对输入的逻辑性数据的正确性及
有效性负责,对输出的逻辑性数据及用户性数据不负责,对数据的呈现样式不负责。
表示层(Web,相对B/S系统)--负责接收用户的输入、将输出呈现给用户以及访问安全性验证。对输入的数据的正确性和
有效性负责,对呈现样式负责,对呈现友好的错误信息负责。
.模块划分及交互设计
综合以上分析,可在宏观上将整个系统分为以下几个模块:
实体类模块(Model/Entity)--一组实体类的集合,负责整个系统中的数据的封装及传递。
数据访问层接口组(IDAL)--一组接口的集合,表示数据访问层的接口。
业务逻辑层接口组(IBLL)--一组接口的集合,表示业务逻辑层的接口。
数据访问层模块(DAL)--一组类的集合,完成数据访问层的具体功能,实现数据访问层接口组。
业务逻辑层模块(BLL)--一组类的集合,完成业务逻辑层的具体功能,实现业务逻辑层接口组。
表示层模块(Web)--程序及可视元素的集合,负责完成表示层的具体功能。
IoC容器模块--负责依赖注入的实现。
辅助类模块--完成辅助性功能。
各个模块的划分及交互性如图:总体架构框图
下面说一下依赖倒置(Dependency Inversion Principle)、控制反转(Inversion Of Control)、依赖注入(Dependency Injection)的概念。
.依赖倒置原则
上一集[三层架构的核心思想.01]提到"面向接口编程"就是运用了"依赖倒置原则"。
这里重申依赖倒置的两个核心原则:
A.上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。
B.抽象不能依赖于具象,具象依赖于抽象。
如图:一个依赖倒置的图形例子:
这里符号含义,-userDAL:private。
虚线+箭头:代表依赖关系。
虚线+空心三角:代表"UserDAL类"实现"IUserDAL接口"。
实线+箭头:代表"UserBLL类"单向关联"IUserDAL接口",其中"UserBLL"知道"IUserDAL接口",
而且(通过DALFactory实例化了IUserDAL的某个具体角色UserDAL)知道"IUserDAL接口"扮演"userDAL" 的角色。
DALFactory是一个IUserDAL的角色组装工厂.
我们定义了一个IUserDAL接口作为抽象接口,其中InsertUser方法是一个抽象方法;
UserDAL类实现了IUserDAL接口。
当我们在UserBLL中调用userDAL.InsertUser方法时,由于 多态性机制的作用,实际调用的是UserDAL类的InsertUser方法。
因此抽象接口隔离了UserBLL类和UserDAL类,使它们之间没有直接的耦合关系,可以独立的扩展。
由此可以总结出,这种通过抽象接口消解模块之间的依赖关系的做法有如下特点:(以上、下层模块为例)
1.上层模块调用了一个抽象接口,依赖于抽象接口; 下层模块实现了这个抽象接口,也依赖于这个抽象接口。
2.上层模块和下层模块的实现完全独立,相互之间没有直接的依赖关系,只要保持接口类的稳定,上层模块和下层模块的具体实现
都可以独立地发生变化。
3.下层模块完全可以独立重用,上层模块也可以和任何一个实现了相同抽象接口的模块协同工作。
.控制反转原则(IoC,我们经常看到的IoC容器就是指这个缩写)
运用"依赖倒置原则"之后,上层模块不再直接依赖下层模块的具体实现类 ,而是依赖于一个抽象接口。
但是,上层模块在运作中又客观的需要决定使用 哪个下层模块的具体实现类。在上图中,我们把下层模块类的实例化解耦到
简单工厂中,但是上层模块还是需要要决定调用简单工厂中的哪一个函数创建实例化。
解决方法:上层模块依赖于抽象,但本身不决定是依赖哪个实现,而由外部(框架、容器、应用程序上下文)去决定。于是控制发生了
反转,由内部控制转向外部控制,具体的依赖关系由外部来“控制,即所谓控制反转。
控制反转,即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。
所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
IoC体现了好莱坞原则,即“do not call us, we'll call you!”,
好莱坞的演员们被告知:不要给我们打电话要剧本,在家好好呆着,适当的时候我们会带着剧本去找你的。
IoC是一个很泛的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖查找和依赖注入
1.依赖查找(Dependency Lookup):容器提供回调接口和上下文环境 给组件。其实现分为:依赖拖拽和上下文配置。
EJB(JDNI)和Apache Avalon都使用这种方式。比如回调函数,MFC里的消息机制等,感觉也是这种情况?欢迎指正!
依赖拖拽(Dependency Pull):
依赖关系是根据需要从一个登记处获取(拖拽)出来。Spring提供了依赖拖拽作为从框架管理的组件中重新获取组件的一种机制。
上下文配置依赖查找(Contextualized Dependency Lookup,CDL):
是在容器管理的资源中进行的,而不是从一个集中的注册处,同时它一般在规定点执行。
摘抄.下面代码展示了基于JNDI实现的依赖查找机制。
public class MyBusniessObject
{
private DataSource ds;
private MyCollaborator myCollaborator;
public MyBusnissObject()
{
Context ctx = null;
try{
ctx = new InitialContext();
ds = (DataSource) ctx.lookup(“java:comp/env/dataSourceName”);
myCollaborator = (MyCollaborator) ctx.lookup(“java:comp/env/myCollaboratorName”);
}……
}
} Code1.依赖查找(Dependency Lookup)代码实现
以上依赖查找,这部分内容不是很清楚,以上摘抄供参考,具体再研究,欢迎指点迷津^_^。
2.依赖注入(Dependency Injection),是这样的一个过程:(从类层次上的定义)由于某客户类只依赖于服务类的一个接口,
而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的
运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。
3.IoC实现机制(框架层次上):现在来考虑IoC的实现机制,组件定义了整个流程框架,而整个流程框架其中的一些业务逻辑的实现要借助于
其他业务对象的加入,它们可以通过两种方式参与到业务流程中,一种是"依赖查找",类似与JDNI的实现,通过JNDI来找到相应的
业务对象(代码1),另一种是"依赖注入",通过IoC容器将业务对象注入到组件中。
另外,模板方法模式、观察者模式应该也体现了"依赖反转" 。
3.依赖查找VS依赖注入:大部分情况选择注入。使用依赖,可以自由地使用完全与IoC容器解耦的类,可以手动设定各个独立对象之间的协作关系。
而在查找的方式下,你的类总是依赖于容器定义的特定类和接口。查找的另一个缺点是很难脱离容器来测试你的代码。
使用 注入,测试你的组件将变得非常轻松,因为你只需要通过适当的构造器和Setter来设定它们之间的关系。其次查找的解决方案比注入复杂的多。
最重要的是注入来代替查找将会使你的生活变得更加轻松。
4.依赖倒置VS控制反转() :(上、下层模块为例)
依赖倒置原则可以减弱上层模块和下层模块之间的依赖关系。
控制反转及具体的模板方法模式可以消解模块之间的依赖关系。
.依赖注入原则
依赖注入的核心思想:
1.抽象接口隔离了使用者和实现之间的依赖关系,但创建具体实现类的实例对象仍会造成对于具体实现的依赖。
2.采用依赖注入可以消除这种创建依赖性。使用依赖注入后,某些类完全是基于抽象接口编写而成的,这可以最大限度第适应需求变化。
依赖注入有多种方式,Setter注入(Setter Injection)、构造注入(Constructor Injection)、
接口注入(Interface Injection)、依赖获取(Dependency Locate)。
更多参考:《向依赖关系宣战--依赖倒置、控制反转和依赖注入辨析》
来源:https://www.cnblogs.com/star-studio/archive/2010/03/05/1676547.html