假设您有一个分为 3 层的应用程序:GUI、业务逻辑和数据访问。在您的业务逻辑层中,您已经描述了您的业务对象:getter、setter、accessors 等等......你明白了。业务逻辑层的接口保证了业务逻辑的安全使用,因此您调用的所有方法和访问器都将验证输入。

当您第一次编写 UI 代码时,这很棒,因为您拥有一个可以信任的、定义清晰的界面。

但棘手的部分来了,当您开始编写数据访问层时,业务逻辑的接口无法满足您的需求。您需要有更多的访问器和获取器来设置隐藏的/曾经隐藏的字段。现在你被迫侵蚀你的业务逻辑的接口;现在可以从UI层设置字段,UI层没有业务设置。

由于数据访问层所需的更改,业务逻辑的接口已经被侵蚀到甚至可以使用无效数据设置业务逻辑的程度。因此,该接口不再保证安全使用。

我希望我足够清楚地解释了这个问题。如何防止接口侵蚀,保持信息隐藏和封装,同时仍然满足不同层之间的不同接口需求?

有帮助吗?

解决方案

如果我正确理解了这个问题,那么您已经创建了一个域模型,并且您希望编写一个对象关系映射器来在数据库中的记录和域对象之间进行映射。但是,您担心读取和写入对象字段所需的“管道”代码会污染您的域模型。

退一步来说,您基本上有两种选择将数据映射代码放在哪里 - 在域类本身中或在外部映射类中。第一个选项通常称为活动记录模式,其优点是每个对象都知道如何持久化自身,并对其内部结构有足够的访问权限,以允许其执行映射,而无需公开非业务相关字段。

例如

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
        // ...
    }
}

在此示例中,我们有一个代表具有名称和帐户状态的用户的对象。我们不想允许直接设置 Status,也许是因为我们想要检查更改是否是有效的状态转换,所以我们没有 setter。幸运的是,GetById 和 Save 静态方法中的映射代码可以完全访问对象的名称和状态字段。

第二种选择是让第二个类负责映射。这样做的优点是可以分离业务逻辑和持久性的不同关注点,从而使您的设计更加可测试和灵活。此方法的挑战是如何向外部类公开名称和状态字段。一些选项是:1.使用反射(这对深入对象的私人零件没有任何疑问)2。提供专门命名的公共设置器(例如将它们带有“私人”一词),希望没有人意外使用它们3。如果您的语言支持它,请将设置器设置为内部,但授予您的数据映射器模块访问权限。例如。使用 .NET 2.0 及以上版本中的 InternalsVisibleToAttribute 或 C++ 中的友元函数

有关更多信息,我推荐 Martin Fowler 的经典著作《企业架构模式》

然而,作为警告,在开始编写自己的映射器之前,我强烈建议您考虑使用第 3 方对象关系映射器 (ORM) 工具,例如 nHibernate 或 Microsoft 的实体框架。我参与过四个不同的项目,由于各种原因,我们编写了自己的映射器,并且很容易浪费大量时间来维护和扩展映射器,而不是编写提供最终用户价值的代码。到目前为止,我已经在一个项目中使用了 nHibernate,尽管它最初的学习曲线相当陡峭,但您早期投入的投资会得到相当大的回报。

其他提示

这是一个经典问题——将领域模型与数据库模型分离。有多种方法可以攻击它,我认为这实际上取决于您的项目的大小。您可以像其他人所说的那样使用存储库模式。如果您使用 .net 或 java 您可以使用 NHibernate 或者 休眠.

我所做的是使用 测试驱动开发 所以我首先编写我的 UI 和模型层,然后模拟数据层,因此 UI 和模型是围绕特定于域的对象构建的,然后我将这些对象映射到我正在使用数据层的任何技术。让数据库决定应用程序的设计,先编写应用程序,然后再考虑数据,这是一个非常糟糕的主意。

ps问题的标题有点误导

@冰^^热:

数据层不应该知道业务逻辑层是什么意思?如何用数据填充业务对象?

UI向业务层中的ServiceClass请求服务,即获取由具有所需参数数据的对象过滤的对象列表。
然后,ServiceClass 在数据层中创建存储库类之一的实例,并调用 GetList(ParameterType 过滤器)。
然后数据层访问数据库,提取数据,并将其映射到“域”程序集中定义的通用格式。
BL 不再需要处理这些数据,因此将其输出到 UI。

