4

我正在努力标准化我所有应用程序的分层/n-Tiered 设计的一种单一方式。

我正在尝试将我的所有应用程序分层为 5 层。

代码:


| 用户界面 |

|

| 业务对象 |

|

| 或映射器 |

|

| 数据访问 |

|

| 关系型数据库 |

假设我正在为用户开发一个具有登录/注销功能的应用程序。我在 VS2005 解决方案下创建了 4 个项目。每个项目都针对上 4 层之一。我正在设计我的业务对象类如下:-

public class User
{
    private string _username;
    public string Username
    {
        get { return _username; }
        set { _username = value; }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { _password = value; }
    }

    public User()
    {
    }

    public bool LogIn(String username, String password)
    {
        bool success = false;

        if (UserMapper.UsernameExists(username))
        {
            success = UserMapper.UsernamePasswordExists(username, password);
        }
        else
        {
            //do nothing
        }

        return success;
    }

    public bool LogOut()
    {
           bool success;
        //----some logic
           return success;
    }

    public static User GetUserByUsername(string username)
    {
        return UserMapper.GetUserByUsername(username);
    }

    public static UserCollection GetByUserTypeCode(string code)
    {
        return UserMapper.GetByUserTypeCode(code);
    }
}

这就是我如何为我的对象提供一些与现实世界场景相匹配的功能。这里 GetByUsername() 和 GetByUserTypeCode() 是 getter 函数。这些功能与现实世界的逻辑不匹配。因为,在现实世界中,用户永远不会“通过用户名获取”或“通过 UserTypeCode 获取”。所以这些函数保持静态。

我的 OR Mapper 层类如下:-

public static class UserMapper
{
    public static bool UsernameExists(String username)
    {
        bool exists = false;

        if (UserDA.CountUsername(username) == 1)
        {
            exists = true;
        }

        return exists;
    }

    public static bool UsernamePasswordExists(String username, String password)
    {
        bool exists = false;

        if (UserDA.CountUsernameAndPassword(username, password) == 1)
        {
            exists = true;
        }

        return exists;
    }
}

最后,DA类如下:-

public static class UserDA
{
    public static int CountUsername(string username)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }  

    public static int CountUsernameAndPassword(string username, string password)
    {
        int count = 0;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT COUNT(*) 
                                FROM User 
                                WHERE User_name = @User_name AND Pass_word = @Pass_word";
                command.Parameters.AddWithValue("@User_name", username);
                command.Parameters.AddWithValue("@Pass_word", password);

                command.Connection.Open();
                object idRaw = command.ExecuteScalar();
                command.Connection.Close();

                if (idRaw == DBNull.Value)
                {
                    count = 0;
                }
                else
                {
                    count = (int)idRaw;
                }
            }
            catch (Exception ex)
            {
                count = 0;
            }
        }

        return count;
    }

    public static int InsertUser(params object[] objects)
    {
        int count = -1;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"INSERT INTO User(ID, User_name, Pass_word, RegDate, UserTypeCode, ActualCodeOrRoll) 
                                                            VALUES(@ID, @User_name, @Pass_word, @RegDate, @UserTypeCode, @ActualCodeOrRoll)";
                command.Parameters.AddWithValue("@ID", objects[0]);
                command.Parameters.AddWithValue("@User_name", objects[1]);
                command.Parameters.AddWithValue("@Pass_word", objects[2]);
                command.Parameters.AddWithValue("@RegDate", objects[3]);
                command.Parameters.AddWithValue("@UserTypeCode", objects[4]);
                command.Parameters.AddWithValue("@ActualCodeOrRoll", objects[5]);

                command.Connection.Open();
                count = command.ExecuteNonQuery();
                command.Connection.Close();
            }
            catch (Exception ex)
            {
                count = -1;
            }
        }

        return count;
    }

    public static SqlDataReader GetUserByUsername(string username)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE User_name = @User_name";
                command.Parameters.AddWithValue("@User_name", username);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }

    public static SqlDataReader GetUserByUserTypeCode(string userTypeCode)
    {
        SqlDataReader dataReader = null;

        SqlConnection conn = DBConn.Connection;

        if (conn != null)
        {
            try
            {
                SqlCommand command = new SqlCommand();
                command.Connection = conn;
                command.CommandText = @"SELECT * FROM User WHERE UserTypeCode = @UserTypeCode";
                command.Parameters.AddWithValue("@UserTypeCode", userTypeCode);

                command.Connection.Open();

                dataReader = command.ExecuteReader(CommandBehavior.CloseConnection);

            }
            catch (Exception ex)
            {
                dataReader.Close();
                dataReader.Dispose();
            }
        }

        return dataReader;
    }
}

