TimeRange
Case study sull'implementazione di un valore
Compatibilità: dotNET 2.0+
Anno 2017/2018
TimeRange 1 di 30
Indice generale
1 Introduzione............................................................................................4
1.1 Problema: NYC Marathon...........................................................................4
2 Un approccio procedurale........................................................................5
2.1 Opzione 1: nessun modello........................................................................5
2.2 Opzione 2: utilizzare dei campi stringa.......................................................5
2.3 Opzione 3: record.......................................................................................6
2.4 Rappresentare il tempo degli atleti: intervallo di tempo............................7
2.4.1 Implementare le operazioni sui tempi....................................................................7
2.5 Implementazione dei requisiti....................................................................8
2.6 Conclusioni.................................................................................................9
3 Un approccio object oriented.................................................................10
3.1 Oggetto TimeRange: dati + operazioni.....................................................10
3.1.1 Analisi della classe: “funzionamento” del codice object oriented.......................12
3.2 Conclusioni...............................................................................................14
4 Adottare i principi di progettazione OO..................................................15
4.1 Stabilire la modalità di creazione dei tempi.............................................15
4.2 Rappresentare un intervallo di tempo in secondi.....................................16
4.3 Ottimizzare e rendere coerente l’implementazione.................................17
4.3.1 Definire in modo chiaro lo stato dell'oggetto.......................................................18
4.3.2 Utilizzare una struttura.........................................................................................19
4.4 Incapsulare completamente TimeRange..................................................20
4.4.1 “Proteggere” lo stato dell'oggetto........................................................................20
4.4.2 Incapsulamento: non dipendere dall'implementazione.......................................20
5 “Standardizzare” TimeRange..................................................................23
5.1 Definire gli operatori standard.................................................................23
5.2 Eseguire il parsing da stringa....................................................................24
5.3 Informazioni sul valore.............................................................................26
5.3.1 Informazioni sul tipo.............................................................................................26
5.4 Creazione di tempi sulla base di valori totali............................................27
6 Considerazioni finali...............................................................................28
6.1 Utilità e qualità di TimeRange..................................................................28
6.1.1 Validità..................................................................................................................28
6.1.2 Allineamento agli standard dei tipi primitivi........................................................28
TimeRange 2 di 30
6.1.3 Risoluzione temporale e intervallo di variazione..................................................29
6.2 “Immutabilità” di TimeRange...................................................................29
TimeRange 3 di 30
1 Introduzione
In questo tutorial utilizzo un semplice problema di programmazione come mezzo per affrontare le problematiche connesse all'implementazione di un nuovo tipo di dato. L'obiettivo finale è quello di realizzare un tipo analogo a
TimeSpan
il quale incapsula un intervallo di tempo.
A partire dalla definizione del problema, procederò per gradi, utilizzando dapprima un approccio procedurale, per arrivare progressivamente a un corretta soluzione OO. Metterò in evidenza i limiti delle soluzioni adottate di volta in volta, mostrando come superarli e dimostrando che l'implementazione di (o tipo analogo) rappresenta il risultato ideale.
TimeSpan
(Nota a margine: non realizzerò un'implementazione identica a quella di
TimeSpan
ma una molto più semplice e che non prende in considerazione le gestione dell'overflow di valori interi)
1.1 Problema: NYC Marathon
Si desiderano elaborare i tempi dei partecipanti dalla gara della maratona olimpica. Questi sono memorizzati in un file CSV:
Cenci Leonardo, Italia, 4:06:16
Kamworor Geoffrey, Kenya, 2:10:23
Ghirmay Ghebreslassie, Eritrea, 2:11:4
Feyisa Lilesa, Etiopia, 2:09:54
Eliud Kipchoge, Kenya, 2:8:44
Munyo Mutai, Uganda, 2:11:49
Galen Rupp, USA, 2:10:05
Alphonce Simbu, Tanzania, 2:11:15
Jared Ward, USA, 2:11:30
...
Occorre eseguire le seguenti operazioni:
- Visualizzare l'elenco in ordine di classifica (in base ai tempi).
- Visualizzare la differenza tra il tempo migliore e quello peggiore.
Vincoli
Non è possibile utilizzare il tipo
TimeSpan
(Nota bene: per semplicità non si fa riferimento ai requisiti relativi alla UI, alla posizione del file e alle azioni da intraprendere nel caso in cui il contenuto del file non sia valido.)
TimeRange 4 di 30
2 Un approccio procedurale
Davanti a un problema simile la prima questione è: come rappresentare i dati. Naturalmente, il modello scelto deve avere la funzione di favorire l'implementazione delle richieste del problema: visualizzazione delle informazioni sugli atleti ed elaborazioni sulla base dei loro tempi.
Di seguito fornisco alcune opzioni, delle quali le prime due sono volutamente inadeguate. (Ma che evidenziano l'importanza di fornire un'appropriata rappresentazione dei dati.)
2.1 Opzione 1: nessun modello
È la scelta più semplice: ogni atleta è rappresentato da una stringa che memorizza la riga corrispondente del file.
class Program
{ static void Main(string[] args)
{ string[] atleti = CaricaAtleti("Maratona.txt");
//... elabora i dati
}
static string[] CaricaAtleti(string nomeFile)
{ return File.ReadAllLines(nomeFile);
}
}
Il codice di caricamento è semplicissimo; d'altra parte, avere le informazioni nella forma:
" , , "
Tadesse Abraham Svizzera 2:11:42
rende complicata l'implementazione dei requisiti, poiché implica l'estrazione dei dati (nominativo, nazione e tempo) per ogni operazione da realizzare.
2.2 Opzione 2: utilizzare dei campi stringa
Ogni atleta è rappresentato da tre campi stringa; i dati degli atleti sono memorizzati in una matrice di tre colonne.
static string[,] CaricaAtleti(string nomeFile)
{ string[] righe = File.ReadAllLines(nomeFile);
string[,] atleti = new string[righe.Length, 3];
for (int i = 0; i < righe.Length; i++)
{ string[] campi = righe[i].Split(',');
atleti[i, 0] = campi[0].Trim();
atleti[i, 1] = campi[1].Trim();
atleti[i, 2] = campi[2].Trim();
}
return atleti;
}
TimeRange 5 di 30
I dati di un atleta sono memorizzati separatamente, ma la loro elaborazione non risulta molto più semplice rispetto a prima: ogni dato è individuato attraverso una coppia di coordinate nella matrice. Inoltre, i tempi sono memorizzati come stringhe e dunque non è possibile utilizzarli direttamente nelle operazioni necessarie a soddisfare i requisiti del problema.
2.3 Opzione 3: record
L'uso di un consente di rappresentare il singolo atleta in modo significativo:
record
class Atleta
{ public string Nominativo;
public string Nazione;
public string Tempo;
}
static Atleta[] CaricaAtleti(string nomeFile)
{ string[] righe = File.ReadAllLines(nomeFile);
Atleta[] atleti = new Atleta[righe.Length];
for (int i = 0; i < righe.Length; i++)
{ string[] campi = righe[i].Split(',');
atleti[i].Nominativo = campi[0].Trim();
atleti[i].Nazione = campi[1].Trim();
atleti[i].Tempo = campi[2].Trim();
}
return atleti;
}
Il tipo incapsula il concetto di atleta e consente di gestire gli atleti come oggetti del tipo
atleta
Atleta
appropriato, mantenendo la possibilità di processare i loro attributi: Nominativo, Nazione e Tempo.
Nominativo Nazione Tempo
Per quest'ultimo è tuttora utilizzato il tipo
string
; ciò non consente di eseguire direttamente operazioni sui tempi. Ad esempio, il tempo di:
"Tadesse Abraham, Svizzera, "
2:11:42
è maggiore del tempo di: "Eliud Kipchoge, Kenya, "
2:8:44
ma così non risulterebbe da un confronto tra stringhe, poiché la prima è minore della seconda.
Le opzioni considerate finora sollevano l’identica questione: la rappresentazione dei dati mediante dei tipi adeguati è fondamentale, poiché sono i tipi a stabilire i vincoli e le operazioni ammissibili su di essi. C# fornisce i tipi primitivi (int, char, string, double, etc), i quali definiscono una rappresentazione di interi, caratteri, stringhe, etc. In alcune situazioni sono sufficienti, in altre è necessario definire nuovi tipi:
- Perché non esiste un tipo che possa rappresentare il dato: vedi, ad esempio, i singoli atleti;
- Perché i tipi utilizzabili non forniscono le operazione richieste: vedi i tempi degli atleti.
TimeRange 6 di 30
2.4 Rappresentare il tempo degli atleti: intervallo di tempo
Elaborare il tempo degli atleti significa poter conoscere ore, minuti e secondi, confrontare due tempi, ottenere l'intervallo che intercorre tra due tempi. Un approccio è quello di rappresentare un singolo tempo mediante un record:
class TimeRange
{ public int Ore;
public int Minuti;
public int Secondi;
}
...
class Atleta
{ public string Nominativo;
public string Nazione;
public TimeRange Tempo;
}
2.4.1 Implementare le operazioni sui tempi
TimeRange si limita a memorizzare i dati; al codice applicativo va la responsabilità di implementare le operazioni necessarie:
static TimeRange Parse(string tempo) // ottiene un tempo da una stringa
{ string[] campi = tempo.Split(':');
var t = new TimeRange();
t.Ore = int.Parse(campi[0]);
t.Minuti = int.Parse(campi[1]);
t.Secondi = int.Parse(campi[2]);
return tr;
}
static string ToString(TimeRange t) // converte un tempo in stringa
{ string segno = (t.Ore < 0 || t.Minuti < 0 || t.Secondi < 0) ? "-" : "";
return string.Format("{0}{1}:{2}:{3}", segno,
Math.Abs(t.Ore), Math.Abs(t.Minuti), Math.Abs(t.Secondi));
}
static int TempoTotale(TimeRange t) // ottiene i secondi totali del tempo
{ return t.Ore * 3600 + t.Minuti * 60 + t.Secondi;
}
static TimeRange Intervallo(TimeRange t1, TimeRange t2) // ottiene l'intervallo di tempo
{ // che intercorre tra due tempi
var tr = new TimeRange();
int scarto = TempoTotale(t1) - TempoTotale(t2);
tr.Ore = scarto / 3600 % 3600;
tr.Minuti = scarto / 60 % 60;
TimeRange 7 di 30
tr.Secondi = scarto % 60;
return tr;
}
static int Confronta(TimeRange t1, TimeRange t2) // confronta due tempi e restituisce:
{ // -1, 0, 1 in base al fatto che il primo
int sect1 = TempoTotale(t1); // sia minore, uguale, maggiore del secondo
int sect2 = TempoTotale(t2);
if (sect1 > sect2)
return 1;
if (sect1 < sect2)
return -1;
return 0;
}
2.5 Implementazione dei requisiti
Sulla scorta dei metodi precedenti, si possono implementare i requisiti:
static void Main(string[] args)
{ Atleta[] atleti = CaricaAtleti("Maratona.txt");
Ordina(atleti);
Visualizza(atleti);
Atleta primo = atleti[0];
Atleta ultimo = atleti[atleti.Length - 1];
TimeRange scarto = Intervallo(ultimo.Tempo, primo.Tempo);
Console.WriteLine("\nTempo fra ultimo e primo = {0}", ToString(scarto));
}
static Atleta[] CaricaAtleti(string nomeFile)
{ string[] righe = File.ReadAllLines(nomeFile);
Atleta[] atleti = new Atleta[righe.Length];
for (int i = 0; i < righe.Length; i++)
{ string[] campi = righe[i].Split(',');
atleti[i] = new Atleta();
atleti[i].Nominativo = campi[0].Trim();
atleti[i].Nazione = campi[1].Trim();
atleti[i].Tempo = Parse(campi[2]);
}
return atleti;
}
static void Visualizza(Atleta[] atleti)
{ Console.WriteLine("{0,-25} {1,-15} {2}\n", "Nominativo", "Nazione",
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
TimeRange: case study sull'implementazione di un valore
Introduzione
In questo tutorial utilizzo un semplice problema di programmazione come mezzo per affrontare le problematiche connesse all'implementazione di un nuovo tipo di dato. L'obiettivo finale è quello di realizzare un tipo analogo a TimeSpan, il quale incapsula un intervallo di tempo. A partire dalla definizione del problema, procederò per gradi, utilizzando dapprima un approccio procedurale, per arrivare progressivamente a una corretta soluzione OO. Metterò in evidenza i limiti delle soluzioni adottate di volta in volta, mostrando come superarli e dimostrando che l'implementazione di TimeSpan (o tipo analogo) rappresenta il risultato ideale.
(Nota a margine: non realizzerò un'implementazione identica a quella di TimeSpan, ma una molto più semplice e che non prende in considerazione la gestione dell'overflow di valori interi)
Un approccio procedurale
Davanti a un problema simile la prima questione è: come rappresentare i dati. Naturalmente, il modello scelto deve avere la funzione di favorire l'implementazione delle richieste del problema: visualizzazione delle informazioni sugli atleti ed elaborazioni sulla base dei loro tempi.
Di seguito fornisco alcune opzioni, delle quali le prime due sono volutamente inadeguate. (Ma che evidenziano l'importanza di fornire un'appropriata rappresentazione dei dati.)