相信大家对三层开发都已经耳熟能详,可是我却发现新公司的既有代码中有一些违背分层开发思想的东西,现在与大家分享这些错误,我们共勉之。如果有人觉得对三层开发拿捏得不是太准,请参照李天平的文章:分层开发思想与小笼包,这篇文章用隐喻说明分层开发,是非常好的一篇文章。正文:1.界面层参与非界面逻辑,抢业务逻辑层的饭碗什么是界面逻辑:界面层应该有的逻辑就是显示的逻辑,例如根据逻辑结果显示某一个Panel不显示另外一个Panel,或者有一个数据集应该在界面上怎么呈现,这是界面层的逻辑例子场景:用户登录时首先验证用户输入的用户名是否有效,如果用户名有效,然后再验证用户输入的密码是否和用户名匹配,如果匹配则表示用户可以登录,增加用户的登录次数,然后将用户的信息写入Session中;否则返回错误。在这个过程中除了将用户信息写入Session这一步属于界面逻辑以外其他的操作都应该放在业务逻辑层。错误代码示例:private void buttonLogin_Click(object sender, EventArgs ev)
{
    string userName = textBoxUserName.Text;
    string password = textPassword.Text;    if (Business.Account.Exists(userName))
    {
        bool success = Business.Account.Login(userName, password);
        if (success)
        {
            Business.Account.AddLoginTime(userName);            Session["user"] = new User(userName, password);
            Redirect("/");
        }
        else
        {
            this.labelMessage.Text = "登录失败。";
        }
    }
    else
    {
        this.lableMessage.Text = "用户名不存在。";
    }
}
 
分析:在上面的代码中一个UI层的一个事件中调用了三次rules层的方法:Business.Account.Exists(userName)Business.Account.Login(userName, password)Business.Account.AddLoginTime(userName);还附加有条件判断,这种方法在执行效果上面是没有什么错误的,可是却造成了逻辑前移;本来应该在逻辑层执行的判断放在了界面层,是不合适的。2.数据访问层参与了大量的业务逻辑这种现象经常出现在大量使用存储过程的系统中,将一大堆逻辑统统放在一个存储过程中实现了,乍一看可能很有效率,其实造成了系统结构的失调,给维护带来困难,数据访问层甚者数据库要抢逻辑层的饭碗了。还以用户登录为例:下面是业务逻辑层的登录方法: //业务逻辑层的登录方法
public int Login(string userName, string password) 
{
    return DataAccess.UserAccount.Login(userName, password);

 下面是数据层的登录方法: //数据访问层的登录方法
public int Login(string userName, string password)

    SqlParameter[] parameters = new SqlParameter[]{
        // 
    };
    return SqlHelper.ExecuteProcedure("Login",parameters);

 下面是登录的存储过程: CREATE PROC Login
  @userName varchar(20),
  @password varchar(20)
AS 
  IF NOT EXISTS(SELECT * FROM UserAccount WHERE UserName = @userName)
      RETURN -1
  IF NOT EXISTS(SELECT * FROM UserAccount WHERE UserName = @userName AND password = @password)
      RETURN 1  UPDATE UserAccount
  SET LoginTimes = LoginTimes + 1
  WHERE UserName = @userName  RETURN 0
 
 分析:从上面三段代码中我们可以很显然得看到登录的业务逻辑已经全部被后移到了数据库的存储过程中。这样使用的三层结构就失去了意义,逻辑层名存实亡了;而数据库的压力会越来越大;我们修改业务逻辑的时候不是到逻辑层修改,而是要到数据库中去修改了。3.将界面层上的数据组件(如SqlDataSource)作为参数传递到业务逻辑层去赋值这样做的坏处很明显,本来是界面层依赖于业务逻辑层的,现在业务逻辑层反过来去依赖界面层的类,需要逻辑层引用System.Web命名空间,显然是错误的。4.为了省事儿,不是直接将参数传递到业务逻辑层,而是通过HttpContext直接获得界面层应该传递的参数例子:在系统设计的初期没有记录用户登录的IP地址,而到了后期发现了这个问题,要求纪录用户IP了,为了不修改业务逻辑层方法的定义,也不用修改界面层的调用方法,于是便写出了下面的代码:public int Login(string userName, string password)
{
  string userIP = System.Web.HttpContext.Current.Request.UserHostAddress;
  //follow is login steps

这一点犯的错误和3中的错误相同,导致层之间形成了依赖环。5.将事务处理放在数据访问层来做事务处理应该放在业务逻辑层处理,原因是1)事务的划分是根据业务逻辑来定义的,通常一个事务就代表完成了一个完整的逻辑操作;2)一个业务逻辑可能有几个数据操作,修改几个表中的数据,而通常数据层的类的划分是根据数据库表来划分的,如果把事务处理放在数据访问层,那么业务层的方法需要调用两个以上的数据层方法时,就会出现执行两个事务的情况,显然这是不合理的。总结:1.在三层结构的划分中我们应该使三层各负其责,谁也不能越权,谁也不能懒惰,通常情况下,逻辑层应该在满足各负其责的条件下,尽可能的厚。2.三层结构中的依赖关系很明确,界面层依赖于逻辑层,逻辑层依赖于数据访问层,不能互相依赖,而形成依赖环。相信大家读了上的文章后,如果是做过三层的,那肯定是犯了什么的错误,怎么犯错误先不说,我先说说本人(今年毕业的学生)
现在在公司开发项目的一点见解:
我现在在公司开发一个商城网站,因为数据层和业务层都是我做的,所以体会还是满深的,特别是在订单处理那块,业务非常复杂
基本要操作5个表,所以需要在业务层实现事务(关于这个我已经发了好几个帖子了),而数据层只是单纯的对某个表进行操作
虽然.net事务能解决上面的问题,但是问题有多,或者服务器配置有问题我现在想把这写操作放在数据层,那觉得有违背了三层的本意,(这样还是三层吗),所以我现在犹豫了到底要怎么做,我想大家在开发中,为了性能的提高而那样(把业务移到数据层做)做却是值得的,因为三层只是一个标准而已,并不是强制要这样做?   关于这样的错误我还想听听大家的意见?

