Unveränderliches Objektmuster in C # - was denkst du?
Habe ich im Laufe von ein paar Projekten entwickelt ein Muster für die Erstellung unveränderlich (readonly) - Objekte und unveränderliche Objekt-Graphen. Unveränderliche Objekte tragen den Vorteil, dass Sie 100% thread-safe und kann daher wiederverwendet werden mehrere threads. In meiner Arbeit habe ich sehr oft verwenden diese Muster in Web-Anwendungen für Konfiguration, Einstellungen und andere Objekte, die ich laden und cache im Arbeitsspeicher. Zwischengespeicherte Objekte sollten immer unveränderlich sind, wie Sie gewährleisten wollen, sind Sie nicht unerwartet verändert.
Nun, Sie können natürlich leicht design immutable Objekte, wie im folgenden Beispiel:
public class SampleElement
{
private Guid id;
private string name;
public SampleElement(Guid id, string name)
{
this.id = id;
this.name = name;
}
public Guid Id
{
get { return id; }
}
public string Name
{
get { return name; }
}
}
Dies ist in Ordnung für einfache Klassen - aber für komplexere Klassen ich Bilde mir nicht ein das Konzept, indem alle Werte durch einen Konstruktor. Mit Set-Methoden auf die Eigenschaften ist wünschenswert und Ihr code wird mit dem Bau eines neuen Objekts wird leichter zu Lesen.
Wie erstellen Sie also unveränderliche Objekte mit setter?
Gut, in meiner pattern-Objekte von Anfang an so vollständig verändert, bis Sie frieren Sie mit einem einzigen Methodenaufruf. Sobald ein Objekt fixiert wird, dann bleibt unveränderlich für immer - es kann nicht sein, verwandelte sich in ein veränderliches Objekt wieder. Wenn Sie eine veränderliche version des Objekts, die Sie einfach zu Klonen.
Ok, nun zum code. Ich habe im folgenden code-Schnipsel versucht zu Kochen, das Muster nach unten auf seine einfachste form. Die IElement ist die Basis-Schnittstelle, die alle unveränderlichen Objekte müssen schließlich umsetzen.
public interface IElement : ICloneable
{
bool IsReadOnly { get; }
void MakeReadOnly();
}
Die Element-Klasse ist die default-Implementierung des interface IElement:
public abstract class Element : IElement
{
private bool immutable;
public bool IsReadOnly
{
get { return immutable; }
}
public virtual void MakeReadOnly()
{
immutable = true;
}
protected virtual void FailIfImmutable()
{
if (immutable) throw new ImmutableElementException(this);
}
...
}
Let ' s umgestalten der SampleElement Klasse zu implementieren, das immutable-Objekt-Muster:
public class SampleElement : Element
{
private Guid id;
private string name;
public SampleElement() {}
public Guid Id
{
get
{
return id;
}
set
{
FailIfImmutable();
id = value;
}
}
public string Name
{
get
{
return name;
}
set
{
FailIfImmutable();
name = value;
}
}
}
Können Sie nun ändern Sie die Id-Eigenschaft und die Name-Eigenschaft, solange das Objekt noch nicht markiert wurden, als unveränderliche, durch aufrufen der MakeReadOnly () - Methode. Einmal ist es unveränderlich, Aufruf einer setter-Ausbeute wird eine ImmutableElementException.
Letzte Anmerkung:
Das vollständige Muster ist komplexer als der code-Schnipsel, die hier gezeigt werden. Es enthält auch Unterstützung für Sammlungen von immutable Objekte und kompletter Objekt-Graphen unveränderliche Objekt-Graphen. Der auf das komplette Suchmuster können Sie ein ganzes Objekt graph unveränderlich durch aufrufen der MakeReadOnly () - Methode für das äußere object. Sobald Sie beginnen, die Erstellung von größeren Objekt-Modelle mit diesem Muster das Risiko von undichten Objekte erhöht. Ein leaky-Objekt ist ein Objekt, das nicht rufen Sie die FailIfImmutable () - Methode auf, bevor eine änderung am Objekt. Zum testen auf Undichtigkeiten habe ich auch entwickelt eine generische Leck-Detektor-Klasse für die Verwendung in unit-tests. Es verwendet reflektion, um zu testen, ob alle Eigenschaften und Methoden werfen die ImmutableElementException in der unveränderlichen Zustand.
In anderen Worten TDD verwendet wird, hier.
Ich habe zugenommen, wie dieses Muster eine Menge und finden Sie tolle Vorteile. Also was ich gerne wissen würde ist, ob jemand von Euch mit ähnlichen mustern? Wenn ja, kennen Sie eine gute Ressourcen, die es zu dokumentieren? Ich bin eigentlich auf der Suche nach möglichen Verbesserungen und für alle Normen, die möglicherweise existieren bereits zu diesem Thema.
InformationsquelleAutor der Frage Lars Fastrup | 2008-11-04
Du musst angemeldet sein, um einen Kommentar abzugeben.
Zur info, der zweite Ansatz wird als "Eis am Stiel Unveränderlichkeit".
Eric Lippert hat eine Reihe von blog-Einträgen auf Unveränderlichkeit ab hier. Ich bin noch immer in den Griff mit der CTP (C# 4.0), aber es sieht interessant aus, was optional /named Parameter (zu den .ctor) hier machst (wenn zugeordnet readonly-Felder)...
[update: ich habe gebloggt auf dieser hier]
Für die info, hätte ich wahrscheinlich nicht machen, diese Methoden in
virtual
- wir möchten wahrscheinlich nicht, dass Unterklassen zu können, machen Sie es nicht-freezable. Wenn Sie wollen, dass Sie in der Lage sein, um hinzuzufügen extra-code, ich würde etwas vorschlagen wie:Auch - AOP (wie PostSharp) könnte eine praktikable option für das hinzufügen all diese ThrowIfFrozen() überprüft.
(entschuldigt, wenn ich mich geändert habe-Terminologie /Methode Namen nicht ALSO halten Sie die original-post sichtbar, das beim schreiben von Antworten)
InformationsquelleAutor der Antwort Marc Gravell
Andere option wäre, eine Art Builder-Klasse.
Für ein Beispiel, in Java (und C# und viele andere Sprachen) String ist unveränderlich. Wenn Sie wollen, um mehrere Operationen zu erstellen, die eine Zeichenfolge, die Sie verwenden einen StringBuilder. Dies ist änderbar, und dann, sobald Sie fertig sind, haben Sie es zurück, um Sie das Letzte String-Objekt. Ab dann ist es unveränderlich.
Könnten Sie tun, etwas ähnliches für die anderen Klassen. Sie haben Ihr unveränderlich Element, und dann ein ElementBuilder. Alle builder tun würde, ist das speichern der Optionen, die Sie setzen, dann, wenn Sie es beenden es Konstrukte und gibt das unveränderliche Element.
Es ist ein wenig mehr code, aber ich denke, es ist sauberer als mit setter einer Klasse, die angeblich unveränderlich.
InformationsquelleAutor der Antwort Herms
Nachdem meine anfängliche Unbehagen über die Tatsache, dass ich hatte, um eine neue zu erstellen
System.Drawing.Point
auf jede änderung, ich habe ganz umarmte das Konzept vor einigen Jahren. In der Tat, mache ich nun jedes Feld alsreadonly
standardmäßig nur ändern, dass es wandelbar ist, wenn es einen zwingenden Grund – die gibt es erstaunlich selten.Ich nicht viel über cross-threading Probleme, obwohl (ich nur selten verwenden Sie den code, wo dies relevant ist). Ich finde es einfach viel, viel besser, weil der semantische Ausdruckskraft. Unveränderlichkeit ist der Inbegriff einer Schnittstelle, die schwer zu bedienen ist falsch.
InformationsquelleAutor der Antwort Konrad Rudolph
Sind Sie immer noch im Umgang mit Staat, und somit kann immer noch gebissen werden, wenn Sie Ihre Objekte sind parallelisiert, bevor Sie unveränderlich.
Einen mehr funktionale Weg sein könnte, um wieder eine neue Instanz des Objekts mit jedem setter. Oder erstellen Sie ein veränderliches Objekt und übergeben, die in dem Konstruktor.
InformationsquelleAutor der Antwort Cory Foy
Die (relativ) neue Software-Design-Paradigma genannt, Domain Driven design, macht die Unterscheidung zwischen entity-Objekte und Wert-Objekte.
Entity-Objekte sind definiert als alles, was für die Zuordnung zu einer der Schlüssel-gesteuerten Objekts in einen persistenten datenspeicher, wie ein Mitarbeiter, oder ein client, oder eine Rechnung, etc..., wo das ändern der Eigenschaften des Objekts bedeutet, dass Sie brauchen, um die änderung zu speichern, um eine Daten irgendwo speichern, und die Existenz von mehreren Instanzen einer Klasse mit dem gleichen "key" imnplies müssen, um ihn zu synchronisieren oder koordinieren Ihre Beharrlichkeit, um die Daten zu speichern, so dass eine Instanz die änderungen nicht überschreiben die andere. Ändern der Eigenschaften einer Entität, Objekt bedeutet, Sie ändern etwas über die Objekt - nicht ändern, DAS Objekt, das Sie referenzieren...
Value-Objekte otoh, sind Objekte, die berücksichtigt werden können, unveränderlich sind, deren Dienstprogramm ist genau definiert durch die Eigenschaft-Werte, und für die mehrere Instanzen, müssen nicht koordiniert werden, in irgendeiner Art und Weise... wie Adressen oder Telefonnummern, oder die Räder eines Autos, oder die Briefe in ein Dokument... diese Dinge sind vollständig definiert durch Ihre Eigenschaften... einem Großbuchstaben 'A' - Objekt in einem text-editor ausgetauscht werden können transparent mit anderen Großbuchstaben 'A' - Objekt in dem Dokument, Sie brauchen nicht einen Schlüssel zur Unterscheidung von allen anderen 'A' In diesem Sinne ist es unveränderlich, denn wenn Sie es ändern, um ein " B " (wie das ändern der Telefonnummer string in einen Telefonnummer-Objekt, ändern Sie nicht die Daten, die im Zusammenhang mit einigen veränderliche Entität, Sie wechseln von einem Wert zum anderen... genauso, wenn Sie ändern Sie den Wert eines string -...
InformationsquelleAutor der Antwort Charles Bretana
System.String ist ein gutes Beispiel für eine immutable Klasse mit setter und mutiert Methoden, nur, dass jeder mutierend Methode liefert eine neue Instanz.
InformationsquelleAutor der Antwort dalle
Aufbauend auf den Punkt von @Cory Foy und @Charles Bretana, wo es einen Unterschied zwischen Entitäten und Werten. In der Erwägung, dass Wert-Objekte müssen immer unveränderlich sind, ich glaube nicht wirklich, dass ein Objekt sollte in der Lage sein, zu frieren, sich selbst, oder lassen sich eingefroren werden willkürlich in der codebase. Es hat einen wirklich schlechten Geruch zu ihm, und ich fürchte, es könnte schwer aufzuspüren, wo genau ein Objekt gefroren war, und warum war es gefroren, und die Tatsache, dass zwischen den anrufen auf ein Objekt, könnte es den Status ändern von aufgetaut, eingefroren.
Das ist nicht zu sagen, dass manchmal Sie wollen eine (veränderliche) Entität zu etwas, und sicherzustellen, dass Sie nicht gehen, um geändert werden.
So, statt einfrieren die Sache selbst, eine andere Möglichkeit ist das kopieren der Semantik von ReadOnlyCollection< T >
Ihrem Objekt kann ein Teil als veränderlich, wenn es Sie braucht, und dann unveränderlich, wenn Sie es wünschen zu sein.
Beachten Sie, dass ReadOnlyCollection< T > implementiert auch ICollection< T > was hat ein
Add( T item)
Methode in der Schnittstelle. Es gibt jedoch auchbool IsReadOnly { get; }
in der Schnittstelle definiert, so dass die Verbraucher überprüfen können, bevor Sie eine Methode aufrufen, die eine Ausnahme werfen.Der Unterschied ist, dass Sie können nicht nur festlegen, IsReadOnly false. Eine Sammlung entweder sein oder nicht, Lesen Sie nur, und das ändert sich nie für die Lebensdauer der Kollektion.
Schön wäre es an Zeit, um die const-correctness, C++ haben Sie zur compile-Zeit, aber das beginnt zu haben eigene Probleme und ich bin froh, dass C# nicht gibt.
ICloneable - ich dachte, ich würde nur zurück zu verweisen, die folgenden:
Brad Abrams - Design-Richtlinien, die Verwalteten code und die .NET Framework
InformationsquelleAutor der Antwort Robert Paulson
Dies ist ein wichtiges problem, und ich habe die Liebe zu sehen, mehr direkte framework/language-support, um es zu lösen. Die Lösung, die Sie haben, erfordert eine Menge boilerplate. Es könnte einfach sein, zu automatisieren einige der vorformulierten durch die Verwendung von code-Generierung.
Würden Sie eine partielle Klasse generiert, die enthält alle die freezable-Eigenschaften. Es wäre relativ einfach, eine wiederverwendbare T4-Vorlage für dieses.
Vorlage nehmen würde, das für die Eingabe:
Und würde die Ausgabe einer C# - Datei, enthält:
AOP-tags auf freezable-Eigenschaften könnte auch funktionieren, aber es würde mehr Abhängigkeiten, in der Erwägung, dass T4 ist in neueren Versionen von Visual Studio.
Einem anderen Szenario ist sehr ähnlich wie diese ist die
INotifyPropertyChanged
- Schnittstelle. Lösungen für dieses problem sind wahrscheinlich zutreffend für dieses problem.InformationsquelleAutor der Antwort Merlyn Morgan-Graham
Mein problem mit diesem Muster ist, dass Sie nicht die Einführung einer compile-Zeit-Beschränkungen auf Unveränderlichkeit. Der Programmierer ist dafür verantwortlich, dass ein Objekt festgelegt ist, unveränderlich, bevor zum Beispiel hinzufügen, um einen cache oder einen anderen non-thread-safe-Struktur.
Deshalb würde ich verlängern diese Codierung Muster mit einer compile-Zeit Zurückhaltung in der form einer generischen Klasse, wie folgt:
Hier ist ein Beispiel, wie würden Sie diesen:
InformationsquelleAutor der Antwort Jos Bosmans
Nur ein Tipp zur Vereinfachung der element-Eigenschaften: Verwenden Sie automatische Eigenschaften mit
private set
und vermeiden Sie explizite deklarieren Sie das Datenfeld. z.B.InformationsquelleAutor der Antwort spoulson
Hier ist ein neues video auf Channel 9, wo Anders Hejlsberg von 36:30 in dem interview reden beginnt Unveränderlichkeit in C#. Er gibt eine sehr gute Verwendung für Eis am Stiel Unveränderlichkeit und erklärt, wie dies ist etwas, das Sie sind derzeit erforderlich, um selber implementieren. Es war Musik in meinen Ohren hören ihn sagen, es ist Wert, darüber nachzudenken, bessere Unterstützung für die Erstellung von immutable Objekt-Graphen in zukünftige Versionen von C#
Experte zu Experte: Anders Hejlsberg - Die Zukunft von C#
InformationsquelleAutor der Antwort Lars Fastrup
Zwei weitere Optionen für Ihr spezielles problem noch nicht besprochen wurde:
Bauen Sie Ihre eigenen deserializer ein, dass ein privates Eigentum setter. Während der Aufwand in den Aufbau der deserializer am Anfang wird viel mehr, es macht die Sache sauberer. Der compiler halten Sie von der auch nur den Versuch zu nennen, die setter und den code in Ihre Klassen leichter zu Lesen.
Setzen Sie einen Konstruktor in jeder Klasse nimmt ein XElement (oder einige andere Geschmack der XML-Objekt-Modell) und füllt sich aus. Offensichtlich, wie die Anzahl der Klassen erhöht, das wird schnell weniger wünschenswert als eine Lösung.
InformationsquelleAutor der Antwort Neil
Wie wäre es mit einer abstrakten Klasse ThingBase, mit Unterklassen MutableThing und ImmutableThing? ThingBase enthält alle Daten in einer geschützten Struktur, die Bereitstellung von öffentlichen nur-lese-Eigenschaften für die Felder und geschützten nur-lese-Eigenschaft, die für seine Struktur. Es würde auch eine überschreibbare AsImmutable Methode, die zurückkehren würde, ein ImmutableThing.
MutableThing würden Schatten die Eigenschaften mit lese - /schreib-Eigenschaften, und bieten sowohl einen Standard-Konstruktor und einen Konstruktor akzeptiert eine ThingBase.
Unveränderliche Sache sein würde, eine versiegelte Klasse, überschreibt AsImmutable einfach sich selbst zurückgeben. Es würde auch einen Konstruktor akzeptiert eine ThingBase.
InformationsquelleAutor der Antwort supercat
Ich nicht wie die Idee des seins in der Lage zu ändern ein Objekt aus einem veränderlich zu einem unveränderlichen Zustand, dass die Art der scheint zu besiegen den Punkt des Designs, zu mir. Wenn Sie benötigen zu tun? Nur Objekte, die WERTE darzustellen, sollte unveränderlich
InformationsquelleAutor der Antwort Andrew Bullock
Können Sie optional verwenden benannte Argumente zusammen mit nullables, um ein unveränderliches Set-mit sehr wenig boilerplate. Wenn Sie wirklich wollen, um eine Immobilie zu null, dann haben Sie möglicherweise einige Probleme.
Verwenden Sie es wie so
InformationsquelleAutor der Antwort bradgonesurfing