Immutable Class Construction Design
So we all realize the benefits of immutable types, particularly in multithreaded scenarios. (Or at least we should all realize that; see e.g. System.String.)
However, what I haven't seen is much discussion for creating immutable instances, specifically design guidelines.
For example, supp开发者_如何学运维ose we want to have the following immutable class:
class ParagraphStyle {
public TextAlignment Alignment {get;}
public float FirstLineHeadIndent {get;}
// ...
}
The most common approach I've seen is to have mutable/immutable "pairs" of types, e.g. the mutable List<T> and immutable ReadOnlyCollection<T> types or the mutable StringBuilder and immutable String types.
To mimic this existing pattern would require the introduction of some type of "mutable" ParagraphStyle
type which "duplicates" the members (to provide setters), and then provide a ParagraphStyle
constructor which accepts the mutable type as an argument
// 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 = ...,
});
So, this works, supports code completion within the IDE, and makes things reasonably obvious about how to construct things...but it does seem fairly duplicative.
Is there a better way?
For example, C# anonymous types are immutable, AND allow using "normal" property setters for initialization:
var anonymousTypeInstance = new {
Foo = "string",
Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error
Unfortunately, the closest way to duplicate these semantics in C# is to use constructor parameters:
// Option 2:
class ParagraphStyle {
public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
/* ... */ ) {...}
}
But this doesn't "scale" well; if your type has e.g. 15 properties, a constructor with 15 parameters is anything but friendly, and providing "useful" overloads for all 15 properties is a recipe for a nightmare. I'm rejecting this outright.
If we try to mimic anonymous types, it seems that we could use "set-once" properties in the "immutable" type, and thus drop the "mutable" variant:
// Option 3:
class ParagraphStyle {
bool alignmentSet;
TextAlignment alignment;
public TextAlignment Alignment {
get {return alignment;}
set {
if (alignmentSet) throw new InvalidOperationException ();
alignment = value;
alignmentSet = true;
}
}
// ...
}
The problem with this is that it it's not obvious that properties can be set only once (the compiler certainly won't complain), and initialization isn't thread-safe. It thus becomes tempting to add a Commit()
method so that the object can know that the developer is done setting the properties (thus causing all properties that haven't previously been set to throw if their setter is invoked), but this seems to be a way to make things worse, not better.
Is there a better design than the mutable/immutable class split? Or am I doomed to deal with member duplication?
In a couple projects I was using fluent approach. I.e. most generic properties (e.g. name, location, title) are defined via ctor while others are updated with Set methods returning new immutable instance.
class ParagraphStyle {
public TextAlignment Alignment {get; private set;}
public float FirstLineHeadIndent {get; private set;}
// ...
public ParagraphStyle WithAlignment(TextAlignment ta) {
var newStyle = (ParagraphStyle)MemberwiseClone();
newStyle.TextAlignment = ta;
}
// ...
}
MemberwiseClone is ok here as soon our class is truly deeply immutable.
精彩评论