【问题标题】:Should business objects be able to create their own DTOs?业务对象是否应该能够创建自己的 DTO?
【发布时间】:2011-02-04 14:33:27
【问题描述】:

假设我有以下课程:

class Camera
{
    public Camera(
        double exposure,
        double brightness,
        double contrast,
        RegionOfInterest regionOfInterest)
    {
        this.exposure = exposure;
        this.brightness = brightness;
        this.contrast = contrast;
        this.regionOfInterest = regionOfInterest;
    }

    public void ConfigureAcquisitionFifo(IAcquisitionFifo acquisitionFifo)
    {
        // do stuff to the acquisition FIFO
    }

    readonly double exposure;
    readonly double brightness;
    readonly double contrast;
    readonly RegionOfInterest regionOfInterest;
}

...和一个 DTO,用于跨服务边界 (WCF) 传输相机信息,例如,在 WinForms/WPF/Web 应用程序中查看:

using System.Runtime.Serialization;

[DataContract]
public class CameraData
{
    [DataMember]
    public double Exposure { get; set; }

    [DataMember]
    public double Brightness { get; set; }

    [DataMember]
    public double Contrast { get; set; }

    [DataMember]
    public RegionOfInterestData RegionOfInterest { get; set; }
}

现在我可以向Camera 添加一个方法来公开其数据:

class Camera
{
    // blah blah

    public CameraData ToData()
    {
        var regionOfInterestData = regionOfInterest.ToData();

        return new CameraData()
        {
            Exposure = exposure,
            Brightness = brightness,
            Contrast = contrast,
            RegionOfInterest = regionOfInterestData
        };
    }
}

,我可以创建一个方法,该方法需要传入一个特殊的 IReporter,以便相机向其公开其数据。这消除了对 Contracts 层的依赖(Camera 不再需要知道 CameraData):

class Camera
{
    // beep beep I'm a jeep

    public void ExposeToReporter(IReporter reporter)
    {
        reporter.GetCameraInfo(exposure, brightness, contrast, regionOfInterest);
    }
}

那我应该怎么做?我更喜欢第二个,但它要求 IReporter 有一个 CameraData 字段(由GetCameraInfo() 更改),这感觉很奇怪。另外,如果有更好的解决方案,请与我分享!我还是个面向对象的新手。

【问题讨论】:

标签: c# oop dto


【解决方案1】:

我通常会说,他们不应该这样说,因为 DTO 是特定于服务或应用程序的,而域模型是您的“最里面”层,应该没有依赖关系。 DTO 是域模型之外的 的实现细节,因此,它打破了域模型了解它们的抽象。

您是否考虑过为此查看AutoMapper?你最终会以这种方式编写更少的代码。在这种情况下,我认为您可以轻松逃脱:

Mapper.CreateMap<RegionOfInterest, RegionOfInterestData>();
Mapper.CreateMap<Camera, CameraData>();

后来:

CameraData cd = Mapper.Map<Camera, CameraData>(camera);

这不仅减少了代码搅动,而且在其自己的“映射层”中划分了映射代码 - 您有一个或多个模块来注册这些映射,您可以将它们放入真正使用 DTO 的任何程序集中。

当然,您总是可以创建扩展方法来简化实际映射:

public static class CameraExtensions
{
    public static CameraData ToCameraData(this Camera camera)
    {
        return Mapper.Map<Camera, CameraData>(camera);
    }
}

这使得整个事情变得像编写camera.ToCameraData() 一样简单,但没有在域对象 (Camera) 和 DTO (CameraData) 之间创建硬依赖关系。您基本上拥有原始版本的所有易用性,但没有耦合。

如果您创建这些依赖项是因为您试图从未公开公开的私有 Camera 数据创建 CameraData 对象,那么我的第一反应是,这个设计有些地方不太对劲。为什么不在Camera 对象上公开只读属性?如果你通过ToData方法让外界访问它们,那么你显然没有隐藏这些信息,你只是让它变得更麻烦。

如果您在 3 个月后决定需要另一种 DTO,该怎么办?您不必在每次想要支持新用例时都修改以域为中心的 Camera 对象。在我看来,最好在类中放置一些只读的公共属性,以便映射器可以访问他们需要的属性。

