求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
 
一个分层架构设计的例子
 

2010-10-19 作者:伍华聪 来源:伍华聪的blog

 

一般来说,对系统的分层,一般都需要下面几个层:实体层(Entity)、数据访问层(DAL)、业务逻辑层(BLL)、界面层(UI);而数据访问层,一般也会加入一个接口层(IDAL)。

在其中的实体层,一般是根据数据库进行映射外加入注释等,技术含量不大,在此一笔带过;数据库访问层和业务逻辑层,是关键之所在,因为这里好的设计,会利用很多基类的操作,减少很多代码和重复劳动;界面层,不管是WebForm还是WinForm,都是尽可能少的逻辑代码或者SQL语句在其中,好的项目可能会利用一些优秀的控件进去,提高体验,减少代码。另外,由于一些创建操作费时费资源,一般还需要把可重复利用的资源缓存起来,提高性能。
先给大家预览下项目的框架,再一层层分析讨论:

1、 实体层(定义一个空的基类,其他实体类继承之,主要是为了利用泛型操作,用途下面细说)

    public class BaseEntity
    
{    
    }

    public class EquipmentInfo : BaseEntity
    
{    
        
Field Members

        
Property Members

    }

2、 数据库访问层,数据访问层的关键是数据访问基类的设计,基类实现大多数数据库的日常操作,如下:

    /// <summary>
    
/// 数据访问层的基类
    
/// </summary>

    public abstract class BaseDAL<T> : IBaseDAL<T> where T : BaseEntity, new()
    
{
}

BaseEntity就是实体类的基类,IBaseDAL是定义的数据访问基类接口,包含各种常用的操作定义;因此BaseDAL就是要对各种操作的进行实现,实现接口越多,将来继承类的重用程度就越高。

以上通过泛型<T> ,我们就可以知道实例化那个具体访问类的信息了,可以实现强类型的函数定义。

    /// <summary>
    
/// 一些基本的,作为辅助函数的接口
    
/// </summary>

    public interface IBaseDAL<T> where T : BaseEntity
    
{
        
/// <summary>
        
/// 查询数据库,检查是否存在指定键值的对象
        
/// </summary>
        
/// <param name="recordTable">Hashtable:键[key]为字段名;值[value]为字段对应的值</param>
        
/// <returns>存在则返回<c>true</c>,否则为<c>false</c></returns>

        bool IsExistKey(Hashtable recordTable);

        
/// <summary>
        
/// 查询数据库,检查是否存在指定键值的对象
        
/// </summary>
        
/// <param name="fieldName">指定的属性名</param>
        
/// <param name="key">指定的值</param>
        
/// <returns>存在则返回<c>true</c>,否则为<c>false</c></returns>

        bool IsExistKey(string fieldName, object key);

        
/// <summary>
        
/// 获取数据库中该对象的最大ID值
        
/// </summary>
        
/// <returns>最大ID值</returns>

        int GetMaxID();

        
/// <summary>
        
/// 根据指定对象的ID,从数据库中删除指定对象
        
/// </summary>
        
/// <param name="key">指定对象的ID</param>
        
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>

        bool DeleteByKey(string key);
        
        
/// <summary>
        
/// 根据条件,从数据库中删除指定对象
        
/// </summary>
        
/// <param name="condition">删除记录的条件语句</param>
        
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>

        bool DeleteByCondition(string condition);


        
/// <summary>
        
/// 插入指定对象到数据库中
        
/// </summary>
        
/// <param name="obj">指定的对象</param>
        
/// <returns>执行成功返回True</returns>

        bool Insert(T obj);

        
/// <summary>
        
/// 更新对象属性到数据库中
        
/// </summary>
        
/// <param name="obj">指定的对象</param>
        
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>

        bool Update(T obj, string primaryKeyValue);

        
/// <summary>
        
/// 查询数据库,检查是否存在指定ID的对象(用于整型主键)
        
/// </summary>
        
/// <param name="key">对象的ID值</param>
        
/// <returns>存在则返回指定的对象,否则返回Null</returns>

