1 软件架构设计之“层间解耦和API接口设计”
1)北向接口设计本质就更大系统的架构设计的一部分。
2)接口设计需要解决耦合与依赖的问题,系统间接口更是如此。
3)接口与实现分离、控制反转、依赖倒置是实现层间解藕的几个方式。
=======================
软件架构设计之“层间解耦和API接口设计”
钟振 00112298,2013-11-11
2 1 概述
“分层模式”是一种最常用的软件架构设计模式,采用分层模式时,要求层间低耦合,并且一般要求上层可调用下层服务、但不允许下层调用上层。那么当系统有这种需求(下层调用上层)时,应该如何设计呢?
本文主要参考[1] 文章,保留最精华的内容、补充部分方法和自己的理解、忽略“依赖注入”(有点晦涩),可结合[1]阅读本文。
其中“下层调用上层”的需求主要在“控制反转”一节给出解决方案。
3 2 接口和实现分离
4 2.1 面向过程的实现:函数库(函数调用)
5 2.2 面向对象的实现:类库(聚合关系)
6 3 依赖倒置(Dependency Inversion Principle)
7 3.1 原则说明
第一种描述([3]):
A. 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。
B. 抽象不能依赖于具象,具象依赖于抽象。
第二种描述:
要针对接口编程,不要针对实现编程
8 3.2 解决方法一:类库设计者定义抽象接口(下层定义接口并实现)
当类库设计者难以和客户深入合作时,或者类库同时提供给很多客户使用时,这种方式比较合适。
9 3.3 解决方法二:调用方定义抽象接口(上层定义接口、下层实现)
如果是同一系统内的设计,优先使用方法二。这种方法也是面向对象设计推荐的方法。
10 3.3.1 Martin Flower给的例子
通常三层架构如上图(a);
经常需要引入一个Mapper层用于Domain/DB之间解耦具体数据映射方式,因此Mapper要依赖于Domain/DB,如(b)图;
但是,Domain怎么读写数据到DB呢?如果Domain依赖于Mapper则出现了“循环依赖”,这是一种很不好的设计。
由Domain层定义一个 Mapper Interface 接口,由Database层实现这个接口,则可以解决上述问题。
如果想简化类设计,可以把接口直接合入Domain层中(上图是合入Store类)。
11 3.3.2 Robert Martin给的例子
下图是一个典型的三层设计模式:
下图是经过依赖倒置重构后的架构:
12 4 控制反转
13 4.1 一个反面的例子
这是一个典型的双向依赖关系。这种双向依赖关系有一个非常严重的缺陷:由于GUI框架调用了应用程序中的某个特定函数(MyWindowProc), GUI框架根本无法独立存在;换一个新的应用程序,GUI框架多半就要做相应的修改。
14 4.2 解决方法一:面向过程设计中的“回调函数机制”
15 4.3 解决方案二:面向对象设计中的“模板方法模式”
16 4.3.1 王咏武给的例子
17 4.3.2 Craig Larman给的例子
18 4.4 解决方案三:面向对象设计中的“观察者模式”
19 4.4.1 Craig Larman给的例子
20 4.5 解决方案四:嵌入式软件中的“中断模式”
通过硬件中断主动上报事件,具体例子略。
21 5 参考资料
[1] 王咏武,《向依赖关系宣战——依赖倒置、控制反转和依赖注入辨析》,《程序员》杂志2005年1月
[2] Martin Flower,《Reducing Coupling》,2001,http://www.martinfowler.com/ieeeSoftware/coupling.pdf
[3] Robert Martin,《Agile Principles, Patterns, and Practices in C#》,2006
[4] Craig Larman,《Applying UML and Patterns》,2004
1)北向接口设计本质就更大系统的架构设计的一部分。
2)接口设计需要解决耦合与依赖的问题,系统间接口更是如此。
3)接口与实现分离、控制反转、依赖倒置是实现层间解藕的几个方式。
=======================
软件架构设计之“层间解耦和API接口设计”
钟振 00112298,2013-11-11
2 1 概述
“分层模式”是一种最常用的软件架构设计模式,采用分层模式时,要求层间低耦合,并且一般要求上层可调用下层服务、但不允许下层调用上层。那么当系统有这种需求(下层调用上层)时,应该如何设计呢?
本文主要参考[1] 文章,保留最精华的内容、补充部分方法和自己的理解、忽略“依赖注入”(有点晦涩),可结合[1]阅读本文。
其中“下层调用上层”的需求主要在“控制反转”一节给出解决方案。
3 2 接口和实现分离
4 2.1 面向过程的实现:函数库(函数调用)
5 2.2 面向对象的实现:类库(聚合关系)
6 3 依赖倒置(Dependency Inversion Principle)
7 3.1 原则说明
第一种描述([3]):
A. 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。
B. 抽象不能依赖于具象,具象依赖于抽象。
第二种描述:
要针对接口编程,不要针对实现编程
8 3.2 解决方法一:类库设计者定义抽象接口(下层定义接口并实现)
当类库设计者难以和客户深入合作时,或者类库同时提供给很多客户使用时,这种方式比较合适。
9 3.3 解决方法二:调用方定义抽象接口(上层定义接口、下层实现)
如果是同一系统内的设计,优先使用方法二。这种方法也是面向对象设计推荐的方法。
10 3.3.1 Martin Flower给的例子
通常三层架构如上图(a);
经常需要引入一个Mapper层用于Domain/DB之间解耦具体数据映射方式,因此Mapper要依赖于Domain/DB,如(b)图;
但是,Domain怎么读写数据到DB呢?如果Domain依赖于Mapper则出现了“循环依赖”,这是一种很不好的设计。
由Domain层定义一个 Mapper Interface 接口,由Database层实现这个接口,则可以解决上述问题。
如果想简化类设计,可以把接口直接合入Domain层中(上图是合入Store类)。
11 3.3.2 Robert Martin给的例子
下图是一个典型的三层设计模式:
下图是经过依赖倒置重构后的架构:
12 4 控制反转
13 4.1 一个反面的例子
这是一个典型的双向依赖关系。这种双向依赖关系有一个非常严重的缺陷:由于GUI框架调用了应用程序中的某个特定函数(MyWindowProc), GUI框架根本无法独立存在;换一个新的应用程序,GUI框架多半就要做相应的修改。
14 4.2 解决方法一:面向过程设计中的“回调函数机制”
15 4.3 解决方案二:面向对象设计中的“模板方法模式”
16 4.3.1 王咏武给的例子
17 4.3.2 Craig Larman给的例子
18 4.4 解决方案三:面向对象设计中的“观察者模式”
19 4.4.1 Craig Larman给的例子
20 4.5 解决方案四:嵌入式软件中的“中断模式”
通过硬件中断主动上报事件,具体例子略。
21 5 参考资料
[1] 王咏武,《向依赖关系宣战——依赖倒置、控制反转和依赖注入辨析》,《程序员》杂志2005年1月
[2] Martin Flower,《Reducing Coupling》,2001,http://www.martinfowler.com/ieeeSoftware/coupling.pdf
[3] Robert Martin,《Agile Principles, Patterns, and Practices in C#》,2006
[4] Craig Larman,《Applying UML and Patterns》,2004