我有违反规则的习惯,如果它使我的代码更简洁或我的API更方便使用,并且如果我能在特定情况下巧妙地做到这一点。我想知道我是否可以逃脱以下这种侵权行为。
下图包含单个Child
和单个Parent
课程。事实上,我正在描述我的应用程序中的常见情况。我对这个问题的决定会影响很多课程,这就是我提出这个问题的原因。
public sealed class Child : INotifyPropertyChanged
{
private Child()
{
//initialize basic state
}
public Child( Parent parent )
: this() //invoke parameterless contructor to initialize basic state
{
this.Parent = parent; //the last thing before the public ctor exits
}
public Parent Parent
{
get { return parent; }
set
{
if ( value != parent ) {
parent = value;
// initialize state that depends on the presence of a Parent instance
parent.Children.Add( this ); //hand off to parent class
OnPropertyChanged( "Parent" );
}
}
}
// INotifyPropertyChanged impl...
}
请注意,在Parent
属性的setter中,我将实例移交给Parent
类。另请注意,我正在从公共构造函数中调用该setter。
在一个earlier question I asked上,发现一个未完全初始化的实例是一个反模式。可能有人认为这就是这里发生的事情。但我会说并非如此,因为基本状态是在无参数构造函数中初始化的,该构造函数在接受Parent
参数之前调用。
我想到了Child
子类的构造函数使用Parent
参数调用基础构造函数时出现的问题场景。如果派生的构造函数初始化它自己的基本状态,那么它将无法及时准备好切换。但我想我可以用sealed
关键字来解决这个问题,如果它符合类的语义,就像我Child
类的情况一样。
在这些具体情况下,我是否愿意打破引用的反模式?还是有一些我错过的考虑因素?
答案 0 :(得分:3)
你是对的,leaking this
during the constructor绝对是你应该尽可能避免的事情。
在这种情况下,它的可能很好(因为你已经将类标记为sealed
),但为什么做一些看起来像反模式的东西,当你可以做得更好的方式代替?
我对你的代码的另一个挑剔是,调用者可能不会期望创建一个孩子的行为将该孩子添加到父母的孩子,所以在这种情况下我可能会改变你的模式使用,以便调用者代码看起来像这样
var child = new Child();
parent.Children.Add(child);
添加时,Children
属性会在子项上设置Parent
。
或者,如果您真的想要保持上述逻辑流程,那么我可能会做这样的事情。
public sealed class Child : INotifyPropertyChanged
{
private Child() { }
public static Child CreateAndAddChild(Parent parent)
{
var child = new Child();
child.Parent = parent;
}
public Parent Parent
{
get { return parent; }
set
{
parent = value;
parent.Children.Add( this);
OnPropertyChanged( "Parent" );
}
}
}
这两个解决方案都完全避免在构造函数中泄漏this
。
答案 1 :(得分:1)
在这些具体情况下,我是否愿意打破引用的反模式?
我认为你很好。
我自己会这样做(已经完成):特别是如果一个Child必须总是有一个Parent,那么将它分配给Parent在逻辑上是构建Child的一部分。
还是有一些我错过的考虑因素?
一个区别是,如果set属性抛出异常,则不构造该对象。如果孩子是一次性的,这会产生影响。
using (Child child = new Child())
{
Child.Parent = null; // throws an exception
... etc ...
} // child.Dispose is invoked
与
using (Child child = new Child(null)) // throws an exception
{
... etc ...
} // child.Dispose is not invoked
答案 2 :(得分:0)
这会让您图书馆的消费者感到困惑。