        T FindByID(int key);

        
/// <summary>
        
/// 查询数据库,检查是否存在指定ID的对象(用于字符型主键)
        
/// </summary>
        
/// <param name="key">对象的ID值</param>
        
/// <returns>存在则返回指定的对象,否则返回Null</returns>

        T FindByID(string key);

        
返回集合的接口
    }

细看上面代码,会发现由一个PagerInfo 的类,这个类是用来做分页参数传递作用的,根据这个参数,你可以知道具体返回那些关心的记录信息,这些记录又转换为强类型的List<T>集合。

再看看数据库访问基类的具体实现代码吧:

    /// <summary>
    
/// 数据访问层的基类
    
/// </summary>

    public abstract class BaseDAL<T> : IBaseDAL<T> where T : BaseEntity, new()
    
{
        
构造函数

        
通用操作方法

        
对象添加、修改、查询接口

        
返回集合的接口
        
        
子类必须实现的函数(用于更新或者插入)
        
        
IBaseDAL接口
    }

3、具体的数据访问类

基类完成所有的操作了,对于具体的类将是一大福音,说明它的工作减少很多了,下面看看具体的实现过程。定义一个数据访问类接口,然后实现接口和继承基类即可。

    public interface IEquipment : IBaseDAL<EquipmentInfo>
    
{
    }

    public class Equipment : BaseDAL<EquipmentInfo>, IEquipment
    
{
        
对象实例及构造函数
}

其实这样就完成了,我们为了提高效率,重载两个函数的实现,避免基类的属性反射带来的性能损失,这两个函数看似很复杂,其实通过代码生成工具,生成起来也是毫不费功夫的。。

protected override EquipmentInfo DataReaderToEntity(IDataReader dataReader)

protected override Hashtable GetHashByEntity(EquipmentInfo obj)

因此最后的代码就变为下面

    public class Equipment : BaseDAL<EquipmentInfo>, IEquipment
    
{
        
对象实例及构造函数

        
/// <summary>
        
/// 将DataReader的属性值转化为实体类的属性值,返回实体类
        
/// </summary>
        
/// <param name="dr">有效的DataReader对象</param>
        
/// <returns>实体类对象</returns>

        protected override EquipmentInfo DataReaderToEntity(IDataReader dataReader)
        
{
            EquipmentInfo equipmentInfo = 
new EquipmentInfo();
            SmartDataReader reader = 
new SmartDataReader(dataReader);
            
            equipmentInfo.ID = reader.GetInt32(
"ID");
            equipmentInfo.PartID = reader.GetString(
"PartID");
            equipmentInfo.Name = reader.GetString(
"Name");
            equipmentInfo.EquipmentType = reader.GetString(
"EquipmentType");
            equipmentInfo.Specification = reader.GetString(
"Specification");
            equipmentInfo.Manufacturer = reader.GetString(
"Manufacturer");
            equipmentInfo.Picture = reader.GetBytes(
"Picture");
            equipmentInfo.ApplyEquipment = reader.GetString(
"ApplyEquipment");
            equipmentInfo.BuyAmount = reader.GetInt32(
"BuyAmount");
            equipmentInfo.BuyDate = reader.GetDateTime(
"BuyDate");
            equipmentInfo.Status = reader.GetString(
"Status");
            equipmentInfo.UserName = reader.GetString(
"UserName");
            equipmentInfo.SafeNumber = reader.GetInt32(
"SafeNumber");
            equipmentInfo.Note = reader.GetString(
"Note");
            
            
return equipmentInfo;
        }


        
/// <summary>
        
/// 将实体对象的属性值转化为Hashtable对应的键值
        
/// </summary>
        
/// <param name="obj">有效的实体对象</param>
        
/// <returns>包含键值映射的Hashtable</returns>

