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
Child
a meno che non esista giàParent
eReference
- non può aggiungere
Child
se esiste un'altraChild
con lo stessoParent
eReference
già 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
bool
che 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
null
per 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
Notification
personalizzato (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 costruireChild
prima di passarlo inAddChild
ed esporre la proprietàChild.ID
come settabile pubblico in modo cheSetOfThings
possa 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.ID
impostabile 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.