因此,我们都认识到不可变类型的好处,特别是在多线程场景中。(或者至少我们都应该认识到这一点;请参见例如System.String。)
然而,我还没有看到的是很多讨论
创建
例如,假设我们希望具有以下不可变类:
class ParagraphStyle {
public TextAlignment Alignment {get;}
public float FirstLineHeadIndent {get;}
// ...
}
我见过的最常见的方法是拥有可变/不可变类型的“对”,例如可变
List<T>
ReadOnlyCollection<T>
类型还是可变的
StringBuilder
String
类型。
要模仿这种现有模式,需要引入某种类型的“可变”
ParagraphStyle
键入“复制”成员的类型(以提供setter),然后提供
段落风格
// Option 1:
class ParagraphStyleCreator {
public TextAlignment {get; set;}
public float FirstLineIndent {get; set;}
// ...
}
class ParagraphStyle {
// ... as before...
public ParagraphStyle (ParagraphStyleCreator value) {...}
}
// Usage:
var paragraphStyle = new ParagraphStyle (new ParagraphStyleCreator {
TextAlignment = ...,
FirstLineIndent = ...,
});
因此,这是可行的,支持IDE中的代码完成,并使如何构造东西变得相当明显……但它看起来确实相当重复。
有更好的办法吗?
例如,C#匿名类型是不可变的,允许使用“普通”属性设置器进行初始化:
var anonymousTypeInstance = new {
Foo = "string",
Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error
不幸的是,在C#中复制这些语义的最接近的方法是使用构造函数参数:
// Option 2:
class ParagraphStyle {
public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
/* ... */ ) {...}
}
但这并不能很好地“扩展”;如果你的类型有15个属性,那么一个有15个参数的构造函数绝对不是友好的,为所有15个属性提供“有用”的重载是一个噩梦。我完全拒绝这个。
// Option 3:
class ParagraphStyle {
bool alignmentSet;
TextAlignment alignment;
public TextAlignment Alignment {
get {return alignment;}
set {
if (alignmentSet) throw new InvalidOperationException ();
alignment = value;
alignmentSet = true;
}
}
// ...
}
Commit()
方法,以便对象可以知道开发人员已完成属性设置(因此,如果调用setter,则会导致以前未设置的所有属性被抛出),但这似乎是一种让事情变得更糟而不是更好的方法。
有比可变/不可变类拆分更好的设计吗?还是我注定要处理会员重复?