        protected override Hashtable GetHashByEntity(EquipmentInfo obj)
        
{
            EquipmentInfo info = obj 
as EquipmentInfo;
            Hashtable hash = 
new Hashtable(); 
            
            hash.Add(
"ID", info.ID);
            hash.Add(
"PartID", info.PartID);
            hash.Add(
"Name", info.Name);
            hash.Add(
"EquipmentType", info.EquipmentType);
            hash.Add(
"Specification", info.Specification);
            hash.Add(
"Manufacturer", info.Manufacturer);
            hash.Add(
"Picture", info.Picture);
            hash.Add(
"ApplyEquipment", info.ApplyEquipment);
            hash.Add(
"BuyAmount", info.BuyAmount);
            hash.Add(
"BuyDate", info.BuyDate);
            hash.Add(
"Status", info.Status);
            hash.Add(
"UserName", info.UserName);
            hash.Add(
"SafeNumber", info.SafeNumber);
            hash.Add(
"Note", info.Note);
                
            
return hash;
        }

    }

文章太长,下面关于逻辑层、缓存、界面部分的设计在下一篇文章中介绍。

以上所引用的代码是通过代码生成工具Database2Sharp自动生成(http://www.iqidi.com/Database2Sharp.htm),选择EnterpriseLibrary架构即可。

Database2Sharp_Enterprise.jpg

接着上一篇关于分层架构的讨论,一个分层架构设计的例子(1)

上篇介绍了实体类(Entity)、数据库访问类(DAL)、数据访问接口(IDAL)的相关设计,本篇主要讨论下面几个部分内容:业务逻辑层、缓存机制、界面层等方面。

业务逻辑层,主要是业务逻辑基类的设计,由于数据库访问类(DAL)的基类封装了大量的操作实现,因此,业务逻辑层的主要工作是进一步封装对底层访问接口的实现,如下所示。

    public class BaseBLL<T> where T : BaseEntity, new()
    
{
        
构造函数

        
对象添加、修改、删除等接口

        
返回集合的接口
    }

业务层基类封装了大量的调用,那么对于业务层的具体操作类,它的工作就很简单了,基本上只需要继承一下基类就可以了,这就是有一个优秀父亲的好处,呵呵

    public class Equipment : BaseBLL<EquipmentInfo>
    
{
        
public Equipment() : base()
        
{
        }

    }

基本上,业务层的设计到此应该收尾了,可是我们注意到,很多开发都使用了缓存的机制来进一步提高程序的性能,下面对这方面进行讨论。缓存的机制,一般是把创建过的对象资源放到一个集合中,需要的时候,调出来,如下业务层的工厂类所示。

    public class BLLFactory<T> where T : class
    
{
        
private static Hashtable objCache = new Hashtable();
        
public static T Instance
        
{
            
get
            
{
                
string CacheKey = typeof(T).FullName;
                T bll = (T)objCache[CacheKey];  
//从缓存读取  
                if (bll == null)
                
{
                    bll = Reflect<T>.Create(
typeof(T).Name, "HuaweiSoftware.IPSPBD.BLL"); //反射创建,并缓存
                }

                
return bll;
            }

        }

    }
  

这是一个业务逻辑类工厂创建类,我们在界面层只需要如下调用即可构造一个(利用了缓存)具体的业务类出来

CustomerInfo info = BLLFactory<Customer>.Instance.FindByID(ID);

在上面的BaseBLL和BLLFactory类中,有一个Reflect的操作类,这是反射缓存的具体实现所在,我们探讨一下它的实现。

    public class Reflect<T> where T : class 
    
{
        
private static Hashtable m_objCache = null;
        
public static Hashtable ObjCache
        
{
            
get
            
{
                
if (m_objCache == null)
                
{
                    m_objCache = 
new Hashtable();
                }

                
return m_objCache;
            }

        }


        
public static T Create(string sName, string sFilePath)
        
{
            
return Create(sName, sFilePath, true);
        }

        
public static T Create(string sName, string sFilePath, bool bCache)
        
{
            
string CacheKey = sFilePath + "." + sName;
            T objType = 
null;
            
if (bCache)
            
{
                objType = (T)ObjCache[CacheKey];    
//从缓存读取 
                if (!ObjCache.ContainsKey(CacheKey))
                
{
                    Assembly assObj = CreateAssembly(sFilePath);
                    
object obj = assObj.CreateInstance(CacheKey);
                    objType = (T)obj;

                    ObjCache.Add(CacheKey, objType);
// 写入缓存 将DAL内某个对象装入缓存
                }

            }

            
else
            
{
                objType = (T)CreateAssembly(sFilePath).CreateInstance(CacheKey); 
//反射创建 
            }


            
return objType;
        }


        
public static Assembly CreateAssembly(string sFilePath)
        
{
            Assembly assObj = (Assembly)ObjCache[sFilePath];
            
if (assObj == null)
            
{
                assObj = Assembly.Load(sFilePath);
                ObjCache.Add(sFilePath, assObj);
//将整个DLL装入缓存
            }

            
return assObj;
        }

    }

另外,如果你在业务层需要实现更加复杂的功能,而数据库访问基类BaseDAL提供的函数不能满足你的需要,可以扩展数据访问层的接口和实现,如下所示。

    public interface ICustomer : IBaseDAL<CustomerInfo>
    
{
        List<
string> GetAllCustomerNumber();

        CustomerInfo GetByCustomerNumber(
string number);
    }



    
public class Customer : BaseDAL<CustomerInfo>, ICustomer
    
{
        
对象实例及构造函数

        

        
ICustomer 成员
    }

那么在业务层的类修改如下

    public class Customer : BaseBLL<CustomerInfo>
    
{
        
public Customer() : base()
        
{
        }


        
public List<string> GetAllCustomerNumber()
        
{
            ICustomer customerDAL = baseDal 
as ICustomer;
            
return customerDAL.GetAllCustomerNumber();
        }


        
public CustomerInfo GetByCustomerNumber(string number)
        
{
            ICustomer customerDAL = baseDal 
as ICustomer;
            
return customerDAL.GetByCustomerNumber(number);
        }

    }

最后,界面方面的设计是见仁见智,但根本一条是利用一些控件,可以统一风格,减少劳动,给出几个界面的设计截图供大家参考

WinForm方面的(颜色标明的是使用了特定的界面控件,其中红色部分为和整个架构整合起来的分页控件,集成了一些基本的右键菜单操作,包括打印功能、数据导出功能等):

WinForm_UI1.jpg

Winform分页控件设计视图

GridViewPager.jpg

可以选择列进行打印

GridViewPager_PrintOption.jpg

在实际运用过程中的界面效果

GridViewPager_Product.jpg

WebForm方面的(可以使用之前文章介绍的查询控件、分页控件、内容编辑控件):

下图是查询控件和分页控件的一起运用:

WebForm_UI1.jpg

修改内容时候的编辑控件

WebForm_UI2.jpg

查看内容时候的编辑控件

WebForm_UI3.jpg

以上所引用的代码是通过代码生成工具Database2Sharp自动生成(http://www.iqidi.com/Database2Sharp.htm),选择EnterpriseLibrary架构即可。

Database2Sharp_Enterprise.jpg

转载请注明出处: 撰写人:伍华聪 http://www.iqidi.com



专家视角看IT与架构
软件架构设计
面向服务体系架构和业务组件
人人网移动开发架构
架构腐化之谜
谈平台即服务PaaS


面向应用的架构设计实践
单元测试+重构+设计模式
软件架构师—高级实践
软件架构设计方法、案例与实践
嵌入式软件架构设计—高级实践
SOA体系结构实践


锐安科技 软件架构设计方法
成都 嵌入式软件架构设计
上海汽车 嵌入式软件架构设计
北京 软件架构设计
上海 软件架构设计案例与实践
北京 架构设计方法案例与实践
深圳 架构设计方法案例与实践
嵌入式软件架构设计—高级实践
更多...