【讨论】:

  • 自动映射器 +1。如果您想以有意义的方式分隔映射 (derans.blogspot.com/2009/12/automapper-aspnet-mvc.html),您还可以定义映射配置文件,并且您可以编写一个单元测试以确保您没有通过调用 Mapper.AssertConfigurationIsValid() 简单地留下未映射的某些字段;
  • 这为永远不会产生影响的抽象增加了不必要的运行时间开销。 DTO 是您在程序各部分之间建立的 API/合同的一部分。如果您更改了在程序的不同部分之间发送的数据结构,那么您就改变了设计。如果您重新设计,再多的自动映射都不会阻止您重新编码。
  • @Jay:恐怕我没有听从你的推理。 DTO = 数据传输对象,它是一个映射/交换合约,不是域 API 的一部分。显然,如果您更改域对象,那么您将不得不更改程序的许多其他部分;使用 DTO(根据域对象上的公共数据构建)的主要原因是避免在某些外部依赖项发生变化时必须更改域模型,以及风险回归或级联效应。见en.wikipedia.org/wiki/Single_responsibility_principle
  • 至于增加开销,是的,它确实增加了一些开销,但是如果您通过DataContractSerializer 和 WCF 服务运行它,那么您已经在谈论更多几个数量级的开销比 AutoMapper 将创建。
  • @Jay:就效率而言,它的效率不亚于耦合方法。就复杂性而言,拥有专用映射器(同样,SRP)的复杂性要低得多。关于创建任意区别,是的,这是我们作为程序员的工作。关于为用户增加价值,代码质量永远不会,它只是降低了维护成本。至于我个人的能力,它们与这个答案的质量或正确性几乎没有关系,对于那些相信它们的人,我会坚持自己的记录。
【解决方案2】:

我通常是这样处理的:业务对象在业务层 DLL 中是“纯”的。然后,我在 WCF 层中添加了一个 Camera.MapToCameraDataContract 扩展方法。我通常在服务层上也有反向扩展方法(CameraDataContract.MapToCamera)。

所以本质上我是第一种方式,但是ToData方法是一种扩展方法,只有WCF层知道。

【讨论】:

  • 这需要我以某种方式公开 Camera 的字段,因为扩展方法无法访问它们扩展的类的私有成员,但我希望尽可能避免这种情况。
【解决方案3】:

第一个(暴露 DTO)对我来说更可取。它更简单,运行速度更快,更容易理解和维护。由于 DTO 确实对数据库对象没有依赖关系,因此仍然很容易达到减少依赖关系的目的。

【讨论】:

  • 它不会减少依赖,它会创建依赖——特别是域对象对特定 DTO 的依赖。
  • DTO 是您与数据层之间合同的一部分。添加 Automapper/Hibernate 会为一个全新的代码库添加一个依赖项,而不仅仅是一个其结构由 API 固定的对象。
  • DTO 不一定指数据层;例如,DTO 通常也用作 MVC 架构中的视图模型。 “添加 Automapper/Hibernate”(相关性?)确实“添加了依赖项”,但我不确定我是否明白你的意思;依赖关系在域对象之外,完全处于不同的级别。你是建议我们应该避免使用框架并重写我们的代码来做所有事情,还是我还缺少其他东西?
  • 添加自动映射器(或任何域映射代码库)会添加对自动映射器代码库的依赖。程序员必须学习使用自动映射器并通过配置进行设置。编译器必须通过更多代码来生成应用程序。添加每次启动应用程序、读取配置文件和执行映射时加载映射代码的运行时间成本。我很困惑怎么会有人推荐这样的东西。
【解决方案4】:

我将 to/from 方法放在我的 DTO 上:

[DataContract]
public class CameraData
{
    ...
    public Camera ToCamera() { ... }

    public static CameraData FromCamera(Camera c) { ... }
}

这样我的域对象就不必知道我的 DTO。

【讨论】:

    【解决方案5】:

    你的服务是 WCF 到 WCF 吗?

    如果是,那么您可以选择将您的业务对象用作 DTO(只要您的业务对象不了解持久性)。如果您这样做,我建议您将 CameraData 类更改为 ICameraData 接口并让 Camera 实现 ICameraData。将属性(DataContract 等)保留在界面上。

    然后您可以将业务对象从客户端传递到服务器。请注意任何特定于客户端或服务器端的逻辑。

    blog post 中的第一张图片显示了在客户端重用业务对象对象是多么容易(当您执行“添加服务引用”时会显示对话框)。该博客文章包含一些关于重用 biz 对象的问题之一的信息。

    我不知道你想用 ExposeToReporter 实现什么,但你是对的,它看起来不正确,我个人会在 IReporter 上放置一个采用 ICameraData 参数的方法,然后在报告器上设置详细信息在里面。

    dnrtv 是学习这些东西的绝佳来源。观看标题中包含 WCF 的所有内容,尤其是 Miguel Castro 的 Extreme WCF!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-27
      • 2013-11-25
      • 1970-01-01
      • 2011-01-22
      • 1970-01-01
      • 2011-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多