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.
vuoi
o PayPal
tutte le volte che vuoi
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 un corretta soluzione OO. Metterò in evidenza i limiti
delle soluzioni adottate di volta in volta, mostrando come superarli e dimostrando che l'imple-
mentazione 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 le 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.)
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 , il quale incapsula un intervallo di tempo.
TimeSpan
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'imple-
mentazione di (o tipo analogo) rappresenta il risultato ideale.
TimeSpan
(Nota a margine: non realizzerò un'implementazione identica a quella di , ma una molto
TimeSpan
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:
1. Visualizzare l'elenco in ordine di classifica (in base ai tempi).
2. 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 corri-
spondente 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 matri-
ce 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 e consente di gestire gli atleti come oggetti del tipo
atleta
Atleta
appropriato, mantenendo la possibilità di processare i loro attributi: , e .
Nominativo Nazione Tempo
Per quest'ultimo è tuttora utilizzato il tipo ; ciò non consente di eseguire direttamente
string
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
poiché sono i tipi a stabilire i vincoli e le operazioni
mediante dei tipi adeguati è fondamentale,
ammissibili su di essi. C# fornisce i tipi primitivi ( , , , , etc), i quali
int char string double
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
si limita a memorizzare i dati; al codice applicativo va la responsabilità di implementare
TimeRange
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", "Tempo (h/m/s)");
foreach (var a in atleti)
{
TimeRange 8 di 30
Console.WriteLine("{0,-25} {1,-15} {2}", a.Nominativo, a.Nazione,
ToString(a.Tempo));
}
}
static void Ordina(Atleta[] atleti)
{ for (int i = 0; i < atleti.Length-1; i++)
{ for (int j = i+1; j < atleti.Length; j++)
{ if (Confronta(atleti[i].Tempo, atleti[j].Tempo) > 0)
{ var temp = atleti[i];
atleti[i] = atleti[j];
atleti[j] = temp;
}
}
}
}
2.6 Conclusioni
L’insieme dei metodi , , e , fornisce un supporto
Parse() ToString() Confronta() Intervallo()
all’elaborazione dei tempi degli atleti; si tratta di una soluzione funzionale, ma non ottimale.
Infatti:
1. Non consente di processare i tempi come accade con gli altri valori. Vorremmo poter
scrivere:
if (atleti[i].Tempo > atleti[j].Tempo)
Oppure:
TimeRange scarto = ultimo – primo;
Ma non possiamo farlo, poiché è un semplice e non definisce alcuna
record
TimeRange
operazione.
2. Si tratta di una soluzione difficile da riutilizzare in altri scenari; occorre copiare e
TimeRange
i metodi che lo elaborano nei nuovi progetti. Non esiste, cioè, un componente da poter
facilmente riutilizzare.
3. Si tratta di una soluzione funzionale al problema corrente, ma probabilmente insufficiente
in altri scenari, nei quali potrebbe essere necessario considerare anche i giorni, oppure
sommare i tempi. In questo caso saremmo costretti ad aggiungere le operazioni
necessarie, col risultato di avere soluzioni simili, ma non identiche, al problema di
rappresentare lo stesso concetto: un intervallo di tempo.
TimeRange 9 di 30
3 Un approccio object oriented
La programmazione presuppone che i tipi incorporino i dati e le operazioni da
object oriented
eseguire su di essi. Non si tratta di un approccio sempre desiderabile; lo diventa soprattutto
quando il tipo rappresenta un concetto indipendente da specifiche applicazioni; il concetto di
(intervallo di) tempo rientra senz'altro in questa casistica. Naturalmente, applicazioni distinte
possono avere requisiti diversi. Alcune potrebbero dover misurare il tempo in giorni (un
programma che registra dei promemoria), altre in millisecondi (programmi di Alcune
benchmark).
devono confrontare dei tempi, in altre basta conoscere ore, minuti, secondi, etc. Proprio per
questo motivo è molto utile incorporare queste caratteristiche in un oggetto, in modo da poter:
semplificare la realizzazione di qualunque codice debba gestire dei tempi;
• riutilizzare lo stesso concetto in applicazioni diverse senza dover scrivere del codice ad
• hoc.
3.1 Oggetto TimeRange: dati + operazioni
Se ci si limita a considerare il “mantra” della OOP, combinare dati e operazioni nello stesso
(l'oggetto, appunto), la trasformazione da a è semplice: basta
contenitore, record oggetto
incorporare i metodi di elaborazione dei tempi nel tipo :
TimeRange
class TimeRange
{ public int Ore;
public int Minuti;
public int Secondi;
//static TimeRange Parse (string tempo) {...}
public void Parse(string tempo)
{ string[] campi = tempo.Split(':');
Ore = int.Parse(campi[0]);
Minuti = int.Parse(campi[1]);
Secondi = int.Parse(campi[2]);
}
//static int Confronta(TimeRange t1, TimeRange t2) {...}
public int Confronta(TimeRange t)
{ int sect1 = TempoTotale(this); //-> calcola i secondi di "se stesso"
int sect2 = TempoTotale(t);
if (sect1 > sect2)
return 1;
if (sect1 < sect2)
return -1;
return 0;
}
//static TimeRange Intervallo(TimeRange t1, TimeRange t2) {...}
public TimeRange Intervallo(TimeRange t)
TimeRange 10 di 30
{ int scarto = TempoTotale(this) - TempoTotale(t);
var tr = new TimeRange();
tr.Ore = scarto / 3600 % 3600;
tr.Minuti = scarto / 60 % 60;
tr.Secondi = scarto % 60;
return tr;
}
//static string ToString(TimeRange t) {...}
public string ToString()