使用与领域实体的一对一接口是好还是坏做法? 为什么?

发布于 2024-07-20 06:52:36 字数 1076 浏览 2 评论 0原文

我在我开发的一些 DDD 企业应用程序中看到的一件事是使用与域实体相同的接口,以及属性和功能的一对一映射。 事实上,域对象始终通过其一对一接口来使用,并且所有域实体都具有这种风格的一对一接口。

例如:

域对象帐户:

public class Account : IAccount
{
     public string Name {get;set;}
     //...some more fields that are also in IAccount
     public decimal Balance {get;set;}
}

它是匹配的接口

public interface IAccount
{
   string Name {get;set;}
   //... all the fields in Account
   decimal Balance {get;set;}
}

但最近我越来越相信这实际上是一种反模式。
我由开源社区的一些架构师运行了它,他们说这是基于设计错误或缺陷,位于设计链的某个地方。

所以我告诉我的同事,他们应该停止为域对象创建接口。 因为它们没有任何目的,并且每当更新域实体时都必须更新接口。

首先,有人声称这些接口提供了“解耦”,但我反驳说,因为这些接口与域实体具有一对一的关系,所以它们并没有真正提供任何解耦,所以对接口的更改意味着域实体,反之亦然。

下一个主张是我们需要用于测试目的的接口。 我的反驳是,Rhino-mocks 提供了具体类的模拟和存根。 但他们声称 Rhino-mocks 在具体类方面存在问题。 我不知道我是否相信这一点,即使 rhino-mocks 在具体类方面存在问题,这并不一定意味着我们应该为领域实体使用接口。

所以我很好奇:

为什么您的域实体需要一对一的接口?

为什么不?

为什么这是一个好或坏的做法?

谢谢阅读!

编辑:我应该注意,我一直使用接口,并且我相信,如果需要的话,我会立即使用接口。 但我特别指的是具有一对一接口的域实体。

One thing I see in some DDD enterprise apps that I work on, is the use of interfaces that are identical to the domain entities, with a one-to-one mapping of properties and functions. Indeed a domain object is always used through it's one-to-one interface, and all domain entities have a one-to-one interface in this style.

For example:

Domain object Account:

public class Account : IAccount
{
     public string Name {get;set;}
     //...some more fields that are also in IAccount
     public decimal Balance {get;set;}
}

And it's matching interface

public interface IAccount
{
   string Name {get;set;}
   //... all the fields in Account
   decimal Balance {get;set;}
}

But lately I've become increasingly convinced that this is, in fact, an anti-pattern.
I ran it by some architects in the open source community, and they say that this is based on design mistakes or flaws, somewhere up the chain of design.

So I tell my colleagues that they should quit creating interfaces for the Domain objects. Because there is no purpose to them, and you have to update the interface whenever you update the domain entities.

First the claim was made that these interfaces provide 'decoupling', but I counter that because the interfaces have a one-to-one relationship with the domain entities that they do not really provide any decoupling, a change to the interface means a change in the domain entity and vice-versa.

The next claim is that we need the interfaces for testing purposes. My counter is that Rhino-mocks provides for the mocking and stubbing of concrete classes. But they claim that Rhino-mocks has trouble with concrete classes. I don't know if I buy that, even if rhino-mocks has trouble with concrete classes, that doesn't necessarily mean we should use interfaces for the domain entities.

So I'm curious:

Why would you have one-to-one interfaces for your domain entities?

Why not?

Why is it a good or bad practice?

Thanks for reading!

EDIT: I should note that I use interfaces all the time, and I believe that if it's called for I will use an interface at the drop of a hat. But I'm specifically referring to domain entities with one-to-one interfaces.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

何必那么矫情 2024-07-27 06:52:37

实体上的一对一接口是一种反模式

James Gregory 说得比我更好 此处

One-to-One Interfaces on entities are an anti-pattern

James Gregory put it better than me here.

肥爪爪 2024-07-27 06:52:37

我同意你的看法。 接口应该充当契约,因此与域实体建立一对一的接口没有任何价值。 如果您想抽象某种行为,它可能会很有用。 但是,这在某些情况下会起作用。

I agree with you. Interfaces should act as a contract, so there is no value from having one-to-one interfaces with domain entities. It may be useful if you would like to abstract a certain behaviour. But, this will work in certain cases.

慕烟庭风 2024-07-27 06:52:37

为啥这个就泡汤了?

我发现领域对象有一个接口,正如查理·马丁所说,允许我选择我的实现。

一个基本示例是对象的标识符 (object.Id),这将有所不同,具体取决于您存储该对象的位置,并且创建它的责任可能会或可能不会取决于稍后数据的实现。 在 SQL Server 中,您可能会选择自动编号,但在 Azure 表存储中,您可能会选择 Guid,但您不想因为更改存储数据的位置而更改应用程序的业务逻辑。

我可能会也可能不会选择保留我的域对象,甚至在表示层中使用它 - 这取决于我的应用程序的最佳范围。 但是在公共层中添加一组公共域接口允许我针对它们编写服务并一次又一次地重用它们。

如果我们没有 IAddress,我们会讨论同样的关于地址应该是什么的争论,如果没有 ICreditCard,新程序员就会重写信用卡的东西。

