Credo che tu abbia frainteso l'SRP, o almeno lo stia applicando solo parzialmente.
- First solution: In the
KPI
class
It seems like it respects the SRP, the KPI class is responsible for the values of the KPI.
Qui ci sono due responsabilità: recuperare il valore e archiviare il valore (in memoria).
"È correlato al KPI" non è una descrizione accurata di una responsabilità.
Le responsabilità sono generalmente espresse come verbi: memorizzazione dei dati, registrazione dei messaggi, stampa dell'output, ordinamento dell'elenco, formattazione della data, ...
- Second solution: In the
Item
class
La mia preoccupazione è che si rompa SRP, l'elemento non dovrebbe essere incaricato di impostare i valori, dovrebbe?
Sono d'accordo con te qui, Item
non è sicuramente dove dovresti metterlo. Ma il tuo ragionamento è in qualche modo imperfetto (allo stesso modo del precedente punto elenco)
What would be the best way of doing it? Is there a third, better way I did not think about?
In this case, performance is not really an issue, the table and the tree are small, but I am learning, and would like to learn about the best practices.
Nell'interesse dell'apprendimento, terrò la risposta dimostrativa ma non implementata a livello di esperti di livello professionale.
Inoltre, non hai specificato come vuoi inserire i dati e vorrei che la mia risposta si concentri sull'intenzione del codice, piuttosto che sull'implementazione. Quindi mi concentrerò principalmente sulla struttura e sul risultato, piuttosto che sul codice specifico.
Prima di tutto
Un piccolo reframing filosofico.
Hai lasciato qualche lacuna nella tua domanda, ad es. come sono collegati gli oggetti o quale è lo scopo principale del sistema. Sto compilando alcuni spazi vuoti qui sulla base dell'esperienza e dell'inferenza.
A quanto ho capito, Item
e KPI
sono due parti della stessa entità. KPI
è la carne e le patate effettive, il valore dell'oggetto. Item
, d'altra parte, è semplicemente un wrapper che ti aiuta a connettere valori di KPI
tra loro (in una struttura gerarchica).
A meno che tu non abbia omesso una parte importante (o ci sto riflettendo sopra), non vedo alcun motivo per Item
e KPI
per essere due classi separate. Per il resto della risposta (a meno che tu non possa spiegare perché questo non è corretto), ho intenzione di unirli in una singola classe:
public class KpiItem
{
public string Name { get; set; }
public double Value { get; set; }
public List<KpiItem> Children { get; set; }
// Personally, I prefer also having a link to the parent.
// But this is optional.
public KpiItem Parent { get; set; }
}
In secondo luogo, in base ai dati della tabella, non vi è alcuna spiegazione su come viene archiviata la gerarchia degli elementi. Forse questo è memorizzato in un'altra tabella (ad esempio Items
) che non ci hai mostrato. Forse semplicemente non hai ancora sviluppato quella parte.
Qualunque sia la ragione, dobbiamo sapere come sono collegati prima di poterli recuperare con la gerarchia corretta. Quindi aggiungo una colonna alla tua tabella: il nome del genitore.
+------+-------+-------------------+
+ Name + Value + Parent_Name +
+------+-------+-------------------+
+ foo + 5 + (null) +
+ bar + 4.56 + foo +
+------+-------+-------------------+
In altre parole: bar
è un figlio di foo
. foo
non è figlio di nessuno, è un elemento di primo livello.
Terza soluzione: una classe separata creata per gestire il recupero dei dati .
Questo è un modo molto più SRP di affrontare il problema.
Ora, non abbiamo ancora stabilito cosa vuoi recuperare:
- Un singolo elemento (per nome)
- Un singolo elemento (per nome) più i suoi figli diretti
- Un singolo elemento (per nome) più tutti i suoi discendenti (figli, nipoti, pronipoti, ...)
A scopo dimostrativo, mostrerò un modo per recuperare un singolo elemento (per nome) più i suoi figli diretti. Se vuoi ottenere tutti i discendenti, è semplice implementare la ricorsione (a meno che il tuo set di dati non abbia riferimenti circolari).
Dato che stai utilizzando query SQL, anch'io. Avremo bisogno di due query:
- Recupero di un articolo in base al suo nome.
- Recupero di un elemento basato sul suo nome del genitore .
Le query sono relativamente semplici:
SELECT * FROM KPIITEMS WHERE NAME = 'foo'
SELECT * FROM KPIITEMS WHERE PARENT_NAME = 'foo'
Sidenote
Notice that I'm doing SELECT *
instead of SELECT value
. For your current example, you already know the name you're looking for, so you're only interested in the value.
But this quickly falls apart when you start looking for other things, e.g. the first alphabetical item, the item with the longest name, ... Because now, you don't know the name.
Generally, you'll want to create your KpiItem
based on the returned query; not based on the filters you supplied. While one could logically explain the other; this only applies in rare cases and is not a good general approach. This is why I did SELECT *
, so that the query returns all columns so we can fully populate the KpiItem
.
Poiché la domanda non si concentra su come recuperare i dati da SQL, ho intenzione di ometterlo per semplicità. Supponiamo che esistano questi metodi:
public KpiItem GetItem(string query);
public List<KpiItem> GetItems(string query);
In altre parole, se forniamo la query corretta a questo metodo, ci fornisce gli oggetti necessari.
Un'implementazione di esempio di questa terza classe sarebbe la seguente:
public class DataFetcher
{
public KpiItem GetKpiItemWithChildren(string name)
{
var theItem = GetKpiItem_ByName(name);
theItem.Children = GetKpiItemsBy_ParentName(name);
return theItem;
}
private KpiItem GetKpiItem_ByName(string name)
{
return GetItem("SELECT * FROM KPIITEMS WHERE NAME = '" + name + "'");
}
private List<KpiItem> GetKpiItems_ByParentName(string parentName)
{
return GetItems("SELECT * FROM KPIITEMS WHERE PARENT_NAME = '" + parentName + "'");
}
}
E quindi puoi usare questa classe come segue:
var fetcher = new DataFetcher();
var fooItem = fetcher.GetKpiItemWithChildren("foo");
Sidenote
There are things missing for this example to be a properly usable (professional) piece of code. Most importantly, this does not protect against SQL injection.
You really should protect against SQL injection (read up on it when you're ready), but I've omitted it from the current example because (a) as a beginner you're likely not at that point yet and (b) I'm trying to keep the example as simple as possible so you can understand the intended structure.
Il tuo esempio è semplice. Una volta entrati nella creazione di applicazioni con molte tabelle e strutture dati notevolmente più complesse, questa risposta non si applica più. Ma suppongo che tu stia attualmente cercando una spiegazione per principianti, non tanto per un'implementazione a livello di esperti.
Se sei interessato a scalare verso l'alto, un buon punto di partenza è la lettura su Entity Framework e LINQ . Ma non c'è bisogno di affrettarsi, c'è il merito di concentrarsi prima sulle basi (come si dice, solo gli stupidi si affrettano a).