然后 UI 想要编辑 Item X。它将项目(或业务对象)发送到业务层中的服务。业务层验证该对象,如果正常,则将其发送到数据层进行存储。

UI 知道业务层中的服务,而业务层又知道数据层。

UI 负责将用户数据输入映射到对象或从对象映射,数据层负责将数据库中的数据映射到对象或从对象映射数据。业务层仍然纯粹是业务。:)

这可能是一个解决方案,因为它不会侵蚀接口。我想你可以有这样的课程:

public class BusinessObjectRecord : BusinessObject
{
}

我总是创建一个单独的程序集,其中包含:

  • 很多小接口(想想 ICreateRepository、IReadRepository、IReadListRepsitory..这样的例子不胜枚举,其中大多数都严重依赖于泛型)
  • 很多具体的接口,比如 IPersonRepository,继承自 IReadRepository,你明白了。
    任何无法用较小的接口描述的东西,都可以放入具体的接口中。
    只要您使用 IPersonRepository 来声明您的对象,您就可以获得一个干净、一致的界面来使用。但更重要的是,你还可以制作一个需要 f.x. 的课程。在其构造函数中包含一个 ICreateRepository ,因此代码最终将非常容易做一些非常时髦的事情。这里还有业务层服务的接口。
  • 最后,我将所有域对象粘贴到额外的程序集中,只是为了使代码库本身更干净、更松散耦合。这些对象没有任何逻辑,它们只是描述所有 3+ 层数据的通用方式。

顺便提一句。为什么要在业务逻辑层中定义方法来适应数据层?
数据层应该没有理由知道业务层的存在。

数据层不应该知道业务逻辑层是什么意思?如何用数据填充业务对象?

我经常这样做:

namespace Data
{
    public class BusinessObjectDataManager
    {
         public void SaveObject(BusinessObject object)
         {
                // Exec stored procedure
         {
    }
}

那么问题来了,业务层需要向数据层暴露更多的功能,而添加这个功能就意味着向UI层暴露了太多的功能?如果我正确理解你的问题,听起来你试图通过单一界面来满足太多需求,而这只会导致它变得混乱。为什么不有两个接口进入业务层呢?其中之一是为 UI 层提供一个简单、安全的界面。另一个是数据层的较低层接口。

您也可以将这种双接口方法应用于需要传递到 UI 层和数据层的任何对象。

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

您可能希望将接口分为两种类型,即:

  • 查看界面——指定您与 UI 交互的界面,以及
  • 数据接口——这些接口允许您指定与数据的交互

可以继承并实现这两组接口,以便:

public class BusinessObject : IView, IData

这样,在你的数据层中你只需要看到IData的接口实现,而在你的UI中你只需要看到IView的接口实现。

您可能想要使用的另一种策略是在 UI 或数据层中组合对象,以便它们仅由这些层使用,例如,

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

这反过来又允许您的业务对象保持对 UI/视图层和数据层的无知。

我将继续我违背常规的习惯,并说你应该质疑为什么要构建所有这些极其复杂的对象层。

我认为许多开发人员将数据库视为其对象的简单持久层,并且只关心这些对象所需的 CRUD 操作。人们在对象模型和关系模型之间的“阻抗不匹配”上投入了太多的精力。这是一个想法:别再尝试了。

编写存储过程来封装您的数据。根据需要从代码中使用结果集、DataSet、DataTable、SqlCommand(或 java/php/任何等效项)来与数据库交互。你不需要那些对象。一个很好的例子是将 SqlDataSource 嵌入到 .ASPX 页面中。

您不应该试图向任何人隐藏您的数据。开发人员需要准确了解他们如何以及何时与物理数据存储进行交互。

对象关系映射器是魔鬼。停止使用它们。

构建企业应用程序通常是管理复杂性的一种练习。您必须使事情尽可能简单,否则您将拥有一个绝对无法维护的系统。如果您愿意允许一些耦合(无论如何,这是任何应用程序所固有的),您可以取消业务逻辑层和数据访问层(用存储过程替换它们),并且您不需要其中任何一个接口。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top