C#和Visual Studio 2005中的程序集之间的循环引用

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

我试图让我的所有应用程序5分层。

码:


| UI |

|

| 业务对象|

|

| OR-Mapper |

|

| 数据访问|

|

| RDBMS |

假设我正在为用户开发具有登录/注销function的应用程序。 我在VS2005解决方案下创建了4个项目。 每个项目都是上层4层中的一个。 我正在设计我的Business Object类,如下所示: –

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); } } 

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

我的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-layer的引用。 BusinessObject-层还需要OR Mapper-layer的引用。

这应该创建循环依赖。

我该如何避免这个问题?

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

我找到了一个博客 。

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

但我不能这样做。

有人能用一个实际的例子告诉我吗?

我认为你可以做的一些事情可以帮助你的设计。 我还认为您可能希望阅读dependency injection,因为它可能为您想要做的事情提供更好的设计模式。

假设你只想做你工作的东西:

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

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

  • UserMapper类上实现UserMapper ,然后通过构造函数将它“注入”它使用静态方法创建的User类实例,所以基本上,当UserMapper创建User对象时,它为它们提供了对IUserLookup接口的引用。实现自己。

通过这种方式, User仅使用IUserLookup方法,这是在同一解决方案中,因此不需要引用。 UserMapper引用此解决方案,因此它可以创建User对象并实现IUserLookup接口。

如果OR Mapper实际上正在执行OR,那么它可能不需要对BL的引用 – 它只需要知道所涉及的类型。 但这是一个副作用……

这类问题的主要答案是“控制反转”/“dependency injection”,大概是剪掉BL下的所有东西 – 所以BL只依赖于一个接口(在基础组件中定义),但不知道具体的OR / DA / RDBMS(它们由IoC / DI提供)。

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

请注意,从技术上讲 ,可以创建循环assembly参考; 但这是一个非常糟糕的主意 – 而且工具将(有意)在每一步都与你作斗争。

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

当你的呼叫从上层移动到底层时…你的对象转换为适合每一层的专门化(但在你的情况下,你在每层上处理基元……至少在提交的代码中)……和当你的电话回来时,应该从专业化到普遍化……

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

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

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

| UI | | | 信息旅行| ** | | 业务对象| | | OR-Mapper | | | 数据访问| | | RDBMS |

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

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

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

要防止程序集之间的循环引用,您应该使用接口。例如,如果您的OR-Mapper需要调用BL的某些成员,您应该识别这些成员并将它们放在一个或多个接口中并将它们放在程序集中(例如,接口)或者在你的OR-Mapper程序集中让你的BL对象实现它们,然后就不需要在你的OR-Mapper程序集中引用你的BL了。