编程到面向数据的接口


9

我们的代码库的一部分以以下样式编写:

// IScheduledTask.cs
public interface IScheduledTask
{
    string TaskName { get; set; }
    int TaskPriority { get; set; }
    List<IScheduledTask> Subtasks { get; set; }
    // ... several more properties in this vein
}

// ScheduledTaskImpl.cs
public class ScheduledTaskImpl : IScheduledTask
{
    public string TaskName { get; set; }
    public int TaskPriority { get; set; }
    public List<IScheduledTask> Subtasks { get; set; }
    // ... several more properties in this vein, 
    // perhaps a constructor or two for convenience.
}

也就是说,有大量的接口仅指定一组属性而没有任何行为,每个接口都有一个唯一的实现,这些实现通过自动属性来实现。该代码是由相当资深的人(比我本人要多)编写的,除了这种使用接口的合理过程代码之外。我想知道是否还有其他人遇到过/使用过这种样式,并且它是否比在没有接口的地方仅使用具体的DTO有什么优势?


1
我这样做的原因之一是出于COM兼容性的考虑,但这似乎并不是您的原因。
麦克

@Mike有趣的是,我从未使用过COM,并且在这里未使用过。我将询问代码这一部分的作者是否打算使用COM或其他任何东西。
walpen '16

我的猜测是,他希望在相对不久的将来使这些属性中的至少一个成为非自动属性,并且一开始就写出这个样板是他养成节省一些时间的习惯,尽管我不是C#的人,这就是我能说的。
Ixrec

6
恕我直言,这是过度痴迷和错误地将代码应用于接口而不是实现。一个关键的故事是唯一的实现。接口的意义是多态性,但是从某种意义上说,仅具有属性的类就是通过具有不同的值来实现的。即使具有行为-方法-单一实现也无济于事。废话遍布我们的代码库,充其量它妨碍维护。我浪费太多时间问为什么,什么,在哪里。他们为什么这样做,我看不到什么设计,其他实现在哪里?
radbobob

2
前面的大多数评论都涉及过早抽象的单个实现“代码味道”。但是,这里也有一个贫血的域模型“代码气味”,请参阅:martinfowler.com/bliki/AnemicDomainModel.html
Erik Eidt,2016年

Answers:


5

这是我的两分钱:

  • 我们不确定DTO永远不会至少有一点点行为。因此对我来说,有些物体将来可能会或不会发生行为。
  • 有这么多的唯一的实现类的一个可能的原因是缺乏设计的,例如未能看到ScheduledTaskScheduledJobScheduledEventScheduledInspection,等,应该只有一个隔离Schedulable接口进行任何实施者调度。
  • 首先创建接口是一种很好的做法,它使您可以洞悉所需内容。许多接口都根本没有实现。然后有人写了一个实现,而他们有了那个实现。如果您避免了我在第二点中提到的内容,那么唯一实现的状态是暂时的。您如何事先知道某个接口永远不会有三分之一的实现?
  • 我们为一个经过深思熟虑的接口选择的名称在将来不会改变,因此对该接口的依赖关系不会受到影响。另一方面,当某个具体类的实现方式发生重要变化时,它很容易被重命名,例如TaxSheetSessionAwareTaxSheet由于进行了大修,而名为的具体类可能会更改为该具体类,但是接口ITaxSheet可能不会那么容易地被重命名。

底线:

  • 良好的设计规范也适用于DTO。
  • 可以向DTO添加一些行为。
  • 每个接口都只有一个实现。
  • 另一方面,如果您有太多的单接口一类组合,则可能会缺乏设计要指出的地方。您的专心可能是有道理的

4
我支持您的回答,但您的第二个要点表示,所有类都应自动具有一个对应的接口,这一立场我从未同意。偶尔编写接口(可能会有第二种实现)只会导致接口激增和YAGNI。
罗伯特·哈维

1

我看到使用接口的DTO的一个特殊问题是它允许这样做:

public interface ISomeDTO { string SomeProperty { get; set; }}

public class SomeDTO : ISomeDTO
{
    public string SomeProperty { get; set; }
    string ISomeDTO.SomeProperty { get { /* different behavior */ } set { SomeProperty = value; } }
}

我已经看到这种模式被应用为快速,肮脏的骇客,以实现某些关键行为。这导致代码可能具有非常令人困惑的行为:

SomeDTO a = GetSomeDTO();
ISomeDTO ia = a;
Assert.IsTrue(a.SomeProperty == ia.SomeProperty); // assert fails!

这很难维护,并且难以解开或重构。IMO将行为添加到DTO违反了单一责任原则。DTO的目的是以持久性框架可以持久和填充的格式表示数据。

DTO不是域模型。如果DTO贫血,我们就不必担心。引用马丁·福勒对贫血领域模型的讨论

还值得强调的是,将行为放入域对象不应与使用分层将域逻辑与持久性和表示责任之类的东西分开的可靠方法相矛盾

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.