反模式标签是对语言的错误使用,它对于描述复杂多样的任务的解决方案的价值过于简单化。

大多数模式甚至是饱受诟病的 Singleton 都有一席之地,这意味着它不是“反模式”,至少就该术语所暗示的而言。

Why has this one fizzled out?

I find having an interface for a domain object, as Charlie Martin said, allows me to choose my implementation.

A fundamental example is the identifier (object.Id) for an object, this will be different depending on where you store that object and the responsibility for creating it may or may not rest in the implementation of the data later. In SQL Server you might go for an autonumber, but in Azure table storage you might go for a Guid, but you don't want to have to change the business logic of you app because you change where you store your data.

I may or may not choose to have my domain object persisted, or even use it in the presentation layer - it depends on the scope of my app which is best. But adding a set of common domain interfaces in a common layer allows me to write services against them and reuse them again and again.

We would go over the same argument about what should be an address if we hadn't got IAddress, new programmers would be rewriting stuff for credit cards if it weren't for ICreditCard.

The anti-pattern label is a bad use of language, it's an over simplification for describing the worth of solutions to complex and varied tasks.

There is a place for most patterns even the maligned Singleton, which means its not an "anti-pattern", at least as far as the term suggests.

眼眸印温柔 2024-07-27 06:52:36

正如所描述的那样,这是一种不好的做法,但是......

没有具体原因表明您的接口需要与域实体不同; 有时它确实是正确的映射。 但令人怀疑的是,情况总是如此。 令人关注的问题是这些界面是否是真正设计的,或者它们是否只是因为缺乏时间/懒惰而被扔到位。

为了使用您的示例,您描述的 IAccount 接口公开了 Account 对象的 getter 和 setter; 似乎有点奇怪,并且不太可能使用帐户的所有内容都需要设置帐户余额,并且该隐含权限是在该界面级别指定的。 您的系统中是否没有任何地方您只想检查而不设置帐户余额?

It's a bad practice as described, but...

There's no specific reason that your interfaces need to be different than your domain entities; sometimes it really is the right mapping. But it's suspicious that it's always the case. The point of concern there is a question of whether or not the interfaces were truly designed, or whether they were just thrown into place out of lack of time / laziness.

To use your example, the IAccount interface you describe exposes getters and setters on the Account object; it seems a little odd and unlikely that everything that uses an Account will have a need to set the balance on the account, and that that implied permission is specified at that level of interface. Is there no place in your system where you want to merely check but not set the Account balance?

弥繁 2024-07-27 06:52:36

始终将域对象指定为接口而不是直接指定为类的最大原因是为您提供一定程度的实现自由度。 在您的示例中,您只有一种 IAccount,因此它有点多余。

但如果你有,例如:

public class Account : IAccount { ... }       // Usual account, persistent
public class MockAccount : IAccount { ... }   // Test mock object
public class TransAccount : IAccount { ... }  // Account, not persistent
public class SimAccount : IAccount { ... }    // Account in a performance sim

等等呢?

通过将域对象定义为接口,您可以在不影响域定义的情况下替换实现。

The biggest reason for always specifying the domain objects as interfaces instead of directly as classes is to give you a degree of freedom on the implementation. In your example you only have one kind of IAccount, so it's a little redunant.

But what if you had, for example:

public class Account : IAccount { ... }       // Usual account, persistent
public class MockAccount : IAccount { ... }   // Test mock object
public class TransAccount : IAccount { ... }  // Account, not persistent
public class SimAccount : IAccount { ... }    // Account in a performance sim

and so on?

By defining the domain objects as interfaces, you can replace the implementations without disturbing your domain definition.

回眸一遍 2024-07-27 06:52:36

一般来说,如果我的类不会成为策略或访问者等设计模式的一部分,我不会添加接口。

添加接口对于“策略”和“访问者”等设计模式确实很有用,但在这些情况下,我不会复制域类的 getter 和 setter。 相反,我创建特定于我创建的设计模式界面的界面。

interface SomeStrategy {
   void doSomething(StrategyData data);
}

interface StrategyData {
   String getProperty1();

   String getProperty2();
} 

这允许我让域类实现这些接口,或者使用适配器模式。 我发现这是一种更简洁的方法,只是为了它而创建接口。

设计应该始终减少不确定性。 为了它而创建接口并不能减少不确定性,事实上它可能会增加混乱,因为它没有任何意义。

In general, if my classes are not going to be part of a design pattern like Strategy or Visitor I don't add interfaces.

Adding interfaces is really useful for design patterns like Strategy and Visitor, but in those cases I don't carbon copy the getters and setters of the domain classes. Instead, I create interfaces that are specific for the design pattern interfaces I create.

interface SomeStrategy {
   void doSomething(StrategyData data);
}

interface StrategyData {
   String getProperty1();

   String getProperty2();
} 

That allows me to let the domain classes implement those interfaces, or to use the Adaptor pattern. I find this is a much cleaner approach that just creating interfaces for the sake of it.

Design should always reduce uncertainty. Creating interfaces for the sake of it doesn't reduce uncertainty, in fact it probably increases confusion since it doesn't make any sense.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文