Tworzę model obiektowy, który ma wiele różnych klas nadrzędnych / podrzędnych. Każdy obiekt podrzędny ma odniesienie do swojego obiektu nadrzędnego. Mogę wymyślić (i próbowałem) kilka sposobów na zainicjowanie referencji nadrzędnej, ale uważam, że każde podejście ma znaczące wady. Biorąc pod uwagę opisane poniżej podejścia, które są najlepsze ... lub jeszcze lepsze.
Nie zamierzam się upewnić, że poniższy kod się skompiluje, więc spróbuj zobaczyć mój zamiar, jeśli kod nie jest poprawny pod względem składniowym.
Zauważ, że niektóre z moich konstruktorów klas potomnych pobierają parametry (inne niż nadrzędne), chociaż nie zawsze je wyświetlam.
Dzwoniący jest odpowiedzialny za ustawienie rodzica i dodanie do tego samego rodzica.
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;} } }
Wada: ustawienie rodzica jest procesem dwuetapowym dla konsumenta.
var child = new Child(parent); parent.Children.Add(child);
Wada: podatność na błędy. Dzwoniący może dodać dziecko do innego rodzica niż ten, którego użyto do zainicjowania dziecka.
var child = new Child(parent1); parent2.Children.Add(child);
Rodzic sprawdza, czy dzwoniący dodaje dziecko do rodzica, dla którego zostało zainicjowane.
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); } }
Wada: dzwoniący nadal ma dwuetapowy proces ustawiania rodzica.
Wada: sprawdzanie w czasie wykonywania - zmniejsza wydajność i dodaje kod do każdego dodającego / ustawiającego.
Rodzic ustawia odwołanie do rodzica dziecka (do siebie), gdy dziecko jest dodawane / przypisywane do rodzica. Setter nadrzędny jest wewnętrzny.
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); } }
Wada: dziecko jest tworzone bez odniesienia rodzica. Czasami inicjalizacja / walidacja wymaga rodzica, co oznacza, że pewna inicjalizacja / walidacja musi zostać wykonana w seterze rodzica dziecka. Kod może się skomplikować. O wiele łatwiej byłoby zaimplementować dziecko, gdyby zawsze miało ono odniesienie do rodzica.
Rodzic udostępnia fabryczne metody dodawania, dzięki czemu dziecko zawsze ma odniesienie rodzica. Dziecko jest wewnętrzne. Seter nadrzędny jest prywatny.
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; } }
Wada: nie można użyć składni inicjującej, takiej jak
new Child(){prop = value}
. Zamiast tego musisz zrobić:var c = parent.AddChild(); c.prop = value;
Wada: trzeba powielić parametry konstruktora potomnego w metodach add-factory.
Wada: nie można użyć narzędzia do ustawiania właściwości dla pojedynczego dziecka. Wydaje się kiepskie, że potrzebuję metody ustawiania wartości, ale zapewniam dostęp do odczytu za pośrednictwem modułu pobierania właściwości. Jest krzywy.
Dziecko dodaje się do rodzica, do którego odwołuje się jego konstruktor. Dziecko jest publiczne. Brak publicznego dodawania dostępu od rodzica.
//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;} } }
Wada: wywołanie składni nie jest świetne. Zwykle wywołuje się
add
metodę nadrzędną zamiast po prostu utworzyć taki obiekt:var parent = new ParentWithChildren(); new Child(parent); //adds child to parent new Child(parent); new Child(parent);
I ustawia właściwość zamiast tworzyć taki obiekt:
var parent = new ParentWithChild(); new Child(parent); // sets parent.Child
...
Właśnie dowiedziałem się, że SE nie pozwala na pewne subiektywne pytania i wyraźnie jest to pytanie subiektywne. Ale może to dobre pytanie subiektywne.
źródło
Odpowiedzi:
Trzymałbym się z dala od jakiegokolwiek scenariusza, który wymagałby, aby dziecko wiedziało o rodzicu.
Istnieją sposoby przekazywania wiadomości od dziecka do rodzica poprzez zdarzenia. W ten sposób rodzic, po dodaniu, musi po prostu zarejestrować się w zdarzeniu, które dziecko wyzwala, bez konieczności bezpośredniego kontaktu dziecka z rodzicem. W końcu jest to prawdopodobnie zamierzone użycie dziecka, które wie o swoim rodzicu, aby móc z niego skorzystać. Tyle że nie chcesz, aby dziecko wykonywało pracę rodzica, więc tak naprawdę to po prostu powiedz rodzicowi, że coś się stało. Dlatego musisz obsłużyć zdarzenie na dziecku, z którego rodzic może skorzystać.
Ten wzór skaluje się również bardzo dobrze, jeśli to wydarzenie stanie się przydatne dla innych klas. Być może jest to trochę przesada, ale uniemożliwia później strzelanie sobie w stopę, ponieważ kusi chęć skorzystania z rodzica w klasie dziecka, która jeszcze bardziej łączy dwie klasy. Refaktoryzacja takich klas jest później czasochłonna i może łatwo powodować błędy w twoim programie.
Mam nadzieję, że to pomaga!
źródło
Child
) utrzymanie zestawu obserwatorów (Parent
), który przywraca cykliczność do tyłu (i wprowadza wiele własnych problemów).Myślę, że Twoja opcja 3 może być najczystsza. Napisałeś.
Nie uważam tego za wadę. W rzeczywistości projekt twojego programu może korzystać z obiektów potomnych, które można najpierw utworzyć bez elementu nadrzędnego. Na przykład może znacznie ułatwić testowanie dzieci w izolacji. Jeśli użytkownik Twojego modelu zapomni o dodaniu dziecka do jego rodzica i wywoła metody, które oczekują, że właściwość nadrzędna zostanie zainicjowana w klasie podrzędnej, wówczas otrzyma wyjątek zerowy - co jest dokładnie tym, czego chcesz: wczesną awarią niewłaściwego stosowanie.
A jeśli uważasz, że dziecko potrzebuje zainicjowania atrybutu nadrzędnego w konstruktorze we wszystkich okolicznościach ze względów technicznych, użyj czegoś takiego jak „zerowy obiekt nadrzędny” jako wartość domyślna (chociaż istnieje ryzyko maskowania błędów).
źródło
SetParent
metody, która albo będzie musiała obsługiwać zmianę rodzicielstwa istniejącego obiektu podrzędnego (co może być trudne i / lub bezsensowne) lub będzie można je wywołać tylko raz. Modelowanie takich sytuacji jako agregatów (jak sugeruje Mike Brown) może być znacznie lepsze niż posiadanie dzieci bez rodziców.Nic nie stoi na przeszkodzie wysokiej spójności między dwiema klasami, które są powszechnie używane razem (np. Order i LineItem będą się nawzajem odwoływać). Jednak w tych przypadkach staram się przestrzegać reguł projektowania opartego na domenie i modelować je jako agregat, a rodzic jest korzeniem agregatu. To mówi nam, że AR jest odpowiedzialny za czas życia wszystkich obiektów w swojej agregacji.
Byłoby to najbardziej podobne do scenariusza czwartego, w którym rodzic udostępnia metodę tworzenia swoich elementów podrzędnych, akceptując wszelkie parametry niezbędne do prawidłowej inicjalizacji elementów podrzędnych i dodając je do kolekcji.
źródło
Sugerowałbym posiadanie obiektów „child-factory”, które są przekazywane do metody nadrzędnej, która tworzy dziecko (przy użyciu obiektu „child factory”), dołącza je i zwraca widok. Sam obiekt potomny nigdy nie zostanie odsłonięty poza rodzicem. To podejście może działać dobrze w przypadku symulacji. W symulacji elektronicznej jeden konkretny obiekt „fabryki podrzędnej” może reprezentować specyfikacje dla pewnego rodzaju tranzystora; inny może reprezentować specyfikację rezystora; obwód, który potrzebuje dwóch tranzystorów i czterech rezystorów, może zostać utworzony z użyciem kodu takiego jak:
Zauważ, że symulator nie musi zachowywać żadnego odniesienia do obiektu twórcy podrzędnego i
AddComponent
nie zwróci odniesienia do obiektu utworzonego i przechowywanego przez symulator, ale raczej obiekt reprezentujący widok. JeśliAddComponent
metoda jest ogólna, obiekt widoku może zawierać funkcje specyficzne dla komponentu, ale nie ujawnia elementów, których rodzic używa do zarządzania załącznikiem.źródło
Świetna oferta. Nie wiem, która metoda jest „najlepsza”, ale tutaj można znaleźć najbardziej ekspresyjne metody.
Zacznij od najprostszej możliwej klasy nadrzędnej i podrzędnej. Napisz do nich swój kod. Gdy zauważysz duplikację kodu, który można nazwać, umieść go w metodzie.
Może dostaniesz
addChild()
. Może dostajesz coś takiego jakaddChildren(List<Child>)
lubaddChildrenNamed(List<String>)
lubloadChildrenFrom(String)
lubnewTwins(String, String)
lubChild.replicate(int)
.Jeśli twoim problemem jest narzucenie relacji jeden do wielu, może powinieneś
To nie jest odpowiedź, ale mam nadzieję, że znajdziesz ją podczas czytania.
źródło
Rozumiem, że posiadanie powiązań między dzieckiem a rodzicem miało swoje wady, jak wspomniano powyżej.
Jednak w wielu scenariuszach obejścia zdarzeń i inne „odłączone” mechanizmy również przynoszą własne złożoności i dodatkowe wiersze kodu.
Na przykład podniesienie zdarzenia od Dziecka, które ma być odebrane przez Rodzica, wiąże oba razem, choć w luźny sposób.
Być może dla wielu scenariuszy dla wszystkich programistów jasne jest, co oznacza właściwość Child.Parent. W przypadku większości systemów, nad którymi pracowałem, działało to dobrze. Nadmiar inżynierii może być czasochłonny i…. Mylące!
Mieć metodę Parent.AttachChild (), która wykonuje całą pracę potrzebną do powiązania dziecka z rodzicem. Wszyscy mają jasność co do tego, co to znaczy
źródło