解决方案 »

  1.   

    参考这里的文章,不过是E文的:
    Designing Data Tier Components and Passing Data Through Tiers
      

  2.   

    关于分层,我的经验是这样的:
    1、界面层不使用System.Data命名空间,但需要了解逻辑层的一些业务,这是界面组织和用户输入验证的需要。
    2、逻辑层不引用System.Web.dll,尽量避免使用System.Data,但可能会用到DataView来缓存数据。
    3、数据层只管数据存取,可能的情况下进行一些与业务无关的数据有效性验证,例如字符串长度。
      

  3.   

    楼主说得好啊,学习中!
    我个人的理解,也不是要完全拘泥于三层,最简单的一个例子SqlDataSource这个控件本身就是对三层架构的极大破坏,但是由于它的方便很多时候在要实现的业务逻辑比较简单时大量程序员用SqlDataSource来实现,主要是看中它的方便。我记得我一个老师曾经说 不要让三层架构框住你,而是要让它为你服务,如果严格按照三层来做导致性价比太低还不如不用
      

  4.   

    你的业务逻辑层就应该做与业务逻辑有关的事情.对于数据的访问才是数据层应该做的.关于你所说的事务问题.
    我发个代码片段(业务层中的一段)你可以看一下.
    这里的事务其实就是指向数据层中声明的事务属性public int ImportDataToMidst(APParameters apParams)
            {
                int batchNumber = 0;
                DbTransaction dbTrans = null;
                try
                {
                    APBatchManager apBatchManager = BusinessManagerFactory.Get<APBatchManager>();
                    dbTrans = apBatchManager.Provider.DbTransaction;//这里                Type realType = Type.GetType(apParams.ImportToMidstType);
                    APDataImporterBase dataImporter = (APDataImporterBase)Activator.CreateInstance(realType);                dataImporter.DbTransaction = dbTrans;
                    batchNumber = dataImporter.Process(apParams);                dbTrans.Commit();//我的业务处理完了.事情办完了,可以提交了.
                }
                catch
                {
                    dbTrans.Rollback();                throw;
                }
                finally
                {
                    if (dbTrans != null && dbTrans.Connection != null)
                    {
                        dbTrans.Connection.Close();
                    }
                }            return batchNumber;
            }
      

  5.   

    关于分层,我的经验是这样的: 
    1、界面层不使用System.Data命名空间,但需要了解逻辑层的一些业务,这是界面组织和用户输入验证的需要。 
    2、逻辑层不引用System.Web.dll,尽量避免使用System.Data,但可能会用到DataView来缓存数据。 
    3、数据层只管数据存取,可能的情况下进行一些与业务无关的数据有效性验证,例如字符串长度。
    =================
    上面的话我觉得说得比较好,注意用词“不使用”、“但需要”、“不引用”、“尽量避免”、“只管”、“可能”。人家那个只是一个建议,不是强行规定。
      

  6.   

    关于业务层使用事务我暂时用的是TransactionScope18楼的代码能不能讲解下啊?看不懂哦,
      

  7.   


    TransactionScope这个东西很好,
    我的事务用的是DbTransaction
      

  8.   

    三层只是一个标准的分层结构,有时根据需要也是可以变通的,不是为了分层而分层,要考虑很多其他因素。另外有人提出什么SqlDataSource,这个当然不符合在三层结构中的使用标准,其实要用到类似控件,ObjectDataSourc已经为我们处理好了,直接绑定数据访问层或者逻辑层.还有就是要依形势而变,不能死搬三层模式概念,数据的复杂性写在存储过程也无可厚非,重要的是性能和速度才是网站运行的大头!
      

  9.   

    Business.Account.Exists(userName)Business.Account.Login(userName, password)Business.Account.AddLoginTime(userName); 怎么都是静态的?
      

  10.   


    http://topic.csdn.net/u/20081009/16/6a3991ea-616d-414b-8ca4-0aff9ff65b1c.html?seed=2010581342
    lz小弟.你的另一篇帖子里,sp1234已经理论化的诠释了我那天所教你的方法.你仔细看一下.体会一下吧:)
      

  11.   

    其实关于三层中的事务处理,你可定义一个接口,最后到真实的数据访问层里去实现。你总结的非常好!你可以看看我写的源码,http://www.afritxia.net/Downloads/MyHome2009(net2.0).rar 里面有关于在业务层实现事务的代码。注意 IDBTransaction 接口和其实现类