使用与域实体的一对一接口是好的还是坏的做法?为什么?
-
05-07-2019 - |
题
我在我工作的一些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;}
}
但最近我越来越相信这实际上是一种反模式 我是由开源社区的一些架构师运行的,他们说这是基于设计错误或缺陷,在设计链的某个地方。
所以我告诉我的同事他们应该退出为Domain对象创建接口。因为它们没有任何意义,所以每当更新域实体时都必须更新接口。
首先声称这些接口提供了“解耦”,但我反驳说,因为接口与域实体具有一对一的关系,它们实际上并没有提供任何解耦,对接口的改变意味着域实体的变化,反之亦然。
下一个主张是我们需要用于测试目的的接口。我的反击是Rhino-mocks提供了具体类的模拟和存根。但是他们声称Rhino-mocks在具体课程方面遇到了麻烦。我不知道我是否会购买它,即使rhino-mocks在具体类中遇到问题,也不一定意味着我们应该为域实体使用接口。
所以我很好奇:
为什么你的域实体有一对一的接口?
为什么不呢?
为什么这是一种好的或坏的做法?
感谢阅读!
编辑:我应该注意到我一直使用接口,我相信如果它被调用,我将使用一个接口。但我特别指的是具有一对一接口的域实体。
解决方案
如上所述,这是一种不好的做法,但是......
没有特别的理由说您的界面需要与您的域实体不同;有时它确实是正确的映射。但它总是如此可疑。值得关注的问题是接口是否真正被设计,或者它们是否因缺乏时间/懒惰而被抛弃。
要使用您的示例,您描述的IAccount接口会在Account对象上公开getter和setter;看起来有点奇怪,并且使用帐户的所有内容都不太可能需要在帐户上设置余额,并且在该级别的界面上指定了隐含权限。您的系统中是否没有您想要仅检查但未设置帐户余额的地方?
其他提示
始终将域对象指定为接口而不是直接指定为类的最大原因是为您提供一定程度的实现自由。在你的例子中,你只有一种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
等等?
通过将域对象定义为接口,您可以在不影响域定义的情况下替换实现。
一般来说,如果我的课程不会成为策略或访客等设计模式的一部分,我就不会添加界面。
添加接口对于策略和访问者等设计模式非常有用,但在这些情况下,我不会复制域类的getter和setter。相反,我创建了特定于我创建的设计模式接口的接口。
interface SomeStrategy {
void doSomething(StrategyData data);
}
interface StrategyData {
String getProperty1();
String getProperty2();
}
这允许我让域类实现这些接口,或者使用适配器模式。我发现这是一个更清洁的方法,只是为了它创建接口。
设计应始终减少不确定性。为了它而创建接口不会减少不确定性,事实上它可能会增加混乱,因为它没有任何意义。
实体上的一对一接口是反模式
詹姆斯格雷戈里把它比我更好这里。
我同意你的看法。接口应该作为契约,因此与域实体的一对一接口没有价值。如果您想抽象出某种行为,这可能会很有用。但是,这在某些情况下会起作用。
为什么这个人失败了呢?
我发现有一个域对象的接口,正如查理·马丁所说,允许我选择我的实现。
一个基本的例子是对象的标识符(object.Id),这将根据您存储该对象的位置而有所不同,创建它的责任可能会或可能不会在稍后的数据实现中停留。在SQL Server中,您可能会使用自动编号,但在Azure表存储中,您可能会使用Guid,但您不必更改应用程序的业务逻辑,因为您更改了存储数据的位置。
我可能会也可能不会选择保留我的域对象,甚至在表示层中使用它 - 这取决于我的应用程序的范围,这是最好的。但是在公共层中添加一组公共域接口允许我针对它们编写服务并一次又一次地重用它们。
如果我们没有IAddress,我们会讨论应该是什么地址的相同论点,如果不是ICreditCard,新程序员会重写信用卡的内容。
反模式标签是一种糟糕的语言使用方式,它过于简化,用于描述复杂多变任务的解决方案的价值。
大多数模式都有一个地方,即使是诽谤的单身人士,这意味着它不是一个“反模式”,至少就这个术语所暗示的那样。