假设您有一个分为3层的应用程序:GUI,业务逻辑和数据访问。在您的业务逻辑层中,您已经描述了业务对象:getters,setter,accessors等等......您明白了。业务逻辑层的接口保证了业务逻辑的安全使用,因此您调用的所有方法和访问器都将验证输入。
当您第一次编写UI代码时,这非常棒,因为您可以信任一个整齐定义的界面。
但是这里有一个棘手的部分,当你开始编写数据访问层时,业务逻辑的接口无法满足你的需求。您需要有更多的访问器和getter来设置隐藏/用于隐藏的字段。现在你被迫侵蚀了业务逻辑的界面;现在可以从UI层设置字段,UI层没有业务设置。
由于数据访问层所需的更改,业务逻辑的接口已经被侵蚀到甚至可以使用无效数据设置业务逻辑的程度。因此,界面不再保证安全使用。
我希望我能够清楚地解释这个问题。如何防止接口侵蚀,维护信息隐藏和封装,还能满足不同层之间不同的接口需求?
如果我正确理解了这个问题,那么您已经创建了一个域模型,并且您希望编写一个对象关系映射器来映射数据库中的记录和域对象。但是,您担心使用“管道”代码来污染您的域模型,这些代码是读取和写入对象字段所必需的。
退一步,您基本上有两种选择,可以放置数据映射代码 - 在域类本身或外部映射类中。
第一个选项通常称为Active Record模式,其优点是每个对象都知道如何持久保存自己并且对其内部结构有足够的访问权限,以允许它执行映射而无需公开非业务相关字段。
例如
public class User
{
private string name;
private AccountStatus status;
private User()
{
}
public string Name
{
get { return name; }
set { name = value; }
}
public AccountStatus Status
{
get { return status; }
}
public void Activate()
{
status = AccountStatus.Active;
}
public void Suspend()
{
status = AccountStatus.Suspended;
}
public static User GetById(int id)
{
User fetchedUser = new User();
// Lots of database and error-checking code
// omitted for clarity
// ...
fetchedUser.name = (string) reader["Name"];
fetchedUser.status = (int)reader["statusCode"] == 0 ? AccountStatus.Suspended : AccountStatus.Active;
return fetchedUser;
}
public static void Save(User user)
{
// Code to save User's internal structure to database
// ...
}
}
在此示例中,我们有一个对象,表示具有Name和AccountStatus的用户。我们不希望允许直接设置Status,可能是因为我们要检查更改是否是有效的状态转换,因此我们没有setter。幸运的是,GetById和Save静态方法中的映射代码可以完全访问对象的名称和状态字段。
第二个选项是有一个负责映射的第二个类。这样做的优点是可以分离出业务逻辑和持久性的不同问题,从而使您的设计更加可测试和灵活。此方法的挑战是如何将名称和状态字段公开给外部类。一些选项是:
1.使用反射(没有任何关于深入挖掘对象私有部分的疑虑)
2.提供特别命名的公共制定者(例如在他们前面加上“私人”一词)并希望没有人意外地使用它们
3.如果您的语言支持它,请将setter设为内部,但授予您的数据映射器模块访问权限。例如。使用.NET 2.0以上的InternalsVisibleToAttribute或C ++中的友元函数
有关更多信息,我建议使用Martin Fowler的经典着作“企业架构模式”
但是,作为一个警告,在开始编写自己的映射器之前,我强烈建议您使用第三方对象关系映射器(ORM)工具,如nHibernate或Microsoft的Entity Framework。我参与了四个不同的项目,由于各种原因,我们编写了自己的映射器,很容易浪费大量时间来维护和扩展映射器,而不是编写提供最终用户价值的代码。到目前为止,我已经在一个项目中使用了nHibernate,虽然它最初的学习曲线非常陡峭,但你早期投入的回报却相当可观。
这是一个经典问题 - 将您的域模型与数据库模型分开。有几种方法可以攻击它,这在很大程度上取决于我认为项目的大小。您可以像其他人所说的那样使用存储库模式。如果您使用的是.net或java,则可以使用 NHibernate的 要么 过冬。
我的用处是 测试驱动开发 所以我首先编写我的UI和模型层,然后模拟数据层,因此UI和模型是围绕特定于域的对象构建的,然后我将这些对象映射到我正在使用数据层的技术。让数据库确定应用程序的设计是一个非常糟糕的主意,首先编写应用程序并稍后考虑数据。
ps问题的标题有点误导
我总是创建一个单独的程序集,其中包含
- 很多小接口(想想ICreateRepository,IReadRepository,IReadListRepsitory ......这个列表还在继续,其中大部分依赖于泛型)
- 很多具体的接口,比如IPersonRepository,继承自IReadRepository,你明白了......
你只需要用较小的接口来描述的东西,你就可以进入具体的界面了。
只要您使用IPersonRepository声明您的对象,您就可以获得一个干净,一致的界面。但是踢球者,你也可以创建一个需要f.x的类。一个ICreateRepository在它的构造函数中,所以代码最终很容易做一些非常时髦的东西。此处还有业务层中的服务接口。
- 最后,我将所有域对象都粘贴到额外的程序集中,只是为了使代码库本身更清洁,更松散耦合。这些对象没有任何逻辑,它们只是描述所有3+层数据的常用方法。
顺便说一句。为什么要在业务逻辑层中定义方法以适应数据层?
数据层应该没有理由知道有业务层..
@冰^^热火:
数据层不应该知道业务逻辑层是什么意思?您如何用数据填充业务对象?
UI向业务层中的ServiceClass请求服务,即获取由具有所需参数数据的对象过滤的对象列表。
然后,ServiceClass在数据层中创建一个存储库类的实例,并调用GetList(ParameterType过滤器)。
然后,数据层访问数据库,提取数据,并将其映射到“域”程序集中定义的通用格式。
BL不再需要处理这些数据,所以它将它输出到UI。
然后UI想要编辑项目X.它将项目(或业务对象)发送到业务层中的服务。业务层验证对象,如果正常,则将其发送到数据层进行存储。
UI知道业务层中的服务,该服务再次了解数据层。
UI负责将用户数据输入映射到对象和从对象映射,数据层负责将db中的数据映射到对象和从对象映射数据。业务层保持纯粹的业务。 :)
它可能是一种解决方案,因为它不会侵蚀界面。我想你可以有这样一个类:
public class BusinessObjectRecord : BusinessObject
{
}
数据层不应该知道业务逻辑层是什么意思?您如何用数据填充业务对象?
我经常这样做:
namespace Data
{
public class BusinessObjectDataManager
{
public void SaveObject(BusinessObject object)
{
// Exec stored procedure
{
}
}
所以问题是业务层需要向数据层公开更多功能,添加这个功能意味着向UI层暴露太多?如果我正确地理解你的问题,听起来你正试图用一个接口来满足太多,而这只会让它变得杂乱无章。为什么没有两个接口进入业务层?一个是UI层的简单,安全的界面。另一个是数据层的低级接口。
您可以将这种双界面方法应用于需要传递给UI和数据层的任何对象。
public class BusinessLayer : ISimpleBusiness
{}
public class Some3LayerObject : ISimpleSome3LayerObject
{}