下面以控件ComboBox为例,来简单阐述如何使用三层架构的:
Windows 窗体ComboBox控件用于在下拉组合框中显示数据。大多数情况下,我们只是将数据源中的相关数据取出来绑定到ComboBox控件上。回想一下前面两个项目“线路巡检系统”和“变电巡检系统”中的做法:我们使用ADO.Net访问数据库,取得DataSet后,设置ComboBox控件的显示文本及其属性,一般是其名称和对应的ID。大致的代码如下:
String strSql = “ Select id, name from myTable ”;
// 连接数据库,取得DataSet
// DataSet ds = ……………………………….
m_cmd.DataSource = ds;
m_cmd. ValueMember = “id”;
m_cmd. DisplayMember = “name”;这样的代码分布程序中的各个窗体文件中,他们大致相同却又有细微的差异,无法复用。更重要的是,一旦使用了ComboBox控件与数据源的自动绑定后,就无法再手动添加显示项了。因为在我们的需求中,有很多地方需要显示一项“全部”,这个“全部”在数据库中并不存在。这样,在需要显示“全部”的地方,我们采用了另外一种方法:首先添加一项“全部”,然后把要显示的名称从数据库中取出,一项一项的添加到ComboBox控件的项列表中。这样做的最大弊病是无法把属性绑定到控件上,造成了巨大的错误隐患。在后面的操作中,用户只好使用项列表中的“名称”作为“伪关键字”来进行比较等操作。有鉴于此,一个变通的做法是:把ID保存在成员变量中,当用户选择ComboBox控件后,到该成员变量中找到对应的属性,再进行其他的操作。
我们希望能够利用三层架构来提供一个比较优雅的解决方案,解除上述存在的隐患,并尽可能的提高复用程度。
根据分层的原理,我们把绑定ComboBox控件的整个实现过程分为四个部分:用户表现层,业务规则层,数据访问层和业务实体层。
数据访问层使用的是个通用数据库访问类,无需多说。
业务实体层把ComboBox控件要绑定的显示文本和其属性映射到自定义类中。
用户表现层设置控件的显示属性,并从业务规则层中取得业务实体,绑定到控件。
业务规则层实现业务逻辑,返回业务实体。1. 业务实体层
ComboBox控件需要的数据库字段很少,只有Name和ID两个字段。因此把他们映射到Model层中的SimpleInfo类中,以后所有的ComboBox控件都可以使用这个实体进行绑定。具体实现如下:
namespace Model
{
/// <summary>
/// /// 相当于Entity Bean,把数据库的数据映射成Object(O-R Mapping)。
/// 名称 + 编号。
/// </summary>
public class SimpleInfo
{
private string m_strName;
private string m_strValue; public SimpleInfo()
{
} public SimpleInfo(string strName, string strValue)
{
this.m_strName  = strName;
this.m_strValue = strValue;
} // Properties
public string Name
{
get { return m_strName; }
} public string Value
{
get { return m_strValue; }
}
}
}2. 业务规则层
    现在在业务规则层实现如何取得具体的数据的。下面以变电所信息为例,在BLL(Business Logic Layer)中窗体对应的业务规则实现类中添加一个取得所有变电所的方法:public static IList GetBds()。具体实现如下:
public static IList GetBds()
{
string strSql = " Select distinct bdsmc, bdsbh From bdxjt_xt_bds Where sfty=0 ";
return GetSimpleList(strSql, CONN_STRING);
}/// <summary>
/// 根据SQL文,取得有关“名称”和“代号”的所有记录组成的LIST。默认显示“全部”项。
/// </summary>
private IList GetSimpleList(string strSql, string strConn)
{
return GetSimpleList(strSql, strConn, true);
} /// <summary>
/// 根据SQL文,取得有关“名称”和“代号”的所有记录组成的LIST。bAllFlg表示是否显示///“全部”项。
/// </summary>
private IList GetSimpleList(string strSql, string strConn, bool bAllFlg)
{
IList lst = new ArrayList(); if ( bAllFlg )
{
SimpleInfo obj = new SimpleInfo("全部", "");
lst.Add(obj);
} DBOperator db = DBOperatorFactory.Create();
db.Open(strConn); IDataReader rdr = db.ExecuteReader(strSql);
while (rdr.Read())
{
SimpleInfo newObj = new SimpleInfo(rdr.GetString(0), rdr.GetString(1));
lst.Add(newObj);
} rdr.Close();
db.Close(); return lst;
}3. 用户表现层
    用户表现层中应该不涉及任何业务逻辑,它只负责如何显示。在窗体的Load事件中绑定ComboBox控件,具体实现如下:
