Ho 3 classi semplici. A Reference , a Parent e a Child . Child conosce le istanze Reference e Parent a cui è associata. Eccoli, inizializzazione e altri dati / metodi omessi:
class Reference
{
public int ID { get; private set; }
}
class Parent
{
public int ID { get; private set; }
}
class Child
{
public int ID { get; private set; }
public Reference Reference {get; private set; }
public Parent Parent {get; private set; }
}
Ecco una classe che ne mantiene le raccolte:
class SetOfThings
{
private List<Reference> _refs;
private List<Parent> _parents;
private List<Child> _children;
// ...
// initialization, etc
// ...
}
Applica vincoli come
- non può aggiungere
Childa meno che non esista giàParenteReference - non può aggiungere
Childse esiste un'altraChildcon lo stessoParenteReferencegià esistente
e così via. Attualmente, il metodo AddChild impone i vincoli con ArgumentExceptions e restituisce il nuovo Child con un nuovo ID univoco:
class SetOfThings // continued
{
public AddChild(Reference ref, Parent parent)
{
if (!_refs.Contains(ref))
throw new ArgumentException($"{ref} not yet known");
if (_children.Any(c => c.Parent == parent && c.Reference == ref))
throw new ArgumentException($"{ref} and {parent} already referenced by a child");
int newID = MakeNewChildID();
var newChild = new Child(newID, ref, parent);
_children.Add(newChild);
return newChild;
}
}
Tutte le mie chiamate AddChild hanno un aspetto simile a questo:
Child newChild;
try
{
newChild = AddChild(someReference, someParent);
}
catch (ArgumentException ex)
{
UI.Alert(ex.Message);
return;
}
// ...
// Trigger state change, update UI with new child, etc.
// ...
Tuttavia, sembra un abuso del meccanismo di eccezione. Non è un comportamento veramente "eccezionale" quando vengono lanciati quei ArgumentExceptions - è solo un normale flusso di controllo. Ma non so come evitarlo:
-
Potrei restituire un
boolche indica successo / fallimento e utilizzare un parametro di output per "restituire" il nuovo figlio,bool AddChild(Reference r, Parent p, out Child c)ma non ho alcuna indicazione di perché si è verificato un errore e i parametri di output sacrificano la leggibilità.
-
Potrei restituire
nullper indicare un errore. La firma del metodo non cambierebbe, ma non so ancora perché si verificano errori. Inoltre, un riferimento null probabilmente non dovrebbe essere previsto comportamento più di quanto dovrebbero essere le eccezioni. -
Potrei restituire un oggetto
Notificationpersonalizzato (o un suo elenco) che indica errori con severità e stringhe di messaggi.Notification AddChild(Reference r, Parent p, out Child c)Molte informazioni, evviva! Ma ho ancora bisogno di un parametro di output. D'oh.
-
Potrei restituire
Notification, ma costruireChildprima di passarlo inAddChilded esporre la proprietàChild.IDcome settabile pubblico in modo cheSetOfThingspossa cambiarlo in modo appropriato.var newChild = new Child(someRef, someParent); var notification = set.AddChild(newChild); if (/* notification is failure */) // change state, discard newChild, display message else // change state, use newChild, etc.Ma la proprietà
Child.IDimpostabile dal pubblico per rendere questo lavoro è al massimo confusa e pericolosa, nel peggiore dei casi.
Mi manca qualche alternativa ovvia o comune? Non sto aprendo nuovi orizzonti, ma non riesco a trovare un modo chiaro e leggibile per una classe di raccolta di avere la creazione e la convalida nello stesso metodo.