【问题标题】:Should Interfaces be aware of Domain Objects?接口应该知道域对象吗?
【发布时间】:2010-03-05 16:13:13
【问题描述】:

在我的系统中,我有两个不同的项目,一个定义它的接口,另一个定义它的域对象。到目前为止,我设法让接口定义独立于域对象,但我现在觉得需要将某些域对象作为接口中定义的方法的参数或作为此类的返回值。

我应该拒绝并尝试保持接口独立于域对象,还是这是一个毫无根据的担忧?

编辑 - 在发布问题后,我考虑为每个域对象定义一个接口,这样它们就可以保持独立 - 不知道这是否是矫枉过正,或者是否需要付出代价,以便它们保持独立。

编辑#2 - 我被要求举一个例子,所以我会尽量保持简单。我有一个处理图像转换的过程,我的一个域对象是一个保存诸如分辨率、页面列表、哈希等信息的类。让它被称为 DocumentInfo。我有一个类使用 DocumentInfo 来执行诸如 GeneratePdfFromTiff 之类的操作;我先将GeneratePdfFromTiff定义为IImageHandler的接口方法,然后由ImageHandler实现。

问题是 - GeneratePdfFromTiff 的参数之一是 DocumentInfo。在这里,我在接口级别定义的方法必须知道在域级别定义的 DocumentInfo。这就是我关心的那种依赖。

【问题讨论】:

  • 外部应用程序现在如何访问您的域对象,您的接口提供了什么样的抽象?这绝对是一个有趣的问题,但我认为它不承认目前措辞的一般性答案。
  • @Jeff - 它实际上是一个由子系统组成的系统,由于我可以访问所有源,因此很难将它们分开。我相信可能有一个通用的答案,即为每个可由接口使用的域对象创建一个接口,我将不得不考虑并尝试看看。
  • ávio - 更新绝对有帮助!我在下面给出了我的镜头。

标签: oop dns interface


【解决方案1】:

这是一个艰难的,我会试一试。为问题 +1。

如果我认为在现实世界中,领域模型应该知道如何与彼此的内部运作进行交互。当然,这在很大程度上取决于您的确切领域模型,因为同一事物的不同模型会提出非常不同的表示。

每个域模型类的接口的问题在于,您的域模型本身已经是对现实的特定视图,就像接口一样。接口仅在该特定域内有效。您不能将相同的ICar 用于交通规划、家庭收入计算器和汽车空气动力学优化。

因此,我怀疑将它们的依赖关系隔离得太远很有用。

据我所知,接口大多(总是?)是公开的,这会导致访问问题。接口不应公开有关实现的细节,但域模型类包含实际的实现。因此,您必须为每个细节创建另一个抽象机制,而不是“仅访问一些值”。例如,您不能访问系统中的任何 ID、用户等。您需要一个抽象 ID 类型、一个 ID 提供者等。我相信这在大型现实世界模型中可能非常麻烦。

当然,某些“自然域边界”仍应被隔离。如果您构建一个社交文本编辑器,那么在传统文本编辑器中也会出现的所有域类都应该与社交网络相关项目完全隔离。因此,文本编辑器应该只知道IUser。但我想我不会告诉你任何新的东西......

恐怕这不是太令人满意,但它似乎本质上是一条细线。

【讨论】:

    【解决方案2】:

    我会牢记使用该界面的软件工程师的兴趣。尝试解耦可以使代码更抽象,通常更难使用。这意味着需要编写更多代码才能“完成工作”,而编写这些代码需要付出更多努力。

    奇怪的是,由于额外的复杂性,更改内容也变得更加困难,因为更多的代码行取决于您域的可见界面。这与您可能想要实现的目标相反,因为解耦应该有助于使系统更易于理解和维护(除其他外)。

    但这真的很大程度上取决于您打算公开什么类型,以及向谁公开。可以举一些例子吗?

    【讨论】:

      【解决方案3】:

      您可能确实需要。如果您的域模型引用了接口库,则您不能接口库引用域模型,因为这会创建循环引用。

      即使不是这样,从服务中引用域模型也会在接口和域模型之间产生硬耦合。这意味着任何实现或引用接口的人也引用了域模型。你可能不想要那个。

      我强烈建议您从希望与接口一起使用的域对象中提取接口。

      如果您已经有一个名为Foo 的类,则从中提取和接口IFoo,并在您的其他接口中使用它:

      public interface IMyService
      {
          public IBar DoStuff(IFoo foo);
      }
      

      一旦你开始使用域对象作为接口,你会发现可能有更多的地方可以用接口替换具体的类。每次您这样做时,您的代码都会变得更加松散耦合,这是一件好事。它使您的代码更易于维护,更能适应变化。

      当您提取界面时,请记住您必须执行深度提取

      如果Foo 看起来像这样

      public class Foo
      {
          public Baz Baz { get; }
      }
      

      界面应该是这样的:

      public interface IFoo
      {
          IBaz Baz { get; }
      }
      
      public interface IBaz {}
      

      (如果您使用其他语言编写,请为 C# 代码道歉...)

      【讨论】:

      • @Mark - 感谢您的回答,它验证了我的一个选项,即为每个接口可以使用的域对象创建一个接口。顺便说一句,不需要道歉 - 我正在使用 C# :)
      【解决方案4】:

      我建议为具有行为的域类定义接口,但不要为简单的容器类或值类型定义接口。

      如果GeneratePdfFromTiff 只是将DocumentInfo 视为parameter object 并且文档信息类没有任何逻辑,则它应该只是一个可公开访问的类型。

      如果文档信息类执行计算或做其他有趣的事情,那么绝对值得提取一个接口。 (马克在他的回答中提到了所有很好的理由。)

      【讨论】:

      • @Jeff - 谢谢。我了解有关 DocumentInfo 的参数对象问题,但即使它可能没有太多行为,它也不也是域的一部分吗?
      • 我同意它应该是域的一部分——我只是认为如果没有要封装的行为,通过接口解耦它没有任何价值。
      • 但这就是难题 - 如果它是在域中定义的,则它不能被接口引用,因为它会创建循环依赖,除非它已提取其接口。
      • 哦,啊,我什至都没注意是吗? D:我想我会考虑将它们组合到同一个程序集中,或者将 DocumentInfo 类提取到一个单独的程序集中。
      • @Otávio - 再想一想,鉴于您所描述的内容,我怀疑DocumentInfo 是一个比您当前域集合中表达的概念域更通用的概念域的一部分。
      猜你喜欢
      • 2011-01-25
      • 2012-05-27
      • 2015-04-27
      • 2011-05-07
      • 2012-11-19
      • 2016-12-29
      • 1970-01-01
      • 1970-01-01
      • 2013-11-25
      相关资源
      最近更新 更多