// 窗体的Load事件
private void FormPlan_Load(object sender, System.EventArgs e)
{
BindBds();
}
private void BindBds()
{
IList lst = Plan.GetBds(); BindComboBox(lst, m_cmbBds);
} private void BindComboBox(IList lst, ComboBox cmb)
{
cmb.DataSource = lst; cmb.DisplayMember  = "Name";
cmb.ValueMember    = "Value"; cmb.SelectedIndex = 0;
}    到此为止,我们已经完全实现了ComboBox控件与数据源的绑定。用户可以访问ComboBox控件的显示文本,也可以使用它的相应属性,并以这个“唯一”的属性进行比较等各种操作。
    上面的实现还有可以改进的地方:
1. 在业务规则层,我们使用了“string strSql = " Select distinct bdsmc, bdsbh From bdxjt_xt_bds Where sfty=0 ";”这样的“硬编码”,建议将这段SQL文移到一个BLL层可见的公共的地方,如一个类中来来维护。这样做的理由如下:
(1) 系统中可能有很多地方要绑定变电所到ComboBox控件,他们取得变电所信息的SQL文都完全一样,把这段SQL文提到公共的类中,大家都可以访问,省却了在程序中的N份拷贝。
(2) 当数据库字段名发生变化等情况出现,需要修改SQL文时,只需修改一处。
(3) 这段SQL文可能会使用到某个数据库系统的特定功能,当移植到另一个数据库平台时,很容易定位并修改。
2. 在业务规则层,方法GetSimpleList()可以提到一个公共类中,供整个BLL层使用。这样,用户在实现业务规则的方法中,只需写成:
public static IList GetBds()
{
return CommBLL.GetSimpleList(CommSQL.XXX_SQL, CONN_STRING);
}
3. 同理,在用户表现层,也可以把绑定的操作BindComboBox()提取出来,放到公共类中。这样,用户端的绑定代码可以写成:
private void BindBds()
{
IList lst = Plan.GetBds(); FormComm.BindComboBox(lst, m_cmbBds);
}    关于具体如何实现三层架构,有很多种方法,这里只是提出了一个较简单的实现。对于争议最大的业务实体层,还有一些其他的实现方法,比如:使用XML,封装DataSet,使用自定义类等。另外,在是否一定要引入业务实体中,我认为应该具体问题具体分析。在一定的条件下,引入业务实体会带来巨大的好处,例如ComboBox控件的绑定,还有,在“线路巡检系统”和“变电巡检系统”中,有很多情况下需要把数据库中的诸如“0”或“1”这样的值转换成具体的汉字信息。我们在实现时采用了SQLServer的扩展语法“Case”语句,当移植到Oracle等其它平台时,就需要做大量的转换工作。据我所知,现在“巡检系统Oracle版”的做法是:把从Oracle数据库中取出的未经转换的数据直接绑定到DataGrid上,然后循环扫描DataGrid,强制改写DataGrid中的某行某列。引入业务实体层后,我们可以使用标准的SQL文取得数据,把转换的工作放到业务实体中。这样一来,既保证了从数据库中取出的数据的“纯洁性”,在移植时又几乎不需修改任何代码。当然,这样做可能会损失一点性能,但与我们投入的若干人月的修改的工作量相比,根本不值一提。
    但是,当画面很简单,且我们想利用DataSet的一些“非连接方式”的特性时,引入业务实体层甚至业务规则层,只会把原本简单的事情弄得异常复杂。比如:在“变电巡检系统”中的计划模块,有一个“巡检线路制定”画面,该画面没有复杂的业务逻辑,只是从一个表中取得数据,不许任何转换绑定到DataGrid,用户可以在DataGrid上进行修改操作,最后把结果更新到数据库。这样一个画面如果采用DateSet和Adapter的“断开”方式直接更新数据库,将会非常的简单,所以没有必要采用三层架构,可以绕过业务规则层和业务实体层,直接通过数据访问层访问数据库。