Estoy desarrollando un modelo de objetos que tiene muchas clases diferentes de padres / hijos. Cada objeto hijo tiene una referencia a su objeto padre. Puedo pensar (y he intentado) varias formas de inicializar la referencia principal, pero encuentro inconvenientes significativos para cada enfoque. Dados los enfoques que se describen a continuación, cuál es el mejor ... o qué es aún mejor.
No voy a asegurarme de que el siguiente código se compila, así que intente ver mi intención si el código no es sintácticamente correcto.
Tenga en cuenta que algunos de mis constructores de clases secundarias toman parámetros (que no sean los primarios) aunque no siempre muestre ninguno.
La persona que llama es responsable de establecer el padre y agregar al mismo padre.
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;} } }
Desventaja: establecer el padre es un proceso de dos pasos para el consumidor.
var child = new Child(parent); parent.Children.Add(child);
Desventaja: propenso a errores. La persona que llama puede agregar un niño a un padre diferente al que se usó para inicializar el niño.
var child = new Child(parent1); parent2.Children.Add(child);
Parent verifica que la persona que llama agrega child a parent para la cual se inicializó.
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); } }
Desventaja: la persona que llama aún tiene un proceso de dos pasos para configurar a los padres.
Desventaja: comprobación de tiempo de ejecución: reduce el rendimiento y agrega código a cada agregado / configurador.
El padre establece la referencia del padre del niño (a sí mismo) cuando el niño se agrega / asigna a un padre. El setter primario es interno.
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); } }
Desventaja: el hijo se crea sin una referencia principal. A veces, la inicialización / validación requiere el padre, lo que significa que se debe realizar alguna inicialización / validación en el setter primario del niño. El código puede complicarse. Sería mucho más fácil implementar el elemento secundario si siempre tuviera su referencia principal.
Principal expone métodos de adición de fábrica para que un hijo siempre tenga una referencia principal. El niño ctor es interno. El setter principal es privado.
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; } }
Desventaja: no se puede usar la sintaxis de inicialización como
new Child(){prop = value}
. En cambio tiene que hacer:var c = parent.AddChild(); c.prop = value;
Desventaja: tiene que duplicar los parámetros del constructor hijo en los métodos add-factory.
Desventaja: no se puede usar un establecedor de propiedades para un hijo único. Parece lamentable que necesite un método para establecer el valor pero proporcionar acceso de lectura a través de un captador de propiedades. Es desigual.
El niño se agrega al padre al que se hace referencia en su constructor. El niño ctor es público. No hay acceso de agregar público desde el padre.
//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;} } }
Desventaja: la sintaxis de llamadas no es excelente. Normalmente uno llama a un
add
método en el padre en lugar de simplemente crear un objeto como este:var parent = new ParentWithChildren(); new Child(parent); //adds child to parent new Child(parent); new Child(parent);
Y establece una propiedad en lugar de simplemente crear un objeto como este:
var parent = new ParentWithChild(); new Child(parent); // sets parent.Child
...
Acabo de enterarme de que SE no permite algunas preguntas subjetivas y claramente esta es una pregunta subjetiva. Pero, tal vez es una buena pregunta subjetiva.
fuente
Respuestas:
Me mantendría alejado de cualquier escenario que necesariamente requiera que el niño sepa sobre el padre.
Hay formas de pasar mensajes del niño al padre a través de eventos. De esta manera, el padre, al agregarlo, simplemente tiene que registrarse en un evento que el niño desencadena, sin que el niño tenga que saber sobre el padre directamente. Después de todo, este es probablemente el uso previsto de un niño que conoce a su padre, para poder usar el padre con algún efecto. Excepto que no querrías que el niño realice el trabajo del padre, así que realmente lo que quieres hacer es simplemente decirle al padre que algo ha sucedido. Por lo tanto, lo que necesita manejar es un evento en el niño, del cual el padre puede aprovechar.
Este patrón también se escala muy bien si este evento se vuelve útil para otras clases. Quizás es un poco exagerado, pero también evita que te dispares en el pie más tarde, ya que es tentador querer usar a los padres en la clase de tu hijo, que solo combina las dos clases aún más. Posteriormente, la refactorización de tales clases lleva mucho tiempo y puede crear fácilmente errores en su programa.
¡Espero que ayude!
fuente
Child
) mantenga un conjunto de observadores (Parent
), que reintroduce la circularidad de forma inversa (e introduce una serie de problemas propios).Creo que su opción 3 podría ser la más limpia. Tu escribiste.
No veo eso como un inconveniente. De hecho, el diseño de su programa puede beneficiarse de los objetos secundarios que se pueden crear sin un padre primero. Por ejemplo, puede hacer que las pruebas de niños en forma aislada sean mucho más fáciles. Si un usuario de su modelo olvida agregar un elemento secundario a su elemento primario y llama a un método que espera que la propiedad principal se inicialice en su clase secundaria, obtiene una excepción de referencia nula, que es exactamente lo que desea: un bloqueo anticipado por un error uso.
Y si cree que un elemento secundario necesita que su atributo principal se inicialice en el constructor en todas las circunstancias por razones técnicas, use algo como un "objeto primario nulo" como valor predeterminado (aunque esto tiene el riesgo de enmascarar errores).
fuente
SetParent
método que deba admitir el cambio de la paternidad de un hijo existente (que puede ser difícil de hacer y / o sin sentido) o solo se podrá llamar una vez. Modelar situaciones como los agregados (como sugiere Mike Brown) puede ser mucho mejor que hacer que los niños comiencen sin padres.No hay nada que evite la alta cohesión entre dos clases que se usan comúnmente juntas (por ejemplo, un Order y LineItem se referenciarán entre sí). Sin embargo, en estos casos, tiendo a adherirme a las reglas de diseño impulsado por dominio y modelarlas como un agregado con el padre como la raíz agregada. Esto nos dice que el AR es responsable de la vida útil de todos los objetos en su conjunto.
Por lo tanto, sería más parecido a su escenario cuatro donde el padre expone un método para crear sus hijos aceptando los parámetros necesarios para inicializar adecuadamente los hijos y agregarlos a la colección.
fuente
Sugeriría tener objetos "child-factory" que se pasan a un método padre que crea un hijo (usando el objeto "child factory"), lo adjunta y devuelve una vista. El objeto hijo en sí nunca se expondrá fuera del padre. Este enfoque puede funcionar bien para cosas como las simulaciones. En una simulación electrónica, un objeto particular de "fábrica secundaria" podría representar las especificaciones para algún tipo de transistor; otro podría representar las especificaciones para una resistencia; Un circuito que necesita dos transistores y cuatro resistencias podría crearse con un código como:
Tenga en cuenta que el simulador no necesita retener ninguna referencia al objeto hijo-creador, y
AddComponent
no devolverá una referencia al objeto creado y mantenido por el simulador, sino más bien un objeto que representa una vista. Si elAddComponent
método es genérico, el objeto de vista podría incluir funciones específicas del componente, pero no expondría a los miembros que el padre utiliza para administrar el archivo adjunto.fuente
Gran listado No sé qué método es el "mejor", pero aquí hay uno para encontrar los métodos más expresivos.
Comience con la clase padre e hijo más simple posible. Escribe tu código con esos. Una vez que note la duplicación de código que se puede nombrar, lo pone en un método.
Quizás lo consigas
addChild()
. Tal vez obtienes algo comoaddChildren(List<Child>)
oaddChildrenNamed(List<String>)
oloadChildrenFrom(String)
onewTwins(String, String)
oChild.replicate(int)
.Si su problema es realmente sobre forzar una relación uno a muchos, tal vez debería
Esta no es una respuesta, pero espero que encuentres una mientras lees esto.
fuente
Aprecio que tener vínculos de niño a padre tuviera sus desventajas, como se señaló anteriormente.
Sin embargo, para muchos escenarios, las soluciones alternativas con eventos y otras mecánicas "desconectadas" también traen sus propias complejidades y líneas de código adicionales.
Por ejemplo, generar un evento de Child para que sea recibido por su Parent unirá a ambos, aunque de forma flexible.
Quizás para muchos escenarios es claro para todos los desarrolladores lo que significa una propiedad Child.Parent. Para la mayoría de los sistemas en los que trabajé, esto ha funcionado bien. El exceso de ingeniería puede llevar mucho tiempo y ... ¡Confuso!
Tenga un método Parent.AttachChild () que realice todo el trabajo necesario para vincular el elemento secundario a su elemento primario. Todos tienen claro lo que esto "significa"
fuente