谈谈系统中的耦合以及从另一个角度来解释姜同学的疑虑
今天姜同学发表了http://www.cnblogs.com/ASPNET2008/archive/2008/08/13/1266942.html
这篇POST表达了对通过分层设计的概念无法解决系统耦合的问题。在之后andytao同学回复了对姜敏朋友的回复 一文作为回应
我在看回复的时候感觉说得不太透,所以忍不住发此文从另外一个角度来试着为姜同学做一次解答。
首先是从姜同学的疑虑开始。姜同学对分层主要是担心增加了工作量缺起不到消除系统耦合的目的。所以我们首先就来谈谈耦合。
-------------------------------------------------以下内容来至软件工程课本----------------------------------------------------------
在软件工程上,耦合指的是指两个实体相互依赖于对方的一个量度。分为以下几种:
非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的
数据耦合:一个模块访问另一个模块时,彼此之间是通过简单数据参数 (不是控制参数、公共数据结构或外部变量) 来交换输入、输出信息的。
标记耦合 :一组模块通过参数表传递记录信息,就是标记耦合。这个记录是某一数据结构的子结构,而不是简单变量。
控制耦合:如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。
外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
公共耦合:若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。
内容耦合:如果发生下列情形,两个模块之间就发生了内容耦合
(1) 一个模块直接访问另一个模块的内部数据;
(2) 一个模块不通过正常入口转到另一模块内部;
(3) 两个模块有一部分程序代码重迭(只可能出现在汇编语言中);
(4) 一个模块有多个入口。
---------------------------------------------------以上部分定义来至软件工程课本---------------------------------------------------
举几个简单的例子
void main(string args[])
{
Console.Write(Console.Read());
}
以上的代码里,Console类的Write方法就和Read方法发生了非直接耦合,这种耦合需要有第三方作为控制方才能发生。
假设有一个类Book是一个DTO类(只有属性的类,用来传递数据的类)。如果在数据层提供了BookAccess类由一个Add的方法。那么在逻辑层里面调用
Book b=new Book();
//设值
BookAccess.Add(b);
那么这个时候逻辑层和数据层之间就是发生了标记耦合。
假设User类提供了一个Login()方法,方法的定义是
bool Login(string UserName,string Password);
那么如果我们在页面上这么写
User.Login(UserN.Text,PassWd.Text);
那么这个时候登陆页和User类就是发生了数据耦合。
如果
void output(bool flag)
{
if(flag)
{
Console.Write("OK!");
}
else
{
Console.Write("NO!");
}
}
那么这个时候调用output方法的时候就和他发生了控制耦合。
如果
public class a
{
private static string msg;
public static void main(string args[])
{
Read();
Write();
}
private static void Read()
{
msg=Console.ReadLine();
}
private static void Write()
{
Console.Write(msg);
}
}
这个时候就是外部耦合。
如果在逻辑层里面调用了HttpContext类,那么就是环境耦合了,因为这个DLL和Web环境已经不可分了。
Class DAL
{
public string sql;
public void ExecSql(string sql)
{
}
}
这样子的设计就容易发生内容耦合,本来sql是不该被外界访问的,但是这里可以非法访问,如果访问到了就发生了内容耦合了。
那么在知道了什么是耦合后我们再回到姜同学的疑问。根据我们对耦合的定义来看,耦合是无法被消除的。如果两个模块不存在耦合,那么也说明这两个模块之间就r无法发生互动。
我们所需要注意的是,虽然耦合是系统交互的必然结果,但是耦合的方式是有区别的。更具上边的介绍我们可以判断出,标记耦合,数据耦合是ok的,内容耦合是最糟糕的,控制耦合和环境耦合其次。所以我们要尽量使用标记耦合而不是后面糟糕的三种耦合方式。当然控制耦合有的时候是无法避免的,那么就能避免就避免了。
C#里比如还是可以用多态来解决控制耦合的问题。
---------------------------------------------------------我是可爱的分割线----------------------------------------------------------
所以我们在回过头来看看分层。分层能不能解决耦合的问题,就要看这个层怎么分,如何分,如何定义两个层次之间的交互界面。
那么如何才能定义良好的交互接口呢?
举个例子
Class AccessDAL
{
public void AddBook(Book newbook)
{
//数据操作的代码;
}
}
对于逻辑层来说AddBook方法就是一个界面了(用广义上来说也是接口)。这个接口和逻辑层发生的关系就是标记耦合。这种定义就应该优先于
public void AddBook(string ISBN,string BookName......);
这类定义,因为标记耦合要比数据耦合要好。当然参数很少的时候其实差不多。
最后说说修改数据库表结构的问题。如果数据库表结构都修改了那确实是大问题了。要么前期需求没做到位。如果要适应这样子的要求确实没有什么好办法避免表结构的改变给数据以上的各个层次带来的代码变更的工作量。但是如果采用的第一种标记耦合的定义方式的话,起码调用接口的那部分代码不用改动,接口的定义不用改动,只需要修改DTO类的定义就行了,自然数据层相关的代码需要改动。其余部分的在DTO改动后可以通过VS的重构功能来处理,其实还是能够减少不少工作量得了。