我无法给出一个好的代码答案,因为我不了解域的语义。
- 什么是没有父母的孩子?
- 为什么 addchild 既要添加无父子节点又要移动子节点
现有的孩子
- 孩子和父母都是聚合根
- 我想这不是关于人类父母和孩子的,而是
关于一些特定的领域。使用
在这个例子中可以做到
可能会提供更好的答案。
当我知道所有这些事情后,我建议您从域中删除所有公共设置器,并为所有域操作添加具有描述性名称的方法。用户不操作数据,但他们做事。方法名称应该是您的领域语义,并包含执行该操作的所有业务逻辑。
当父级是子级的聚合根时,我会像这样实现向父级添加一个新子级:
public class Child
{
protected Child() { } // Constructor to please NHiberante's "Poco" implementation
// internal to prevent other assemblies than the domain assembly from constructing childs
internal Child(string somethingElse, Parent parent)
{
SomethingElse = somethingElse;
Parent = parent;
}
// Parent can not be changed by the child itself, because parent is the aggregate root of child.
public Parent Parent { get; private set; }
public string SomethingElse { get; private set; }
}
public class Parent
{
private readonly ISet<Child> children;
public Parent()
{
children = new HashedSet<Child>();
}
public IEnumerable<Child> Children
{
// only provide read access to the collection because manipulating the collection will disturb the domain semantics.
get { return children; }
}
// This method wouldn't be called add child, but ExecuteSomeBusinessLogic in real code
public void AddChild(string somethingElse)
{
// child constructor can only be called here because the parent is the aggregate root.
var child = new Child(somethingElse, parent);
children.Add(child);
}
}
如果您提供更多信息,我可以回答您的其他要求。
这个答案总结在一个单行词中:“对于应用程序中的所有状态操作,您需要一个方法,其名称说明它的作用。”
编辑:对 cmets 的反应。
我发现您的一个规范存在问题:只有父级是聚合根。在这种情况下,您永远不会在没有父母的情况下使用孩子。这使得双向关系毫无用处,因为当您访问它的孩子时,您已经知道父母。双向关系在 DDD 中很难管理,因此最好的解决方案是避免它们。我知道在使用 NHibernate 时无法避免它们的原因有很多。
以下代码具有双向关系,并通过使用内部辅助方法解决了域处于临时无效状态的问题。内部方法只能从域程序集中调用,不对外暴露。我不太喜欢这种方法,但它是我认为最不差的解决方案。
public class Client : AggregateRoot
{
private readonly ISet<Contact> contacts;
public Client()
{
contacts = new HashedSet<Contact>();
}
public IEnumerable<Contact> Contacts
{
get { return contacts; }
}
public void LogCall(string description)
{
var contact = new Contact(description, this);
AddContact(contact);
}
internal void AddContact(Contact contact)
{
contacts.Add(contact);
}
internal void RemoveContact(Contact contact)
{
contacts.Remove(contact);
}
}
public class Contact : AggregateRoot
{
protected Contact { }
public Contact(string description, Client client)
{
if (description == null) throw new ArgumentNullException("description");
if (client == null) throw new ArgumentNullException("client");
Client = client;
Description = description;
}
public Client Client { get; private set; }
public string Description { get;private set; }
// I assumed that moving a contact to another client would only be done by the user to correct mistakes?
// Isn't it an UI problem when the user frequently makes this mistake?
public void CorrectMistake(Client client)
{
Client.RemoveContact(this);
Client = client;
client.AddContact(this);
}
}