Ich entwickle ein Objektmodell mit vielen verschiedenen Eltern / Kind-Klassen. Jedes untergeordnete Objekt hat einen Verweis auf sein übergeordnetes Objekt. Ich kann mir mehrere Möglichkeiten überlegen (und habe sie ausprobiert), um die übergeordnete Referenz zu initialisieren, finde aber bei jedem Ansatz erhebliche Nachteile. In Anbetracht der unten beschriebenen Ansätze, welche am besten ... oder was noch besser ist.
Ich werde nicht sicherstellen, dass der folgende Code kompiliert wird. Bitte versuchen Sie, meine Absicht zu erkennen, wenn der Code syntaktisch nicht korrekt ist.
Beachten Sie, dass einige meiner untergeordneten Klassenkonstruktoren (andere als übergeordnete) Parameter verwenden, obwohl ich nicht immer welche zeige.
Der Anrufer ist dafür verantwortlich, das übergeordnete Element festzulegen und es demselben übergeordneten Element hinzuzufügen.
class Child { public Child(Parent parent) {Parent=parent;} public Parent Parent {get; private set;} } class Parent { // singleton child public Child Child {get; set;} //children private List<Child> _children = new List<Child>(); public List<Child> Children { get {return _children;} } }
Nachteil: Das Festlegen des übergeordneten Elements ist für den Verbraucher ein zweistufiger Prozess.
var child = new Child(parent); parent.Children.Add(child);
Nachteil: fehleranfällig. Der Anrufer kann einem anderen übergeordneten Element ein untergeordnetes Element hinzufügen, das nicht zum Initialisieren des untergeordneten Elements verwendet wurde.
var child = new Child(parent1); parent2.Children.Add(child);
Übergeordnet überprüft, ob der Anrufer dem übergeordneten Element, für das die Initialisierung durchgeführt wurde, ein untergeordnetes Element hinzufügt.
class Child { public Child(Parent parent) {Parent = parent;} public Parent Parent {get; private set;} } class Parent { // singleton child private Child _child; public Child Child { get {return _child;} set { if (value.Parent != this) throw new Exception(); _child=value; } } //children private List<Child> _children = new List<Child>(); public ReadOnlyCollection<Child> Children { get {return _children;} } public void AddChild(Child child) { if (child.Parent != this) throw new Exception(); _children.Add(child); } }
Nachteil: Der Anrufer hat immer noch einen zweistufigen Prozess zum Einstellen des übergeordneten Elements.
Nachteil: Laufzeitprüfung - reduziert die Leistung und fügt jedem Add / Setter Code hinzu.
Das Elternteil legt die Elternreferenz des Kindes (auf sich selbst) fest, wenn das Kind einem Elternteil hinzugefügt / zugewiesen wird. Eltern-Setter ist intern.
class Child { public Parent Parent {get; internal set;} } class Parent { // singleton child private Child _child; public Child Child { get {return _child;} set { value.Parent = this; _child = value; } } //children private List<Child> _children = new List<Child>(); public ReadOnlyCollection<Child> Children { get {return _children;} } public void AddChild(Child child) { child.Parent = this; _children.Add(child); } }
Nachteil: Das untergeordnete Element wird ohne einen übergeordneten Verweis erstellt. Manchmal erfordert die Initialisierung / Validierung das übergeordnete Element, was bedeutet, dass einige Initialisierungen / Validierungen im übergeordneten Element des untergeordneten Elements vorgenommen werden müssen. Der Code kann kompliziert werden. Es wäre so viel einfacher, das Kind zu implementieren, wenn es immer einen Elternverweis hätte.
Parent macht Factory-Add-Methoden verfügbar, sodass ein Kind immer einen Elternverweis hat. Child ctor ist intern. Eltern-Setter ist privat.
class Child { internal Child(Parent parent, init-params) {Parent = parent;} public Parent Parent {get; private set;} } class Parent { // singleton child public Child Child {get; private set;} public void CreateChild(init-params) { var child = new Child(this, init-params); Child = value; } //children private List<Child> _children = new List<Child>(); public ReadOnlyCollection<Child> Children { get {return _children;} } public Child AddChild(init-params) { var child = new Child(this, init-params); _children.Add(child); return child; } }
Nachteil: Die Initialisierungssyntax kann nicht verwendet werden, z
new Child(){prop = value}
. Stattdessen müssen Sie Folgendes tun:var c = parent.AddChild(); c.prop = value;
Nachteil: Sie müssen die Parameter des untergeordneten Konstruktors in den Add-Factory-Methoden duplizieren.
Nachteil: Es kann kein Eigenschaftssetter für ein Singleton-Kind verwendet werden. Es scheint lahm, dass ich eine Methode zum Festlegen des Werts benötige, aber Lesezugriff über einen Eigenschafts-Getter bereitstelle. Es ist schief.
Child fügt sich dem übergeordneten Element hinzu, auf das in seinem Konstruktor verwiesen wird. Kinder ctor ist öffentlich. Kein öffentlicher Zugriff durch übergeordnetes Element.
//singleton class Child{ public Child(ParentWithChild parent) { Parent = parent; Parent.Child = this; } public ParentWithChild Parent {get; private set;} } class ParentWithChild { public Child Child {get; internal set;} } //children class Child { public Child(ParentWithChildren parent) { Parent = parent; Parent._children.Add(this); } public ParentWithChildren Parent {get; private set;} } class ParentWithChildren { internal List<Child> _children = new List<Child>(); public ReadOnlyCollection<Child> Children { get {return _children;} } }
Nachteil: Aufrufsyntax ist nicht so toll. Normalerweise ruft man eine
add
Methode auf dem Parent auf, anstatt nur ein Objekt wie dieses zu erstellen:var parent = new ParentWithChildren(); new Child(parent); //adds child to parent new Child(parent); new Child(parent);
Und legt eine Eigenschaft fest, anstatt nur ein Objekt wie das folgende zu erstellen:
var parent = new ParentWithChild(); new Child(parent); // sets parent.Child
...
Ich habe gerade erfahren, dass SE einige subjektive Fragen nicht zulässt und dies eindeutig eine subjektive Frage ist. Aber vielleicht ist es eine gute subjektive Frage.