Я разрабатываю объектную модель, которая имеет много разных родительских / дочерних классов. Каждый дочерний объект имеет ссылку на свой родительский объект. Я могу придумать (и попробовал) несколько способов инициализации родительской ссылки, но я нахожу существенные недостатки каждого подхода. Учитывая подходы, описанные ниже, что лучше ... или что еще лучше.
Я не собираюсь удостовериться, что код ниже компилируется, поэтому, пожалуйста, постарайтесь увидеть мое намерение, если код не является синтаксически правильным.
Обратите внимание, что некоторые из моих конструкторов дочерних классов принимают параметры (кроме родительских), хотя я не всегда показываю их.
Абонент отвечает за установку родителя и добавление к тому же родителю.
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;} } }
Недостаток: установка родителя является двухэтапным процессом для потребителя.
var child = new Child(parent); parent.Children.Add(child);
Недостаток: подвержен ошибкам. Вызывающая сторона может добавить дочерний элемент к другому родителю, чем тот, который использовался для инициализации дочернего элемента.
var child = new Child(parent1); parent2.Children.Add(child);
Родитель проверяет, что вызывающая сторона добавляет потомка к родителю, для которого была инициализирована.
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); } }
Недостаток: у вызывающего абонента все еще есть двухэтапный процесс установки родителя.
Недостаток: проверка во время выполнения - снижает производительность и добавляет код для каждого добавления / установки.
Родитель устанавливает родительскую ссылку дочернего элемента (на себя), когда дочерний элемент добавляется / присваивается родителю. Родительский сеттер является внутренним.
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); } }
Недостаток: дочерний элемент создается без родительской ссылки. Иногда инициализация / проверка требует родителя, что означает, что некоторая инициализация / проверка должны быть выполнены в родительском установщике ребенка. Код может быть сложным. Было бы намного проще реализовать дочерний элемент, если бы у него всегда была родительская ссылка.
Родитель предоставляет доступ к фабричным методам добавления, так что дочерний элемент всегда имеет родительскую ссылку. Детский ctor является внутренним. Родительский сеттер является частным.
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; } }
Недостаток: невозможно использовать синтаксис инициализации, например
new Child(){prop = value}
. Вместо этого нужно сделать:var c = parent.AddChild(); c.prop = value;
Недостаток: приходится дублировать параметры дочернего конструктора в методах добавления фабрики.
Недостаток: нельзя использовать установщик свойства для дочернего элемента-одиночки. Мне кажется, что мне нужен метод для установки значения, но для доступа к чтению через метод получения свойства. Это однобокий
Child добавляет себя к родителю, указанному в его конструкторе. Детский ctor является публичным. Нет публичного доступа для добавления от родителя.
//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;} } }
Недостаток: синтаксис вызова не велик. Обычно вызывается
add
метод для родителя, а не просто создается объект, подобный этому:var parent = new ParentWithChildren(); new Child(parent); //adds child to parent new Child(parent); new Child(parent);
И устанавливает свойство, а не просто создает объект следующим образом:
var parent = new ParentWithChild(); new Child(parent); // sets parent.Child
...
Я только что узнал, что SE не допускает некоторые субъективные вопросы, и ясно, что это субъективный вопрос. Но, может быть, это хороший субъективный вопрос.
источник
Ответы:
Я бы держался подальше от любого сценария, который обязательно требует, чтобы ребенок знал о родителе.
Существуют способы передачи сообщений от ребенка к родителю через события. Таким образом, родительский элемент после добавления просто должен зарегистрироваться в событии, которое вызывает дочерний элемент, при этом дочерний элемент не должен знать непосредственно о родительском элементе. В конце концов, это, вероятно, предполагаемое использование ребенком знания о его родителе, чтобы иметь возможность использовать родителя для какого-либо эффекта. За исключением того, что вы не хотите, чтобы ребенок выполнял работу родителя, поэтому на самом деле вы просто хотите сказать родителю, что что-то произошло. Поэтому вам нужно обработать событие для ребенка, которым родитель может воспользоваться.
Этот шаблон также очень хорошо масштабируется, если это событие станет полезным для других классов. Возможно, это немного излишне, но это также не позволяет вам выстрелить себе в ногу позже, поскольку становится заманчивым желание использовать родителя в вашем дочернем классе, который только больше объединяет эти два класса. Рефакторинг таких классов впоследствии занимает много времени и может легко создавать ошибки в вашей программе.
Надеюсь, это поможет!
источник
Child
), поддерживающее набор наблюдателей (Parent
), которое вновь вводит цикличность в обратном направлении (и вводит ряд собственных проблем).Я думаю, что ваш вариант 3 может быть самым чистым. Вы написали.
Я не считаю это недостатком. Фактически, дизайн вашей программы может извлечь выгоду из дочерних объектов, которые могут быть созданы без родителя. Например, это может сделать тестирование детей в изоляции намного проще. Если пользователь вашей модели забывает добавить дочерний элемент к своему родительскому элементу и вызывает методы, которые ожидают инициализации родительского свойства в вашем дочернем классе, он получает исключение null ref - это именно то, что вам нужно: ранний сбой из-за неправильного использование.
И если вы думаете, что дочерний атрибут должен инициализировать свой родительский атрибут в конструкторе при любых обстоятельствах по техническим причинам, используйте что-то вроде «нулевого родительского объекта» в качестве значения по умолчанию (хотя это может привести к ошибкам маскирования).
источник
SetParent
метод, который либо должен будет поддерживать изменение происхождения существующего дочернего объекта (что может быть трудно сделать, и / или бессмысленный) или будет вызван только один раз. Моделирование таких ситуаций, как совокупности (как предлагает Майк Браун), может быть намного лучше, чем начинать детей без родителей.Нет ничего, что могло бы предотвратить высокую сплоченность между двумя классами, которые обычно используются вместе (например, Order и LineItem обычно ссылаются друг на друга). Однако в этих случаях я, как правило, придерживаюсь правил разработки, управляемых доменом, и моделирую их как агрегат, в котором родитель является агрегатным корнем. Это говорит нам о том, что AR отвечает за время жизни всех объектов в своей совокупности.
Таким образом, наиболее похоже на ваш четвертый сценарий, где родительский объект предоставляет метод для создания своих дочерних элементов, принимая любые необходимые параметры для правильной инициализации дочерних элементов и добавляя его в коллекцию.
источник
Я бы предложил иметь объекты "child-factory", которые передаются родительскому методу, который создает дочерний объект (используя объект "child factory"), присоединяет его и возвращает представление. Сам дочерний объект никогда не будет выставлен вне родительского объекта. Этот подход может хорошо работать для таких вещей, как моделирование. В моделировании электроники один конкретный объект «фабрика-потомок» может представлять спецификации для некоторого типа транзистора; другой может представлять спецификации для резистора; схема, для которой нужны два транзистора и четыре резистора, может быть создана с кодом, подобным следующему:
Обратите внимание, что симулятору не нужно сохранять какую-либо ссылку на объект child-creator, и
AddComponent
он не будет возвращать ссылку на объект, созданный и поддерживаемый симулятором, а скорее на объект, который представляет представление. ЕслиAddComponent
метод является универсальным, объект представления может включать в себя специфичные для компонента функции, но не отображать элементы, которые родительский элемент использует для управления вложением.источник
Отличный список. Я не знаю, какой метод является «лучшим», но вот один, чтобы найти наиболее выразительные методы.
Начните с самого простого родительского и дочернего класса. Напишите свой код с этими. Как только вы заметите дублирование кода, которое может быть названо, вы поместите его в метод.
Может быть, вы получите
addChild()
. Может быть, вы получаете что-то вродеaddChildren(List<Child>)
илиaddChildrenNamed(List<String>)
илиloadChildrenFrom(String)
илиnewTwins(String, String)
или илиChild.replicate(int)
.Если ваша проблема действительно в навязывании отношений один-ко-многим, возможно, вам следует
Это не ответ, но я надеюсь, что вы найдете его, читая это.
источник
Я ценю то, что наличие ссылок от ребенка к родителю имело свои недостатки, как отмечалось выше.
Однако для многих сценариев обходные пути с событиями и другой «отключенной» механикой также имеют свои сложности и дополнительные строки кода.
Например, создание события от Child, которое будет получено его Родителем, свяжет оба вместе, хотя и в стиле слабой связи.
Возможно, для многих сценариев всем разработчикам понятно, что означает свойство Child.Parent. Для большинства систем, над которыми я работал, это работало просто отлично. На проектирование может уйти много времени и…. Заблуждение!
Иметь метод Parent.AttachChild (), который выполняет всю работу, необходимую для привязки дочернего элемента к его родительскому элементу. Всем понятно, что это значит
источник