0

我正在尝试遵循一些更流行的设计原则,包括 SOLID 和领域驱动设计。我的问题是关于人们如何处理“初始化”域对象。

这是一个简单的例子:

基于SOLID,我不应该依赖混凝土,所以我创建了一个接口和一个类。由于我正在利用领域驱动设计,因此我使用相关方法创建了一个对象。(即不贫血)。

Interface IBookstoreBook
{
   string Isbn {get; set;} 
   int Inventory {get; set;}
   void AddToInventory(int numBooks);
   void RemoveFromInventory(int numBooks);
}

public class BookstoreBook : IBookstoreBook
{
   public string Isbn {get; set;} 
   public int Inventory {get; private set;}
   public void AddToInventory(int numBooks);
   public void RemoveFromInventory(int numBooks);       
}

为了帮助测试和更松耦合,我还使用 IoC 容器来创建本书。所以当这本书被创建时,它总是被创建为空的。但是,如果一本书没有 ISBN 和 Inventory,则它是无效的。

BookstoreBook(string bookISBN, int bookInventory) {..} // Does not exist

我可以有 4 或 5 个使用 BookstoreBook 的不同类。一方面,

public class Bookstore : IBookstore
{
   ...
   public bool NeedToIncreaseInventory(BookstoreBook book) { ...}
   ...
}

任何方法如何知道得到一本有效的书?我下面的解决方案似乎违反了“告诉不要问”的原则。

a) 每种使用书店书籍的方法都应该测试有效性吗?(即需要NeedToIncreaseInventory 测试书籍的有效性?我不确定它应该知道什么使BookstoreBook 有效。)

b)我是否应该在 IBookstoreBook 对象上有一个“CreateBook”,并且只是“假设”客户知道他们必须在他们想要初始化 BookstoreBook 的任何时候调用它?这样,NeedToIncreaseInventory 就会相信 BookstoreBook 上已经调用了“CreateBook”。

我对这里推荐的 appreach 感兴趣。

4

4 回答 4

2

首先,我认为您BookstoreBook没有任何真正相关的方法,这意味着它没有任何相关的行为,根本没有业务规则。而且由于它不包含任何业务规则,它实际上是乏善可陈的。它只有一堆 Getter 和 Setter。我会争辩说,拥有这样的方法AddToInventory最终只是向属性添加 +1 是没有意义的行为。

另外,你为什么会BookstoreBook知道它的类型有Bookstore多少?我觉得这可能是它Bookstore本身应该跟踪的东西。

至于 a) 点:不,如果您从用户输入创建书籍,您甚至应该在创建新书之前检查提供的数据。这可以防止您的系统中出现无效书籍。

至于对象的创建,问题是您是否会拥有不止一种书籍类型?如果答案是否定的,您可以删除接口并在一个类中实例化一本书,该类负责从用户输入创建新书。如果您需要更多书籍类型,抽象工厂可能会很有用。

于 2017-02-11T23:22:22.350 回答
1

首先,这是一种确保实体状态只能通过行为(方法)设置的好方法,从而使所有属性设置器都是私有的。它还允许您确保在状态更改时设置所有相关属性。

但是,如果一本书没有 ISBN 和 Inventory,则它是无效的。

你有两个业务规则。让我们从 ISBN 开始。如果一本书没有它是无效的,它必须在构造函数中指定。否则完全有可能创建无效的书。ISBN 还具有指定的格式(至少是长度)。所以这种格式也必须得到验证。

关于库存,我认为这不是真的。您可能有已售罄的书籍或可以在发行前预订的书籍。正确的?所以一本书可以在没有库存的情况下存在,只是不太可能。如果您从领域的角度来看库存和书籍之间的关系,它们是具有不同职责的两个独立实体。

一本书代表了用户可以阅读并使用该信息来决定是否应该租用或购买的东西。

清单用于确保您的应用程序可以满足客户的要求。通常可以通过直接交付(减少库存)或延期交货(从供应商处订购更多副本,然后交付书籍)来完成。

因此,应用程序的库存部分实际上并不需要了解有关这本书的所有信息。因此,我建议库存只知道书的身份(根据 Martin Fowler 的书,这通常是根聚合可以相互引用的方式)。

控制反转容器通常用于管理服务(在 DDD 中是应用程序服务和域服务)。它的工作不是充当域实体的工厂。它只会使事情复杂化而没有任何好处。

于 2017-02-12T11:04:02.767 回答
0

基于 SOLID,我不应该依赖混凝土

如果您指的是依赖倒置原则,它并没有完全这么说。

- 高级模块不应依赖于低级模块。两者都应该依赖于抽象。

- 抽象不应依赖于细节。细节应该取决于抽象。

没有域实体比另一个更高级别,并且域层中的任何对象通常都不是“细节”,因此DIP 通常不适用于域实体

我还使用 IoC 容器来创建这本书

考虑到BookstoreBook它没有依赖性,我不确定你为什么要这样做。

任何方法如何知道得到一本有效的书?

通过假设这本书总是有效的,总是一致的。这通常需要一个在创建时检查所有相关规则的 Book 构造函数,以及强制执行关于 Book 的不变量的状态更改方法。

一个) ...

b) ...

您在这里混淆了两个问题 - 确保Book无论在何处使用它都处于一致状态,并初始化Book. 我不确定您的问题到底是什么,但是如果您应用“始终有效”的方法并忘记 Book 是一个接口/更高级别的抽象,那么您应该很高兴。

于 2017-02-15T12:33:44.900 回答
0

为了帮助测试和更松耦合,我还使用 IoC 容器来创建本书。

为什么您的 IoC 容器要创建书籍?这有点奇怪。您的域模型应该与容器无关(将接口和实现连接在一起是您的组合根的关注点)。

任何方法如何知道得到一本有效的书?

领域模型知道它正在获得一本有效的书,因为它就在界面中这样说。

数据模型知道它正在生成一本有效的书,因为构造函数/工厂方法接受了它的参数而没有抛出异常。

每种使用书店书籍的方法都应该测试有效性吗?

不,一旦您拥有一本书,它将保持有效(您的域模型中不应定义任何会创建无效数据模型的动词)。

我是否应该在 IBookstoreBook 对象上有一个“CreateBook”,并且只是“假设”客户知道他们必须在他们想要初始化 BookstoreBook 的任何时候调用它?这样,NeedToIncreaseInventory 就会相信 BookstoreBook 上已经调用了“CreateBook”。

有一个创建对象的工厂是很正常的。见埃文斯,第 6 章。

书籍可以从数据库和许多其他地方创建。我假设其他人在使用 DDD 时必须解决这个问题,我想知道他们的方法。我们都应该使用工厂吗 - 正如您建议的那样,将所需的数据作为输入?

实际上只有两个数据源——您自己的记录簿(在这种情况下,您通过存储库加载数据),以及其他任何地方(您需要确保数据符合模型的假设)。

于 2017-02-12T03:10:50.057 回答