如果有人仔细研究这些类,他可以理解,OR Mapper 层需要 BusinessObject 层的引用。BusinessObject-layer 也需要 OR Mapper-layer 的引用。

这应该创建一个循环依赖。

我怎样才能避免这个问题?

有人建议使用普通数据传输对象 (DTO)。但是,据我所知,根据 OOP,现实世界对象的属性和功能应该作为一个类组合在一起。如果我使用 DTO,那么如何将功能封装到一个类中?此外,我正在创建另一个没有任何属性(BO)的类。对我来说,这两种方式都违反了 OOP。如果我这样做了,那么 OOP 在这个世界上有什么用?相同的答案可以应用于“UserManager”类。

我找到了一个博客

它讨论了实现接口。定义一个单独的接口,在 BusinessObject 中的数据类中实现它,并针对 BusinessObject 和 OR-Mapper 层中的接口进行编程。

但我不能这样做。

谁能用一个实际的例子告诉我?

4

4 回答 4

4

我认为您可以一起做一些事情来帮助您的设计。我还认为您可能想阅读依赖注入,因为它可能为您想要做的事情提供更好的设计模式。

假设您只想使您拥有的工作:

  • 首先,static从您的类中删除这些方法User,因为它们“创建”用户,因此最好留在UserMapper.

  • 之后,仍有许多方法可能使用该类UserMapper的功能User。创建一个支持and方法的接口IUserLookup(或其他东西) ;将此接口与类放在同一个项目中。UserNameExistsUserNamePasswordExistsUser

  • IUserLookup在类上实现UserMapper,然后通过构造函数将其“注入”到User它使用静态方法创建的类实例中,因此基本上,作为UserMapper创建User对象,它为它们提供了IUserLookup对其实现自身的接口的引用。

这样,User只使用方法 on IUserLookup,它们在同一个解决方案中,因此不需要参考。并UserMapper引用此解决方案,因此它可以创建User对象并实现IUserLookup接口。

于 2009-05-29T04:58:56.513 回答
3

如果 OR Mapper 实际上是在做 OR,那么它可能不需要对 BL 的引用——它只需要知道所Type涉及的 (s)。但这是一个附带问题...

此类问题的主要答案是“控制反转”/“依赖注入”,大概是在 BL 下剪断所有内容 - 所以 BL 仅依赖于接口(在基础程序集中定义),但不知道具体的 OR/DA/RDBMS(它们由 IoC/DI 提供)。

这是一个很大的话题,所以我故意含糊其辞。我个人喜欢 StructureMap,但有很多可用的 IoC/DI 工具。

请注意,从技术上讲可以创建循环程序集引用;不过,这是一个非常糟糕的主意——而且这些工具会(故意)在每一步都与你作对。

于 2009-05-29T04:58:37.090 回答
0

在您上面提交的代码中,没有循环依赖的证据。

当您的呼叫从上层传输到底层时......您的对象转换为适合每一层的专业化(但是在您的情况下,您正在处理每一层上的原语......至少在提交的代码中)......并且当您的电话返回时,它应该从专业化转向泛化......

反之亦然,如果以这种方式观察单个路径,则不存在循环依赖问题。但是,如果在任何分层中您尝试为两侧信息行进路径实现规范化场景,那么我们就会遇到问题,因为每个层都将依赖并需要其封闭层的引用。

但是在您的代码中没有这种循环依赖的证据。但是,如果出现这种情况,可以通过实现接口层或适配器模式(接口层是适配器模式)来避免这种情况。

例如,我们有一个 Layer InformationTravel (IT)...(好吧,我明白这听起来不太好)

| 用户界面 | | | 资讯旅游 | ** | | 业务对象 | | | 或映射器 | | | 数据访问 | | | 关系型数据库 |

您所做的是为您的用户业务对象,声明一个接口 IUser 并在用户业务对象中实现它......

然后BO就有了IT的参考。对象创建应该只在实现 IT 接口的 BO 层中。这完全没问题。当您需要将此对象传递给您的 ORM 时,您通过将其切片到 IT 中实现的接口来传递它,当您收到它时,您在进行必要的修改后再次返回相同的对象。

但这再次暗示您无法在多个层上创建 BO,因为只有 BO 具有接口的实现。但是,如果您真的无法避免这种情况,那么您必须在多个位置提供实现或使用适配器模式(将您的对象封装到客户端期望的另一个对象中)。

于 2009-05-29T05:19:34.880 回答
0

To prevent circular reference between assemblies you should use interfaces .For example if your OR-Mapper needs to invoke some members of BL you should recognize these members and put them in one or several interfaces and put them either in an assembly (Interfaces for example) or in your OR-Mapper assembly and let your BL objects implement them then there will be no need to reference your BL in your OR-Mapper assembly.

于 2009-05-29T06:54:17.987 回答