Je développe un modèle objet qui comporte de nombreuses classes parent / enfant. Chaque objet enfant a une référence à son objet parent. Je peux penser (et avoir essayé) plusieurs façons d’initialiser la référence parente, mais je trouve des inconvénients importants pour chaque approche. Compte tenu des approches décrites ci-dessous, quelle est la meilleure ... ou encore, la meilleure solution?
Je ne vais pas m'assurer que le code ci-dessous est compilé, essayez donc de voir mon intention si le code n'est pas correct du point de vue de la syntaxe.
Notez que certains de mes constructeurs de classes enfants prennent des paramètres (autres que parent) même si je n'en affiche pas toujours.
L'appelant est responsable de la définition du parent et de l'ajout au même parent.
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;} } }
Inconvénient: la définition du parent est un processus en deux étapes pour le consommateur.
var child = new Child(parent); parent.Children.Add(child);
Inconvénient: risque d'erreur. L'appelant peut ajouter un enfant à un parent différent de celui utilisé pour l'initialiser.
var child = new Child(parent1); parent2.Children.Add(child);
Parent vérifie que l'appelant ajoute l'enfant au parent pour lequel il a été initialisé.
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); } }
Inconvénient: l' appelant a toujours un processus en deux étapes pour définir le parent.
Inconvénient: vérification de l'exécution - réduit les performances et ajoute du code à chaque ajout / configurateur.
Le parent définit la référence parent de l'enfant (à lui-même) lorsque l'enfant est ajouté / attribué à un parent. Le parent parent est interne.
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); } }
Inconvénient: l'enfant est créé sans référence parent. Parfois, l’initialisation / validation nécessite le parent, ce qui signifie qu’une initialisation / validation doit être effectuée dans le parent parent de l’enfant. Le code peut devenir compliqué. Il serait tellement plus facile d'implémenter l'enfant s'il avait toujours sa référence parent.
Le parent expose des méthodes d'ajout d'usine afin qu'un enfant ait toujours une référence parent. Le ctor de l'enfant est interne. Le parent parent est privé.
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; } }
Inconvénient: impossible d'utiliser une syntaxe d'initialisation telle que
new Child(){prop = value}
. Au lieu de faire:var c = parent.AddChild(); c.prop = value;
Inconvénient: Doivent dupliquer les paramètres du constructeur enfant dans les méthodes add-factory.
Inconvénient: impossible d'utiliser un créateur de propriété pour un enfant singleton. Il semble boiteux que j'ai besoin d'une méthode pour définir la valeur mais fournir un accès en lecture via un getter de propriété. C'est déséquilibré.
Child s'ajoute au parent référencé dans son constructeur. Le ctor de l'enfant est public. Aucun public n'ajoute l'accès du parent.
//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;} } }
Inconvénient: la syntaxe d'appel n'est pas géniale. Normalement, on appelle une
add
méthode sur le parent au lieu de simplement créer un objet comme ceci:var parent = new ParentWithChildren(); new Child(parent); //adds child to parent new Child(parent); new Child(parent);
Et définit une propriété plutôt que de simplement créer un objet comme celui-ci:
var parent = new ParentWithChild(); new Child(parent); // sets parent.Child
...
Je viens d'apprendre que SE n'autorise pas certaines questions subjectives et qu'il s'agit clairement d'une question subjective. Mais, peut-être que c'est une bonne question subjective.
la source
Réponses:
Je resterais à l'écart de tout scénario qui obligerait nécessairement l'enfant à connaître le parent.
Il existe des moyens de transmettre des messages d'un enfant à un parent via des événements. De cette façon, le parent, lors de l’ajout, doit simplement s’inscrire à un événement déclenché par l’enfant, sans que celui-ci ait à connaître le parent directement. Après tout, il s’agit probablement de l’utilisation prévue d’un enfant connaissant son parent afin de pouvoir utiliser celui-ci à bon escient. Sauf que vous ne voudriez pas que l'enfant d' effectuer le travail du parent, donc vraiment ce que vous voulez faire est de dire simplement le parent que quelque chose est arrivé. Par conséquent, vous devez gérer un événement sur l’enfant, dont le parent peut tirer parti.
Ce modèle évolue également très bien si cet événement devenait utile à d'autres classes. C’est peut-être un peu exagéré, mais cela vous empêche également de vous tirer une balle dans le pied plus tard, car il devient tentant de vouloir utiliser parent dans votre classe enfant qui ne couple encore plus les deux classes. La refactorisation ultérieure de telles classes prend beaucoup de temps et peut facilement créer des bogues dans votre programme.
J'espère que ça t'as aidé!
la source
Child
) maintenant un ensemble d'observateurs (Parent
), ce qui réintroduit la circularité de manière rétrograde (et introduit un certain nombre de problèmes qui lui sont propres).Je pense que votre option 3 pourrait être la plus propre. Tu as écrit.
Je ne vois pas cela comme un inconvénient. En fait, la conception de votre programme peut tirer parti d’objets enfants pouvant être créés sans parent au préalable. Par exemple, cela peut faciliter beaucoup le test des enfants en isolation. Si un utilisateur de votre modèle oublie d’ajouter un enfant à son parent et appelle une méthode qui attend la propriété parent initialisée dans votre classe enfant, il obtient alors une exception null ref, ce qui est exactement ce que vous souhaitez: un crash précoce pour une erreur. usage.
Et si vous pensez qu'un enfant a besoin que son attribut parent soit initialisé dans le constructeur dans toutes les circonstances pour des raisons techniques, utilisez quelque chose comme un "objet parent null" comme valeur par défaut (bien que cela risque de masquer des erreurs).
la source
SetParent
méthode permettant de modifier la filiation d'un enfant existant (ce qui peut s'avérer difficile / ou absurde) ou ne sera appelable qu'une fois. Modéliser de telles situations sous forme d'agrégats (comme le suggère Mike Brown) peut être beaucoup mieux que d'avoir des enfants qui commencent sans parents.Rien n'empêche une forte cohésion entre deux classes couramment utilisées ensemble (par exemple, Order et LineItem se référencent généralement). Toutefois, dans ces cas, j’ai tendance à adhérer aux règles de conception gérées par le domaine et à les modéliser sous la forme d’un agrégat, le parent étant la racine de l’agrégat. Cela nous indique que le RA est responsable de la durée de vie de tous les objets de son agrégat.
Cela ressemble donc beaucoup à votre scénario quatre où le parent expose une méthode pour créer ses enfants en acceptant tous les paramètres nécessaires pour initialiser correctement les enfants et les ajouter à la collection.
la source
Je suggérerais d'avoir des objets "fabrique enfant" qui sont transmis à une méthode parent qui crée un enfant (à l'aide de l'objet "fabrique enfant"), l'attache et renvoie une vue. L'objet enfant lui-même ne sera jamais exposé en dehors du parent. Cette approche peut bien fonctionner pour des choses comme les simulations. Dans une simulation électronique, un objet "d'usine enfant" particulier peut représenter les spécifications d'un type de transistor; un autre pourrait représenter les spécifications d'une résistance; un circuit nécessitant deux transistors et quatre résistances peut être créé avec un code tel que:
Notez que le simulateur n'a pas besoin de conserver de
AddComponent
référence à l'objet créé par l' enfant et ne renvoie pas de référence à l'objet créé et détenu par le simulateur, mais plutôt à un objet représentant une vue. Si laAddComponent
méthode est générique, l'objet de la vue pourrait inclure des fonctions spécifiques au composant, mais n'exposerait pas les membres que le parent utilise pour gérer la pièce jointe.la source
Grande liste. Je ne sais pas quelle méthode est la "meilleure" mais en voici une pour trouver les méthodes les plus expressives.
Commencez avec les classes parents et enfants les plus simples possibles. Écrivez votre code avec ceux-ci. Une fois que vous avez remarqué la duplication de code que vous pouvez nommer, vous l'insérez dans une méthode.
Peut-être que vous obtenez
addChild()
. Peut-être que vous obtenez quelque chose commeaddChildren(List<Child>)
ouaddChildrenNamed(List<String>)
ouloadChildrenFrom(String)
ounewTwins(String, String)
ouChild.replicate(int)
.Si votre problème est vraiment de forcer une relation un-à-plusieurs, vous devriez peut-être
Ce n'est pas une réponse, mais j'espère que vous en trouverez une en lisant ceci.
la source
J'apprécie que le fait d'avoir des liens d'un enfant à l'autre de ses parents présente les inconvénients mentionnés ci-dessus.
Cependant, pour de nombreux scénarios, les solutions de contournement avec des événements et d'autres mécanismes "déconnectés" apportent également leurs propres complexités et des lignes de code supplémentaires.
Par exemple, le fait de lever un événement de Child pour qu'il soit reçu par son parent liera les deux ensemble, bien que de manière lâche.
Peut-être que pour de nombreux scénarios, tous les développeurs savent ce que signifie une propriété Child.Parent. Pour la majorité des systèmes sur lesquels j'ai travaillé, cela a très bien fonctionné. Plus d'ingénierie peut prendre beaucoup de temps et…. Déroutant!
Avoir une méthode Parent.AttachChild () qui effectue tout le travail nécessaire pour lier l’enfant à son parent. Tout le monde sait ce que cela signifie
la source