不同逻辑层上的接口

时间:2020-03-05 18:39:16  来源:igfitidea点击:

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

第一次编写UI代码时,这很棒,因为我们拥有一个可以信任的简洁定义的界面。

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

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

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

解决方案

回答

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

  • 很多小的接口(想想ICreateRepository,IReadRepository,IReadListRepsitory ..清单还在继续,其中大多数都严重依赖于泛型)
  • 可以理解很多具体的接口,例如从IReadRepository继承的IPersonRepository。只要使用IPersonRepository声明对象,我们就可以使用一个干净,一致的接口。但更重要的是,我们还可以创建一个采用f.x的类。在其构造函数中创建一个ICreateRepository,因此该代码最终将非常容易执行某些非常时髦的工作。这里在业务层中也有用于服务的接口。
  • 最后,我将所有领域对象都放入了额外的程序集中,只是为了使代码库本身更简洁,耦合更松散。这些对象没有任何逻辑,它们只是描述所有3+层数据的常用方式。

顺便提一句。为什么要在业务逻辑层中定义方法以容纳数据层?
数据层应该没有理由甚至没有业务层。

回答

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

public class BusinessObjectRecord : BusinessObject
{
}

回答

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

我经常这样做:

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

回答

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

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

ps问题的标题有点误导

回答

@ Ice ^^ Heat:

What do you mean by that the data tier should not be aware of the business logic tier? How would you fill an business object with data?

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

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

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

UI负责将用户数据输入与对象之间进行映射,数据层负责将db中的数据与对象之间进行映射。业务层保持纯粹的业务。 :)

回答

因此,问题在于业务层需要向数据层公开更多功能,而添加此功能意味着向UI层公开太多呢?如果我正确地理解了问题,这听起来像是我们试图通过单个界面满足太多要求,这只会使它变得混乱。为什么没有两个接口连接到业务层?一个可能是用于UI层的简单,安全的界面。另一个将是数据层的较低级接口。

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

public class BusinessLayer : ISimpleBusiness
{}

public class Some3LayerObject : ISimpleSome3LayerObject
{}

回答

我们可能需要将接口分为两种类型:

  • 查看界面-这些界面用于指定我们与UI的交互,以及
  • 数据接口-这些接口将允许我们指定与数据的交互

可以继承和实现两组接口,例如:

public class BusinessObject : IView, IData

这样,在数据层中,我们仅需要查看IData的接口实现,而在UI中,我们仅需要查看IView的接口实现。

我们可能要使用的另一种策略是在UI或者Data层中组合对象,以使它们仅被这些层使用,例如,

public class BusinessObject : DomainObject

public class ViewManager<T> where T : DomainObject

public class DataManager<T> where T : DomainObject

反过来,这又使业务对象对UI / View层和数据层均一无所知。

回答

如果我对问题的理解正确,则说明我们已经创建了域模型,并且希望编写一个对象关系映射器来映射数据库中的记录与域对象之间的映射。但是,我们担心要使用"管道"代码来污染域模型,而这对于读取和写入对象的字段必不可少。

退后一步,我们基本上有两种选择,可以将数据映射代码放置在域类本身内还是外部映射类内。
第一个选项通常称为"活动记录"模式,其优点是每个对象都知道如何持久保存自身,并具有对其内部结构的充分访问权限,以使其能够执行映射而无需暴露非业务相关字段。

例如

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

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

第二种选择是让第二个类负责映射。这样做的好处是可以分离出业务逻辑和持久性的不同方面,这可以使设计更具可测试性和灵活性。这种方法的挑战在于如何将名称和状态字段公开给外部类。一些选项是:
1.使用反射(对于深入探究对象的私有部分没有约束)
2.提供专门命名的公共二传手(例如,在它们前面加上"私人"一词),希望没有人会意外使用它们。
3.如果语言支持,请将设置器设为内部,但授予数据映射器模块访问权限。例如。在.NET 2.0及更高版本中使用InternalsVisibleToAttribute或者在C ++中使用好友函数

有关更多信息,我推荐Martin Fowler的经典著作"企业体系结构模式"

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

回答

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

我认为许多开发人员将数据库视为其对象的简单持久层,并且只关心那些对象所需的CRUD操作。对象模型和关系模型之间的"阻抗不匹配"花费了很多精力。这是一个主意:停止尝试。

编写存储过程以封装数据。根据需要使用代码中的结果集,数据集,数据表,SqlCommand(或者等效的java / php /任何形式)与数据库进行交互。我们不需要那些对象。一个很好的例子是将SqlDataSource嵌入到.ASPX页中。

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

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

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