不同逻辑层的接口

时间:2008-08-12 21:06:39

标签: architecture

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

当您第一次编写UI代码时,这非常棒,因为您可以信任一个整齐定义的界面。

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

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

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

9 个答案:

答案 0 :(得分:6)

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

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

E.g

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,虽然它最初的学习曲线相当陡峭,但你早期投入的回报却相当可观。

答案 1 :(得分:5)

这是一个经典问题 - 将您的域模型与数据库模型分开。有几种方法可以攻击它,这在很大程度上取决于我认为项目的大小。您可以像其他人所说的那样使用存储库模式。如果您使用的是.net或java,则可以使用NHibernateHibernate

我所做的是使用Test Driven Development所以我首先编写我的UI和模型层,并且模拟数据层,因此UI和模型是围绕特定于域的对象构建的,然后我将这些对象映射到技术我正在使用数据层。让数据库确定应用程序的设计是一个非常糟糕的主意,首先编写应用程序并稍后考虑数据。

ps问题的标题有点误导

答案 2 :(得分:1)

@冰^^热量:

  

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

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

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

UI知道业务层中的服务,该服务再次了解数据层。

UI负责将用户数据输入映射到对象和从对象映射,数据层负责将db中的数据映射到对象和从对象映射数据。业务层保持纯粹的业务。 :)

答案 3 :(得分:0)

我总是创建一个包含以下内容的单独程序集:

  • 许多小型接口(想想ICreateRepository,IReadRepository,IReadListRepsitory ......这个列表还在继续,其中大部分依赖于泛型)
  • 许多具体的接口,比如IPersonRepository,继承自IReadRepository,你明白了...... 你只需要用较小的接口来描述的东西,你就可以进入具体的界面了 只要您使用IPersonRepository声明您的对象,您就可以获得一个干净,一致的界面。但是踢球者,你也可以创建一个需要f.x的类。一个ICreateRepository在它的构造函数中,所以代码最终很容易做一些非常时髦的东西。此处还有业务层中的服务接口。
  • 最后,我将所有域对象都粘贴到额外的程序集中,只是为了使代码库本身更清洁,更松散耦合。这些对象没有任何逻辑,它们只是描述所有3层以上数据的常用方法。

顺便说一下。为什么要在业务逻辑层中定义方法以适应数据层? 数据层应该没有理由知道有业务层..

答案 4 :(得分:0)

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

public class BusinessObjectRecord : BusinessObject
{
}

答案 5 :(得分:0)

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

我经常这样做:

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

答案 6 :(得分:0)

所以问题是业务层需要向数据层公开更多功能,添加此功能意味着向UI层暴露太多?如果我正确理解你的问题,听起来你正试图用一个界面来满足太多,这只会让它变得杂乱无章。为什么没有两个接口进入业务层?一个是UI层的简单,安全的界面。另一个是数据层的低级接口。

您可以将这种双界面方法应用于需要传递给UI和数据层的任何对象。

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

答案 7 :(得分:0)

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

  • 查看界面 - 这些界面指定您与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 / View层和数据层。

答案 8 :(得分:0)

我将继续养成违背粮食的习惯,并说你应该质疑为什么要建造所有这些非常复杂的物体层。

我认为许多开发人员认为数据库是其对象的简单持久层,并且只关注这些对象所需的CRUD操作。对象和关系模型之间的“阻碍不匹配”付出了太多的努力。这是一个想法:停止尝试。

编写存储过程以封装数据。根据需要使用结果集,DataSet,DataTable,SqlCommand(或java / php /等价物)与数据库进行交互。你不需要那些对象。一个很好的例子是将SqlDataSource嵌入到.ASPX页面中。

您不应该试图向任何人隐藏您的数据。开发人员需要准确了解他们与物理数据存储的交互方式和时间。

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

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