Così tutti ci rendiamo conto dei vantaggi dei tipi immutabili, in particolare negli scenari multithread. (O almeno dovremmo tutti rendercene conto; vedere ad esempio System.String.)Design della struttura a classe immutabile
Tuttavia, quello che non ho visto è molto discussione per creazione di istanze immutabili, in particolare le linee guida di progettazione.
Per esempio, supponiamo di voler avere la seguente classe immutabile:
class ParagraphStyle {
public TextAlignment Alignment {get;}
public float FirstLineHeadIndent {get;}
// ...
}
L'approccio più comune che ho visto è quello di avere mutevole/immutabili "coppie" di tipi, per esempio il mutabile List<T> e immutable ReadOnlyCollection<T> tipi o il mutabile StringBuilder e immutable String tipi.
di imitare questo modello esistente richiederebbe l'introduzione di un certo tipo di "mutabile" ParagraphStyle
tipo che "duplicati" i membri (a fornire incastonatori), e quindi fornire un costruttore ParagraphStyle
che accetta il tipo mutabile come argomento
// 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 = ...,
});
Quindi, questo funziona, supporta il completamento del codice all'interno dell'IDE e rende le cose ragionevolmente ovvie su come costruire le cose ... ma sembra piuttosto duplicato.
C'è un modo migliore?
Ad esempio, C# tipi anonimi sono immutabili, e permettono l'utilizzo di "normali" setter di proprietà per l'inizializzazione:
var anonymousTypeInstance = new {
Foo = "string",
Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error
Purtroppo, la strada più vicina per duplicare questa semantica in C# è quello di utilizzare parametri del costruttore:
// Option 2:
class ParagraphStyle {
public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
/* ... */) {...}
}
Ma questo non "scala" bene; se il tuo tipo ha, ad es. 15 proprietà, un costruttore con 15 parametri è tutt'altro che amichevole e fornire sovraccarichi "utili" per tutte e 15 le proprietà è una ricetta per un incubo. Sto respingendo questo a titolo definitivo.
Se cerchiamo di imitare i tipi anonimi, sembra che potremmo usare "set-una volta" le proprietà nel tipo "immutabili", e quindi rilasciare la variante "mutabile":
// Option 3:
class ParagraphStyle {
bool alignmentSet;
TextAlignment alignment;
public TextAlignment Alignment {
get {return alignment;}
set {
if (alignmentSet) throw new InvalidOperationException();
alignment = value;
alignmentSet = true;
}
}
// ...
}
Il problema con questo è che non è ovvio che le proprietà possano essere impostate una sola volta (il compilatore certamente non si lamenterà) e l'inizializzazione non è thread-safe. Diventa quindi tentato di aggiungere un metodo Commit()
in modo che l'oggetto possa sapere che lo sviluppatore ha finito di impostare le proprietà (causando così tutte le proprietà che non sono state precedentemente impostate per il lancio se il loro setter è invocato), ma questo sembra essere un modo per peggiorare le cose, non meglio.
Esiste un design migliore rispetto alla divisione di classe mutevole/immutabile? O sono condannato a gestire la duplicazione dei membri?
Mi rendo conto che potrebbe essere difficile da credere, ma sto lavorando con una base di codice C# esistente. Portare l'intera cosa a F # non è un'opzione, e anche se fosse un'opzione la licenza MSR-SSLA che le librerie F # sono rilasciate sotto non sembra appropriato per il mio uso, in particolare (ii) (c). – jonp
Questo sembra un duplicato di: http://stackoverflow.com/questions/263585/immutable-object-pattern-in-c-what-do-you-think – jonp
Potrebbe non essere una risposta, ma ho affrontato requisiti simili in passato implementando ISupportInitialize. Dopo una chiamata a EndInit() diventi immutabile e lancia quando vengono chiamati i setter. Non è sicuro in fase di compilazione, ma la documentazione e il lancio precoce aiutano spesso. – Will