我正在构建一种域层,我希望能够实现以下方法:
[注意:根据使用.Equals()进行检查,ISet是一个不允许重复的集合类。]
public class Parent
{
public ISet Children = new HashedSet<Child>;
public void AddChild()
{
...
}
public void RemoveChild()
{
...
}
}
public class Child
{
public Parent Parent
{
get {...} set {...}
}
}
以下测试将通过:
调用parent.AddChild(child)时:
调用parent.RemoveChild(child)时:
设置child.Parent相当于调用parent.AddChild()。 当子级具有现有父级时,设置child.Parent = null等同于调用parent.RemoveChild()。
但我真的似乎无法做到!
这在域层中必须是非常常见的逻辑,例如使用例如NHibernate,所以我想知道其他人是怎么做到的?
答案 0 :(得分:1)
在像这样的孩子身上使用属性和变量
private Parent _parent;
public Parent parent {
get { return _parent; }
set {
if(_parent == value)
return; //Prevents circular assignement
if(null != _parent) {
Parent temp = _parent;
_parent = null;
temp.removeChild(this);
}
if(null != value) {
_parent = value;
value.addChild(this);
}
}
}
这将导致设置childs parent属性将调用父方法,并且所有其他逻辑可以放在这些方法中。
Parent应该检查childs parent属性是否需要更改,但是如果尝试再次设置相同的parent,set方法也会失败。 AR
答案 1 :(得分:1)
我将从ISet派生Parent类并覆盖Add和Remove方法,以完成将子项添加到列表并设置其父项的工作。
只需编写将子项添加到Parent属性的set访问器中的父集合的逻辑。
答案 2 :(得分:1)
可能有更聪明的方法,但最终你只需要确保设置父/子关系的每种方式检查其他方式(即添加子检查以查看父属性是否已设置并设置父属性检查以确保父项包含子项。
// in Parent
public void AddChild(Child c)
{
// need to check if Parent hasn't yet been set to this
if (!c.Parent.Equals(this)) c.Parent = this;
...
}
public void RemoveChild(Child c)
{
// need to check if Parent is still set to this
if (c.Parent.Equals(this)) c.Parent = null;
...
}
public bool Contains(Child c)
{
// assuming ISet implements this function
return Children.Contains(c);
}
// in Child
public Parent Parent
{
...
set
{
Parent old = _Parent;
_Parent = value;
if ((old != null) &&
(old.Contains(this)))
{
old.RemoveChild(this);
}
if ((_Parent != null) &&
(!_Parent.Contains(this)))
{
_Parent.AddChild(this);
}
}
}
我在删除子项之前更改了_Parent的值,因为否则将从Parent.Add / RemoveChild代码中调用Child.Parent设置代码。
答案 3 :(得分:0)
我无法提供良好的代码答案,因为我不理解域的语义。
当我知道所有这些内容时,我建议您从域中删除所有公共设置器,并为所有域操作添加具有描述性名称的方法。用户不操纵数据,但他们会做事。方法名称应该是您的域语义,并包含执行该操作的所有业务逻辑。
当parent是子节点的聚合根时,我会实现向父节点添加一个新子节点,如下所示:
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);
}
}
如果您向我们提供更多信息,我可以回答您的其他要求。
这个答案总结在一个oneliner:“对于你的应用程序中的所有状态操作,你需要一个名称告诉它它做什么的方法。”
编辑:对评论的反应。 我发现你的一个规范有问题:只有父是聚合根。在这种情况下,你绝不会在没有父母的情况下使用孩子。这使双向关系变得毫无用处,因为当你访问它的孩子时你已经知道了父。 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);
}
}
答案 4 :(得分:0)
我会实现自己的ISet,并在那里实现父跟踪。使用常规HashSet来完成存储对象的工作,但在你的类中做一些额外的工作。
public class ChildrenSet : ISet<Child>
{
private HashSet<Child> backing = new HashSet<Child>();
private Parent parent;
internal ChildrenSet(Parent p)
{
this.parent = p;
}
public bool Add(Child item)
{
if(backing.Add(item))
{
item.Parent = this.parent;
return true;
}
return false;
}
public bool Remove(Child item)
{
if(backing.Remove(item))
{
item.Parent = null;
return true;
}
return false;
}
// etc for the rest of ISet. Also GetEnumerator, if desired.
}
但是,这并没有实现你提到的一件事:“如果孩子有一个前一个父母,那么那个父母的孩子集合不再包含孩子”,因为你也说“一些实体将有多个子集合”。
将孩子从一个父母移动到另一个父母时,如果有多个馆藏,则必须单独检查所有这些。由于听起来您正在描述父母的层次结构,因此在尝试添加已有父母的子女时,可能更容易抛出InvalidOperationException
。