universita` degli studi di urbino - Istituto di Scienze e Tecnologie dell
Transcript
universita` degli studi di urbino - Istituto di Scienze e Tecnologie dell
UNIVERSITA’ DEGLI STUDI DI URBINO CORSO DI LAUREA IN INFORMATICA APPLICATA RELAZIONE DEL PROGETTO DI LABORATORIO Implementazione di un programma per la gestione di un distributore automatico per il noleggio di dvd per l’esame di INGEGNERIA DEL SOFTWARE Professore Tommaso Pagnini Nome: Togni Gabriele Matricola: 200857 Nome: La Gatta Francesco Matricola: 201003 SPECIFICA DEL PROBLEMA Il programma deve gestire un distributore automatico di dvd. Il distributore dovrà gestire il noleggio e la riconsegna degli articoli già presenti in archivio da parte di utenti anch’essi già registrati precedentemente. Tutte le informazioni costituenti sia l’archivio degli articoli sia quello dei clienti sono da considerare precedentemente memorizzate su file. Ogni cliente, di cui sono memorizzati anche nome e cognome, possiede un credito che va gestito dal programma e che viene usato per il pagamento dei noleggi. Ciascun utente viene identificato da un codice (che sostituisce la reale tessera magnetica di un qualsiasi distributore) a cui viene associato un codice segreto (pin) da utilizzare per l’accesso al sistema. Per quel che riguarda gli articoli presenti in archivio si specifica che questi sono suddivisi in tre principali categorie: film, documentari e concerti. Ciascuna categoria è sua volta divisa in generi: • Film: drammatico, azione, thriller, drammatico, commedia… • Documentari: scienza, natura, storia… • Concerti: rock, pop, reggae, soul… Inoltre per quanto riguarda gli articoli si tenga conto che quelli presenti in archivio da non più di tre mesi sono etichettati come ‘novità’ indipendentemente sia dalla categoria che dal genere. Per quel che riguarda la gestione del credito si specifica il seguente listino dei prezzi: • Riconsegna entro 12 ore: 2 • Riconsegna entro 1 giorno: 3 • Ogni giorno successivo: 2 Si consideri che un cliente può noleggiare un articolo anche se ne deve ancora restituire degli altri. Il credito minimo per effettuare un noleggio è pari a 2 per ogni articolo che si vuol noleggiare. Si consideri il programma come una macchina a stati operante secondo il seguente schema: Specifiche dei vari stadi: • ‘Idle’: è lo stato di stanby del sistema in cui si resta in attesa dell’arrivo di un cliente che dovrà digitare il proprio codice identificativo (questo nel caso reale corrisponderebbe all’inserimento della tessera magnetica) accedendo così allo stato successivo. • ‘Riconoscimento’: è lo stato in cui si chiede all’utente identificato il proprio codice segreto che permette l’effettivo accesso al sistema in caso di inserimento corretto o il ritorno allo stato ‘idle’ in caso di inserimento errato. • ‘Scelta’: è uno stato che costituisce il menù principale dell’applicazione e da cui è possibile accedere alle tre opzioni principali o tornare allo stato ‘idle’: • ‘Gestione credito’: in questo stato viene visualizzato il credito residuo; si può quindi scegliere di ricaricarlo o ritornare allo stato ‘scelta’. Nella fase di ricarica, nel caso reale il sistema rimarrebbe in attesa dell’inserimento di una o più banconote; nel nostro caso questa operazione si compierà digitando l’importo desiderato e verrà visualizzato il nuovo credito. • ‘Riconsegna’: in questo stato si resta in attesa dell’inserimento del codice dell’articolo da restituire; nel caso reale questa operazione corrisponderebbe all’attesa dell’inserimento nel distributore dell’articolo noleggiato, è comunque possibile ritornare allo stato ‘scelta’. La riconsegna di un articolo coincide con la detrazione del prezzo del noleggio. Qualora l’utente non avesse un credito sufficiente si passa automaticamente allo stato ‘gestione credito’. Se l’operazione va a buon fine si passa allo stato di ‘restituzione’. • ‘Restituzione’: in questa fase viene aggiornato il sistema (sia per quel che riguarda la disponibilità dell’articolo restituito sia per quel che riguarda l’utente). Da questo stato si ritorna automaticamente allo stato ‘scelta’. • ‘Noleggio’: in questo stato l’utente sceglie l’articolo da noleggiare sulla base di varie tecniche di visualizzazione degli articoli disponibili. In particolare potranno essere visualizzati tutti gli articoli di una categoria (film, documentari, concerti), tutti gli articoli relativi ad un determinato genere oppure tutte le novità. È comunque possibile ritornare allo stato ‘scelta’. La scelta definitiva dell’articolo da accesso allo stato di ‘erogazione’ qualora l’utente abbia il credito necessario; viceversa si ritorna allo stato ‘scelta’. • ‘Erogazione’: in questa fase viene aggiornato il sistema (sia per quel che riguarda la disponibilità dell’articolo noleggiato sia per quel che riguarda l’utente, in particolare una parte del prezzo del noleggio, pari a 2 , viene detratta immediatamente). Da questo stato si ritorna automaticamente allo stato ‘scelta’. SPECIFICA DEI REQUISITI Diagramma degli Use Cases: !! %% 1 0,0/ <()** %% 1 0,0/ <()** !! %% 1 0,0/ <()** " ! " ! #$ !! %% ' &( ()** + ,(- . -/ / ,0. 0)1 -32 !! " ! 45 6 7 8 !9 45 6 ! 7 45 6 8: ; %% 3 &( 0)** + ,0-' . -/ / ,0. )1 -'2 Use Case: Identificazione Attori: Utente Punti di estensione: Precondizioni: 1. L’utente non ha inserito il proprio codice cliente. Corso d’azione base: 1. L’utente inserisce il proprio codice cliente 2. Viene chiesto all’utente di inserire il proprio PIN 3. L’utente inserisce il PIN 4. In caso di inserimento corretto vengono visualizzati nome e cognome dell’utente 5. Si passa al menù principale Postcondizioni: 1. L’utente è stato identificato dal sistema. Corso d’azione alternativo: In caso di inserimento errato si ritorna al punto “1” del corso d’azione base Use Case: Gestione Credito Attori: Utente Punti di estensione: Precondizioni: 1. L’utente è stato identificato dal sistema. Corso d’azione base: 1. Viene visualizzato il credito dell’utente. 2. Viene visualizzato un menù che prevede 2 scelte: “1” Ricarica credito, “2” Ritorno al menù principale. 3. Se l’utente digita “2” si esce dallo use case. 4. Se l’utente digita “1” vengono visualizzati gli importi corrispondenti alle banconote accettate (5, 10 e 20 ). 5. L’utente digita l’importo della ricarica (0 per tornare al punto ”1” del corso d’azione base). 6. Viene aggiornato il credito dell’utente(credito massimo consentito 999 ). 7. Si torna al punto “4” del corso d’azione base. Postcondizioni: Corso d’azione alternativo: In caso di inserimento errato si ritorna al punto precedente del corso d’azione base. Use Case: Noleggio Attori: Utente Punti di estensione: 1. L’utente deve avere almeno 2 per noleggiare un articolo Precondizioni: 1. L’utente è stato identificato dal sistema. Corso d’azione base: 1. Viene visualizzato un menù che prevede 4 scelte: “1” Visualizza per categoria, “2” Visualizza per genere, “3” Visualizza novità, “4” Ritorno al menù. 2. L’utente sceglie una delle 4 opzioni. 3. Se l’utente digita “4” si esce dallo use case. 4. Vengono visualizzati gli articoli disponibili sulla base della scelta effettuata. 5. L’utente sceglie l’articolo da prenotare inserendo il relativo codice (0 per tornare alle opzioni di visualizzazione). 6. Viene visualizzata una scheda contenente tutte le informazioni sull’articolo 7. L’utente scegli se confermare la prenotazione o tornare al menù precedente 8. Viene verificato che l’utente abbia almeno 2 sul proprio conto. 9. In caso positivo vengono detratti i 2 . 10. Il sistema decrementa di una unità la disponibilità dell’articolo. Postcondizioni: Corso d’azione alternativo: In caso di inserimento errato si ritorna al punto precedente del corso d’azione base. In caso di credito insufficiente viene comunicato all’utente un messaggio d’errore e si ritorna al menù di visualizzazione scelto. Use Case: Riconsegna Attori: Utente Punti di estensione: 1. L’utente deve avere almeno un credito pari al prezzo del noleggio Precondizioni: 1. L’utente è stato identificato dal sistema. Corso d’azione base: 1. Viene chiesto all’utente il codice dell’articolo da restituire (0 per uscire dallo use case) 2. Viene calcolato il prezzo del noleggio 3. Se il credito dell’utente è sufficiente viene detratto il prezzo del noleggio dal credito. 4. Viene incrementata di una unità la disponibilità dell’articolo. Postcondizioni: Corso d’azione alternativo: Se il credito dell’utente è insufficiente si passa allo use case “Gestione Credito”. Use Case: Uscita Attori: Utente Punti di estensione: Precondizioni: 1. L’utente è stato identificato dal sistema. Corso d’azione base: 1. Il sistema torna nello stato di attesa dell’identificazione di un utente. Postcondizioni: Corso d’azione alternativo: 4 ^ # "# # '( " $$ % a B # # & &# '( b( D( # Y" #B & &# % B & &# $ #$ &#% Y & ) ( D( c '( _` _` % +( & $$% , # '( ) ( # !" !* # -. d e 4 ^ 8 9 ;< 8:7 89 < @= ? 7> = 6 7 56 2 /01 -. 4 3 4 ^ # ( B ( # X# VW 3 e 4 ff _ d STU B &# YZ (( \]% $ WX ( \ B[ ( IK L QFR N LM F K IK JF PO N (%C GH EF B& % &% & % &% & ( D( ( D( ( A # '( " #B 3 ANALISI E PROGETTAZIONE Diagramma degli stati dell’intera applicazione: Questo diagramma descrive il funzionamento completo dell’applicazione immaginando di navigare tutti i menù del distributore di dvd che vogliamo implementare. Decidiamo di implementare solo il lato utente dell’applicazione, quindi dobbiamo immaginare il nostro distributore perennemente acceso(solo per comodità prevediamo di “spegnere” il sistema nel caso in cui al posto del codice cliente venga inserita la parola “stop”). A questo punto risulta immediatamente chiaro il motivo per cui non si parlerà mai ne di come accendere o spegnere il distributore, ne di come inserire o rimuovere gli articoli o i clienti dal database del sistema. Il database sopra citato è costituito da alcuni file di testo contenenti le informazioni riguardanti i clienti, gli articoli e i noleggi: - per i clienti(Clienti.txt) memorizziamo Codice_cliente, Pin, Nome, Cognome, Credito; - per i noleggi(Noleggi.txt) memorizziamo Istante_riconsegna(scritto ovviamente solo dopo l’avvenuta restituzione), Codice_cliente, Codice_articolo, Istante_noleggio; Per quanto riguarda gli articoli abbiamo già detto che si suddividono in 3 categorie(Film, Concerti, Documentari) che hanno alcune informazioni in comune; tuttavia decidiamo di memorizzare tutte le informazioni riguardanti il concetto di Articolo in 3 file separati(Film.txt, Concerti.txt, Documentari.txt): - per i film(Film.txt) memorizziamo Codice_articolo, Titolo, Data_arrivo, Anno_uscita, Genere, Copie_disponibili, Regista, Attore_protagonista; - per i concerti(Concerti.txt) memorizziamo Codice_articolo, Titolo, Data_arrivo, Anno_uscita, Genere, Copie_disponibili, Cantante, Luogo; - per i documentari(Documentari.txt) memorizziamo Codice_articolo, Titolo, Data_arrivo, Anno_uscita, Genere, Copie_disponibili, Luogo, Breve_descrizione; Per rendere più efficiente la visualizzazione degli articoli in base al loro genere abbiamo deciso di memorizzare in 3 file separati(Generi_film.txt, Generi_concerti.txt, Generi_documentari.txt) tutti i generi degli articoli presenti in archivio suddivisi in base alla categoria. Anche se la nostra applicazione implementa solo il lato utente, è chiaro che le operazioni di amministrazione del sistema(aggiunta e rimozione dei clienti e degli articoli) possono essere facilmente effettuate editando manualmente i files di testo. Come già detto l’applicazione risulta essere sempre in esecuzione, quindi un qualsiasi utente deve come prima operazione, inserire direttamente il proprio Codice_cliente; dopo l’inserimento l’applicazione confronta il codice inserito con tutti quelli presenti in archivio ed in caso di successo chiede all’utente il proprio Pin confrontandolo con quello memorizzato in archivio. Se anche questo confronto va a buon fine il cliente accede di fatto all’applicazione e verranno quindi memorizzati tutti i sui dati. A questo punto si visualizzano nome e cognome dell’utente riconosciuto il quale si trova mediante un menù a dover scegliere tra 4 opzioni(Gestione credito, Noleggio, Restituzione e Uscita): - Se viene scelta l’opzione Gestione credito l’applicazione visualizza il credito attuale dell’utente e chiede allo stesso se vuole effettuare una ricarica; in caso affermativo si esegue l’operazione di ricarica e si torna alla fase di visualizzazione del credito mentre nel caso in cui non si voglia effettuare la ricarica si ritorna al menù. - Se viene scelta l’opzione Noleggio il cliente sceglie il tipo di visualizzazione degli articoli(verranno sempre visualizzati solo gli articoli con disponibilità maggiore o uguale ad 1): nella visualizzazione per categoria, una volta scelta una delle 3 categorie, si accede al corrispondente file visualizzando Codice e Titolo di ogni articolo presente in archivio. Nella visualizzazione delle novità si accede a tutti e 3 i file contenenti gli articoli visualizzando tutti quelli presenti in archivio da non più di 3 mesi. Infine nella visualizzazione per genere verranno visualizzati tutti i diversi generi presenti in archivio suddivisi in base alla categoria di appartenenza(verranno quindi letti i 3 file Generi_film.txt, Generi_concerti.txt, Generi_documentari.txt), una volta scelto un genere verranno visualizzati all’utente tutti gli - - articoli (Codice e Titolo) che vi appartengono. Comune a tutte e 3 le visualizzazioni è la fase di scelta dell’articolo da noleggiare, fase che può essere vista come una prenotazione dell’articolo durante la quale vengono visualizzate tutte le informazioni presenti su quel articolo. Se l’utente sceglie di ritirare l’articolo verrà effettuato un controllo che permetterà l’effettivo noleggio solo se l’utente possiede almeno 2 che in caso positivo verranno immediatamente detratti insieme al decremento della disponibilità dell’articolo, in caso negativo sarà comunicata all’utente l’insufficienza del suo credito ma saranno comunque concessi tutti i tipi di visualizzazione. Il noleggio si conclude con la scrittura sul file Noleggi.txt delle informazioni sull’operazione appena eseguita(fatta ovviamente eccezione per il campo Istante_riconsegna sostituito momentaneamente da una stringa del tipo GG/MM/AAAA HH:MM:SS) Se viene scelta l’opzione Restituzione il cliente deve inserire il codice dell’articolo che vuole restituire, verrà quindi cercata nel file Noleggi.txt la coppia di informazioni Codice_cliente, Codice_articolo corrispondente ed in cui manca l’istante della restituzione(questo specifica che un utente può ovviamente noleggiare più volte lo stesso articolo). Se l’articolo risulta effettivamente da riconsegnare viene calcolato il prezzo del noleggio. Se questo risulta inferiore a 2 l’operazione risulta completata aggiornando la disponibilità dell’articolo,se invece risulta superiore a 2 viene confrontato con il credito residuo del cliente. A questo punto per completare la riconsegna l’utente è obbligato a ricaricare il suo credito qualora risultasse insufficiente e ripetere l’operazione di riconsegna, mentre in caso contrario l’operazione si conclude con successo aggiornando la disponibilità dell’articolo. In ogni caso la restituzione si conclude aggiornando l’archivio dei noleggi con l’istante della riconsegna. Se viene scelta l’opzione Uscita si ritorna allo stato “Idle” in cui si resta in attesa di un nuovo cliente da identificare. Diagramma delle classi: or Classe “Clienti”: è la classe che contiene le informazioni del cliente che accede al sistema. I suoi attributi corrispondono quindi alle informazioni che abbiamo memorizzato su un file nella forma: Clienti.txt Codice_cliente | Pin | Nome | Cognome $ Credito Codice cliente e pin sono dichiarati come string ma per convenzione stabiliamo che questi siano formati da 5 caratteri decimali; nome e cognome sono delle normali string ed infine credito è un int. Nel file è contraddistinto da un segnaposto speciale in modo da poter facilmente modificare solo questo campo, inoltre al credito assegniamo sempre uno spazio di 3 cifre decimali(credito massimo 999 ) per non creare problemi di sovrapposizione tra le righe del file. Tutti i 5 attributi sono privati ed a ciascuno di essi corrisponde quindi un metodo pubblico chiamato GetAttributo (es GetCredito) usato per leggere il valore dello stesso anche dalle altre classi. La classe possiede un altro metodo pubblico AggCredito usato sia per incrementare(nella fase di ricarica) che per decrementare(durante le operazioni di noleggio/restituzione) il credito dell’utente. Il valore degli attributi è assegnato dal costruttore al momento della creazione dell’oggetto di tipo Clienti sulla base della stringa corrispondente letta dal file di testo Clienti.txt. Il distruttore implementa la fase di aggiornamento dei dati del cliente in base all’attributo booleano Modificato che rivela se è necessaria una riscrittura della riga. Classe “Articoli”: è la classe base che contiene le informazioni comuni a tutti gli articoli che da luogo ad una generalizzazione nelle 3 classi derivate “Film”, “Concerti” , “Documentari”. Per la memorizzazione degli attributi degli articoli esistono 3 file separati corrispondenti alle 3 classi derivate(che includono quindi anche gli attributi della classe base): Film.txt Codice_articolo | Titolo | Data_arrivo | Anno_uscita | Genere $ Copie_disponibili | Regista | Attore_protagonista Concerti.txt Codice_articolo | Titolo | Data_arrivo | Anno_uscita | Genere $ Copie_disponibili | Cantante | Luogo Documentari.txt Codice_articolo | Titolo | Data_arrivo | Anno_uscita | Genere $ Copie_disponibili | Luogo_registrazione | Breve_descrizione Va specificato che il codice articolo anche se dichiarato come string è formato da un carattere minuscolo nella prima posizione(che ne identifica la categoria di appartenenza) seguito da 4 cifre decimali(es. f0023 identifica un film, c0012 identifica un concerto …). Anche in questo caso il segnaposto speciale è usato per individuare facilmente l’unico campo che va aggiornato, ed anche in questo caso lasciamo uno spazio fisso di due caratteri per non creare problemi di sovrascrittura. Tutti gli attributi della classe base sono privati e ognuno ha il corrispondente metodo GetAttributo (GetAttributo, es. GetTitolo) per leggere il valore anche dalla classe Distributore. Gli attributi delle classi derivate sono tutti privati, anch’essi con il relativo metodo per la lettura. Siccome gli attributi delle classi derivate devono essere accessibili dalla classe base, abbiamo dotato tale classe di due metodi virtuali puri implementati diversamente in ciascuna delle classi derivate. Inoltre la classe base possiede un metodo pubblico AggCopie_disponibili usato per decrementare e incrementare il numero delle copie disponibili al momento del noleggio e della restituzione. Il valore di tutti gli attributi è assegnato anche in questo caso dai costruttori delle 3 classi derivate che si differenziano per l’accesso a file diversi. In particolare specifichiamo che il costruttore della classe base, invocato esplicitamente dai costruttori delle classi derivate, inizializza tutti i suoi attributi mentre i due attributi che differenziano le classi derivate sono inizializzati dai relativi costruttori. Classe “StateMgr”: è la classe utilizzata per controllare i cambi di stato del nostro sistema(Distributore). Questa classe ha un solo attributo privato che corrisponde allo stato corrente (CurrState di tipo State da noi definito come enum) in cui si trova il nostro sistema ed il cui valore può essere letto con il metodo pubblico GetCurrState. Questa classe è dotata di altri due metodi: uno pubblico (SetNewState) che implementa il passaggio da uno stato all’altro del sistema controllando che esso sia consentito, ed uno privato (PrintCurrState) che stampa a video il nuovo stato dopo ogni transizione. Classe “Distributore”: è la classe centrale del nostro sistema. Avendo implementato solo il lato utente del distributore possiamo assumere che vi sia istanziato sempre uno ed un solo oggetto di questa classe. Questa classe possiede 3 attributi privati che sono: - Utente, è un puntatore ad un oggetto della classe Clienti che rappresenta l’utente acceduto al sistema. La cardinalità della relazione tra la classe Distributore e la classe Clienti (0..1) esplicita che il nostro sistema ammette al massimo la presenza di un utente alla volta. Alla creazione dell’oggetto della classe distributore questo puntatore verrà settato a NULL, successivamente, dopo l’identificazione dell’utente verrà effettivamente allocata l’istanza del cliente. Questa sarà poi distrutta al momento dell’uscita del cliente dal menù principale. - Sm, è un oggetto della classe StateMgr. La cardinalità della relazione di aggregazione(1..1) ci indica che è sempre presente uno ed un solo oggetto della classe StateMgr istanziato al momento della creazione dell’oggetto della classe Distributore. - Dvd, è un vettore di puntatori ad oggetti della classe Articoli usato per permettere la creazione di un contenitore dinamico di oggetti. La cardinalità di questa relazione(0..*) esplicita che nel sistema possono essere istanziati da 0 a più oggetti della classe articoli; questo perché nelle fasi di visualizzazione preleviamo dall’archivio tutti gli articoli desiderati. La scelta di utilizzare un vettore di puntatori associata al design pattern “factory” ci permette di inserire nel vettore puntatori ad oggetti differenti, in particolare il vettore durante le visualizzazioni verrà dinamicamente riempito di puntatori ad oggetti delle classi derivate Film, Concerti e Documentari. L’unico metodo pubblico della classe(oltre al costruttore e al distruttore) è il metodo Idle il quale, invocato dal costruttore della classe, rappresenta lo stato iniziale del sistema. Questo metodo si occupa dell’acquisizione del codice del cliente e della chiamata al metodo Riconoscimento che verifica l’esistenza in archivio del cliente corrispondente al codice immesso e chiede il relativo Pin. Se anche questo risulta corretto viene istanziato l’oggetto della classe Clienti e passa il controllo al metodo Scelta. Questo metodo rappresenta il menù principale dell’applicazione, e sulla base della scelta effettuata dall’utente invoca il relativo metodo. Il metodo Gestione_credito visualizza il credito residuo dell’utente e permette ad esso di effettuare l’operazione di ricarica. Il metodo Noleggio rappresenta un menù che permette all’utente di scegliere il tipo di visualizzazione tra i 3 disponibili cui corrispondono altrettanti metodi: - Visual_categoria visualizza tutti gli articoli disponibili di una determinata categoria scelta tramite un altro menù e permette all’utente di selezionarne uno in particolare; - Visual_genere visualizza tutti gli articoli disponibili di un determinato genere scelto tramite un altro menù creato dinamicamente sulla base dei file dei generi delle tre categorie, quindi permette all’utente di selezionarne un particolare articolo; - Visual_novita visualizza tutti gli articoli disponibili presenti in archivio da non più di tre mesi(confrontando la data odierna con quella d’arrivo letta dal file) e permette all’utente di selezionarne uno in particolare; Tutti e tre i metodi, come già detto, permettono di selezionare un articolo sulla base del suo codice, il sistema visualizzerà quindi una scheda contenente tutte le informazioni su quell’articolo e chiederà se lo si vuole noleggiare (metodo Visualizza_scheda). In caso affermativo sarà invocato il metodo Ritira il quale dopo aver controllato l’effettiva disponibilità dell’articolo e che l’utente abbia almeno i 2 necessari, aprirà l’archivio degli articoli e vi inserirà le informazioni relative a quel noleggio. Questo metodo aggiorna inoltre il campo delle copie disponibili dell’articolo noleggiato. Il metodo Restituzione acquisisce il codice dell’articolo che si vuole restituire e cerca una corrispondenza codice cliente / codice articolo nell’archivio dei noleggi considerando solo le righe relative agli articoli non restituiti. Una volta trovata questa corrispondenza viene calcolato il prezzo del noleggio e verificata la disponibilità di credito. Qualora questa risulti insufficiente l’operazione di riconsegna viene interrotta invocando il metodo Gestione_credito, viceversa, se l’utente ha un già detratti e viene credito sufficiente, viene detratto il prezzo del noleggio a meno dei 2 aggiornato l’archivio inserendo nei rispettivi file la data ed ora di riconsegna e la nuova disponibilità dell’articolo. Infine il metodo Uscita non fa altro che invocare il distruttore della classe Clienti il quale apporta le modifiche all’archivio dei clienti solo se necessario. Classe “MyException”: è la classe che permette di gestire gli errori che possono verificarsi durante le operazioni di di accesso ai files. In particolare in caso di errore il programma stampa a video il tipo di errore, e se possibile anche lo specifico file che ha generato l’errore, terminando con un’eccezione. Libreria “TimeMgr”: questa libreria è utilizzata per gestire il tempo(data ed ora di noleggi e restituzioni). Essa è composta da tre funzioni che implementano tutte le operazioni che dobbiamo eseguire per la gestione del tempo, sia sotto forma di stringa(scrittura/lettura su file) che sotto forma di variabile del tipo predefinito tm (libreria <ctime> con relative funzioni). - GetCurrTime: questa funzione si preoccupa di prendere l’ora di sistema e restituirla sotto forma di stringa nel formato GG/MM/AAAA HH:MM:SS. - Converti: questa funzione prende come parametro una stringa contenente data e ora nel formato sopra citato e restituisce una variabile del tipo tm contenente le stesse informazioni. - OreNoleggio: questa funzione prende come parametri due stringhe contenenti data e ora nel formato sopra citato e restituisce la differenza in ore tra queste due. IMPLEMENTAZIONE File Videonolo.cpp #include "Distributore.h" int main(void) { Distributore d1; return 0; } //Il nostro sistema è da immaginare SEMPRE funzionante, quindi l'unica cosa che fa' il main //è istanziare un oggetto della classe distributore. //Per comodità il sistema è dotato di una possibilità di uscita: basta inserire la parola "stop" //al posto del codice cliente File Articoli.h //dichiarazione della classe articoli #ifndef ARTICOLI_H #define ARTICOLI_H #include <iostream> #include <ctime> using namespace std; //classe articoli class Articoli { string Codice_articolo; string Titolo; int Anno_uscita; string Data_arrivo; int Copie_disponibili; string Genere; public: Articoli(string DatiArticoli, int& i, int& j); virtual ~Articoli() {} Articoli* factory(char scelta, string Datidvd, int& i, int& j); string GetCodice_articolo() const { return Codice_articolo; } string GetTitolo() const { return Titolo; } int GetAnno_uscita() const { return Anno_uscita; } tm GetData_arrivo() const; int GetCopie_disponibili() const { return Copie_disponibili; } string GetGenere() const { return Genere; } //metodi per la lettura degli attributi bool AggCopie_disponibili(bool Aggiornamento); //metodo per aggiornare il num delle copie disponibili //funzioni virtuali pure! =0 virtual string GetInfo_specifica1() const = 0; //ciascuna delle classi derivate possiede due attibuti virtual string GetInfo_specifica2() const = 0; //privati contenenti le informazione specifiche di quella categoria }; //classe derivata film class Film: public Articoli { string Regista; string Attore_protagonista; public: Film(string DatiFilm, int& i, int& j); string GetInfo_specifica1() const { return Regista; } string GetInfo_specifica2() const { return Attore_protagonista; } }; //classe derivata concerti class Concerti: public Articoli { string Cantante; string Luogo; public: Concerti(string DatiConcerto, int& i, int& j); string GetInfo_specifica1() const { return Cantante; } string GetInfo_specifica2() const { return Luogo; } }; //classe derivata documentari class Documentari: public Articoli { string Luogo_registrazione; string Breve_descrizione; public: Documentari(string DatiDocumentario, int& i, int& j); string GetInfo_specifica1() const { return Luogo_registrazione; } string GetInfo_specifica2() const { return Breve_descrizione; } }; #endif File Articoli.cpp #include "Articoli.h" //costruttore articoli Articoli::Articoli(string DatiArticoli, int& i, int& j) { i = DatiArticoli.find('|'); j = 0; //indici temporanei per segnaposto nella stringa Codice_articolo = DatiArticoli.substr(j, i-1); //prendo i-1 caratteri di stringa dall'inizio (escludo anche lo spazio) j = DatiArticoli.find('|', i+2); i += 2; //fisso i al primo carattere del Titolo Titolo = DatiArticoli.substr(i, (j - 1) - i); i = DatiArticoli.find('|', j+2); j += 2; //fisso j al primo carattere della data di arrivo Data_arrivo = DatiArticoli.substr(j, (i - 1) - j); j = DatiArticoli.find('|', i+2); i += 2; //fisso i al primo carattere dell'anno di uscita Anno_uscita = atoi(DatiArticoli.substr(i, (j - 1) - i).c_str()); i = DatiArticoli.find('$'); j += 2; //fisso j al primo carattere del Genere Genere = DatiArticoli.substr(j, (i - 1) - j); j = DatiArticoli.find('|', i+2); i += 2; //fisso i al primo carattere delle Copie disponibili Copie_disponibili = atoi(DatiArticoli.substr(i, (j - 1) - i).c_str()); } //Fino a Copie_disponibili i 3 file sono identici(stessi dati per le 3 categorie) //////////////////////////////////////////////////////////////////////////////// //metodo factory Articoli* Articoli::factory(char scelta, string Datidvd, int& i, int& j) { if (scelta == '1') return new Film(Datidvd, i, j); else if (scelta == '2') return new Concerti(Datidvd, i ,j); else if (scelta == '3') return new Documentari(Datidvd, i, j); else return NULL; } //in base a scelta istanzio un oggetto diverso(film, concerto o documentario) //////////////////////////////////////////////////////////////////////////////// //metodo per incrementare o decrementare le copie disponibili bool Articoli::AggCopie_disponibili(bool Aggiornamento) { bool AggOk = true; if(Aggiornamento) ++Copie_disponibili; //se agg = 1 viene incrementato il numero di copie disponibili (riconsegna) else { if(Copie_disponibili) --Copie_disponibili; //se agg = 0 viene decrementato il numero di copie disponibili (noleggio) else AggOk = false; } return AggOk; //si ritorna false solo se si cerca di noleggiare un articolo con disponibilità nulla } //////////////////////////////////////////////////////////////////////////////// //metodo che legge la stringa della data di arrivo (GG/MM/AAAA) e costruisce una variabile di tipo tm tm Articoli::GetData_arrivo() const { tm temp; temp.tm_mday = atoi((Data_arrivo.substr(0,2)).c_str()); temp.tm_mon = atoi((Data_arrivo.substr(3,2)).c_str())-1; temp.tm_year = atoi((Data_arrivo.substr(6,4)).c_str())-1900; temp.tm_hour = 0; temp.tm_min = 0; temp.tm_sec = 0; //supponiamo che gli articoli siano arrivati alle ore 00:00:00 indipendentemente dal giorno temp.tm_isdst = -1; return temp; } //////////////////////////////////////////////////////////////////////////////// //classe derivata Film Film::Film(string DatiFilm, int& i, int& j) : Articoli(DatiFilm, i, j) { i = DatiFilm.find('|', j+2); j += 2; //fisso j al primo carattere del Regista Regista = DatiFilm.substr(j, (i - 1) - j); j = DatiFilm.find('\n', i+2); i += 2; //fisso i al primo carattere dell'attore protagonista Attore_protagonista = DatiFilm.substr(i, (j - 1) - i); } //Film.txt //Codice_articolo | Titolo | Data_arrivo | Anno_uscita | Genere | Copie_disponibili Attore_protagonista //////////////////////////////////////////////////////////////////////////////// | Regista | //classe derivata concerti Concerti::Concerti(string DatiConcerto, int& i, int& j) : Articoli(DatiConcerto, i, j) { i = DatiConcerto.find('|', j+2); j += 2; //fisso j al primo carattere del Cantante Cantante = DatiConcerto.substr(j, (i - 1) - j); j = DatiConcerto.find('\n', i+2); i += 2; //fisso i al primo carattere del luogo Luogo = DatiConcerto.substr(i, (j - 1) - i); } //Concerti.txt //Codice_articolo | Titolo | Data_arrivo | Anno_uscita | Genere | Copie_disponibili //////////////////////////////////////////////////////////////////////////////// | Cantante | Luogo //classe derivata documentari Documentari::Documentari(string DatiDocumentario, int& i, int& j) : Articoli(DatiDocumentario, i, j) { i = DatiDocumentario.find('|', j+2); j += 2; //fisso j al primo carattere del Cantante Luogo_registrazione = DatiDocumentario.substr(j, (i - 1) - j); j = DatiDocumentario.find('\n', i+2); i += 2; //fisso i al primo carattere del luogo Breve_descrizione = DatiDocumentario.substr(i, (j - 1) - i); } //Documentari.txt //Codice_articolo | Titolo | Data_arrivo | Anno_uscita | Genere | Copie_disponibili Breve descrizione //////////////////////////////////////////////////////////////////////////////// | Luogo di registrazione | File Clienti.h //Dichiarazione della classe Clienti #ifndef CLIENTI_H #define CLIENTI_H #include <iostream> #include <fstream> #include "MyException.h" using namespace std; //Classe clienti class Clienti { string Codice_cliente; string Pin; string Nome; string Cognome; //attributi privati int Credito; bool Modificato; //l'archivio viene aggiornato solo se è variato il credito public: Clienti(string DatiCliente); ~Clienti(); string GetCodice_cliente() const { return Codice_cliente; } string GetPin() const { return Pin; } string GetNome() const { return Nome; } string GetCognome() const { return Cognome; } int GetCredito() const { return Credito; } //metodi pubblici per la lettura degli attributi void AggCredito(int Aggiornamento); //metodo per l'aggiornamento del credito }; #endif File Clienti.cpp //definizione della classe clienti #include "Clienti.h" //costruttore Clienti::Clienti(string DatiCliente) { int i = DatiCliente.find('|'), j = 0; //indici temporanei per segnaposto nella stringa Codice_cliente = DatiCliente.substr(j, i-1); //prendo i-1 caratteri di stringa dall'inizio (escludo anche lo spazio) j = DatiCliente.find('|', i+2); i += 2; //fisso i al primo carattere del Pin Pin = DatiCliente.substr(i, (j - 1) - i); i = DatiCliente.find('|', j+2); j += 2; //fisso j al primo carattere del nome Nome = DatiCliente.substr(j, (i - 1) - j); j = DatiCliente.find('$', i+2); i += 2; //fisso i al primo carattere del cognome Cognome = DatiCliente.substr(i, (j - 1) - i); i = DatiCliente.find('\n', j+2); j += 2; //fisso j al primo carattere del Credito Credito = atoi(DatiCliente.substr(j, (i - 1) - j).c_str()); cout << Nome << ' ' << Cognome << endl; Modificato = false; } //////////////////////////////////////////////////////////////////////////////// //metodo che aggiorna il credito; nel file di testo sono riservate 3 cifre decimali void Clienti::AggCredito(int Aggiornamento) { if(Credito + Aggiornamento > 999) cout << "\nLa ricarica non verrà effettuata, il sistema non gestisce un credito maggiore a 999 else { Credito += Aggiornamento; cout << "\nIl suo nuovo credito è " << Credito << " \n"; Modificato = true; } !\n"; } //////////////////////////////////////////////////////////////////////////////// //distruttore Clienti::~Clienti() { bool aggiornato = false; if (Modificato) { try { fstream File_utenti; File_utenti.open("c:/Progetto/Clienti.txt"); if (File_utenti.fail()) throw("Errore durante l'apertura del file Clienti.txt"); while(!File_utenti.eof() && !aggiornato) { char temp[200]; long posizione = File_utenti.tellp(); File_utenti.getline(temp,200); string appoggio(temp); int i = appoggio.find('$') + 2; //i = indice del $ + 2 per scrivere sopra al vecchio credito if(appoggio.substr(0, 5) == Codice_cliente) { //cerco la riga del cliente che è nel sistema File_utenti.seekp(posizione + i); //mi posiziono sopra al vecchio credito... File_utenti.width(3); //scrivo sempre 3 cifre.. File_utenti.setf(ios::left); //..allineate a sinistra File_utenti << Credito; //modifico solo il campo credito delimitato da un segnaposto diverso $ //setto la condizione di uscita dal while aggiornato = true; } } File_utenti.close(); } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } } } File Distributore.h //Dichiarazione della classe distributore #ifndef DISTRIBUTORE_H #define DISTRIBUTORE_H #include <vector> #include <typeinfo> #include "Articoli.h" #include "Clienti.h" #include "StateMgr.h" #include "TimeMgr.h" #include "MyException.h" //classe distributore class Distributore { vector<Articoli *> dvd; // vettore per gli articoli //oggetto utente della classe Clienti Clienti* Utente; StateMgr Sm; //oggetto della classe StateMgr //metodi privati bool Riconoscimento(string Cod_cli_inserito); //metodo per il riconoscimento dell'utente void Scelta(); //metodo per la visualizzazione del menu principale void Gestione_credito(); //implementazione stato gestione credito void Noleggio(); bool Visual_categoria(); bool Visual_genere(); bool Visual_novita(); //prende come paramentro il codice dell'articolo da visualizzare, visual l'articolo e ritorna il suo indice nel vettore int Visualizza_scheda(string Codice); bool Ritira(int Codice); //prendo come paramentro il codice articolo noleggiato void Restituzione(); void Uscita(); public: Distributore(); ~Distributore(); void Idle(); }; #endif //costruttore //distruttore File Distributore.cpp //definizione della classe distributore #include "Distributore.h" //costruttore Distributore::Distributore() { cout << "\nAvvio del sistema in corso....."; Utente = NULL; //punto utente a NULL Idle(); //chiamo il metodo Idle che implementa l'omonimo stato ed il sistema è avviato! } //////////////////////////////////////////////////////////////////////////////// //distruttore Distributore::~Distributore() { cout << "\nUscita dal sistema ad opera dell'amministratore...\n"; cout << "Il distributore stà per essere spento.\n"; } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato IDLE void Distributore::Idle() { bool uscita, acceso = true; string cod_utente; while(acceso) { //il distributore è sempre acceso cout << "\n\n**********************************************************************************\n"; cout << "** Sistema VIDEONOLO disponibile, effettuare l'accesso per le vostre operazioni **\n"; cout << "**********************************************************************************\n"; uscita = false; do { //visualizzazione del menu per l'accesso utente cout << '\n' << "\nInserire codice utente: "; cin >> cod_utente; if (cod_utente == "stop") { //per convenzione l'uscita dal sistema si effettua digitando 'stop' acceso = false; uscita = true; } else if (cod_utente.length() == 5) { //se la lunghezza della stringa digitata è esatta for (int flag = 1, i = 0;((i < 5) && (flag)); i++) if (isdigit(cod_utente[i])) //si controlla che sono stati digitate tutte cifre decimali uscita = true; else { uscita = false; flag = 0; } } } while (!uscita); //si esce dal ciclo se è stato inserito 'stop' o un possibile codice if (acceso && Sm.SetNewState(RICONOSCIMENTO)) //si passa allo stato riconoscimento if (Riconoscimento(cod_utente)) //se il riconoscimento va a buon fine if (Sm.SetNewState(SCELTA)) Scelta(); //si entra nel menù principale dell'applicazione } } //////////////////////////////////////////////////////////////////////////////// //implementazione dell stato RICONOSCIMENTO bool Distributore::Riconoscimento(string Cod_cli_inserito) { bool Cod_Pin = true; //flag per il codice del pin bool Riconosciuto = false; //flag utente riconosciuto string Pin; //variabile per il confronto del pin inserito con quello memrizzato char Temp[6]; //appoggio per il codice cliente e pin char DatiUtente[150]; //stringa per la lettura da file try { ifstream FileUtenti("C:/Progetto/Clienti.txt"); //istanzio una variabile per la lettura di Clienti.txt ed apro in lettura if(FileUtenti.fail()) throw MyException("Errore in apertura del file Clienti.txt"); //leggo il file confrontando il codice cliente inserito con il pin corrispondente while(!FileUtenti.eof() && Cod_Pin && !Riconosciuto) { FileUtenti.getline(DatiUtente, 150); strncpy(Temp, DatiUtente, 5); Temp[5] = '\0'; //completo l'array di char per l'appoggio del cod cliente letto dal file if (Cod_cli_inserito == Temp) { //se il codice inserito ha una corrispondenza nell'archivio dei clienti cout << "\nInserisci il Pin: "; cin >> Pin; for(int i = 0;(i < 5); i++) Temp[i] = DatiUtente[i+8]; Temp[5] = '\0'; //prelevo il pin dalla stringa letta dal file if (Temp == Pin) { //controllo che il pin sia corretto cout << "\nAccesso Effettuato: "; //chiamo il costruttore per istanziare il cliente appena riconosciuto Utente = new Clienti(DatiUtente); Riconosciuto = true; } else { //il pin inserito non corrisponde a quello in archivio cout << "\n" << "Errore!! Pin Errato!!"; Sm.SetNewState(IDLE); //torno allo stato idle -> pin errato Cod_Pin = false; } } } if (!Riconosciuto && Cod_Pin) { //se il codice cliente inserito non è in archivio Sm.SetNewState(IDLE); //torno allo stato idle -> codice cliente errato Cod_Pin = false; } FileUtenti.close(); //chiudo il file Clienti.txt } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } return Riconosciuto; } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato SCELTA void Distributore::Scelta() { bool flag = true; do { //visualizzo il menù principale e attendo una scelta char scelta; string tmp_scelta; cout << "\n----- MENU PRINCIPALE -----\n"; cout << " 1 - GESTIONE CREDITO\n"; cout << " 2 - NOLEGGIO\n"; cout << " 3 - RESTITUZIONE\n"; cout << " 4 - USCITA\n\n"; cout << "Scelta: "; cin >> tmp_scelta; //controllo dell'inserimento! if (tmp_scelta.length() == 1) scelta = tmp_scelta[0]; else scelta = '5'; switch(scelta) { case '1': { if (Sm.SetNewState(GESTIONE_CREDITO)) Gestione_credito(); break; } case '2': { if (Sm.SetNewState(NOLEGGIO)) Noleggio(); break; } case '3': { if (Sm.SetNewState(RESTITUZIONE)) Restituzione(); break; } case '4': { if (Sm.SetNewState(IDLE)){ Uscita(); flag = false; } break; } default: cout << "\nERRORE DI INSERIMENTO!!\n"; } } while(flag); } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato GESTIONE CREDITO void Distributore::Gestione_credito() { char scelta; int credito = 1; do { //visualizzazione del menù per la gestione del credito string tmp_scelta; cout << "\nCredito Residuo = " << Utente->GetCredito() << " cout << "\nScegliere l'operazione da effettuare: \n"; cout << "1 - Ricarica credito\n"; cout << "2 - Torna al menù principale\n"; cout << "Scelta: "; cin >> tmp_scelta; .\n"; if (tmp_scelta.length() == 1) { //controllo dell'inserimento! scelta = tmp_scelta[0]; if (scelta == '1') { do { cout << "\nRICARICA DEL CREDITO:\n"; cout << "Importi consentiti 5, 10, 20 Euro\n"; cout << "Inserire l'importo (0 per uscire): "; cin >> tmp_scelta; //controllo dell'inserimento! if (tmp_scelta.length() <= 2 && isdigit(tmp_scelta[0])) if (isdigit(tmp_scelta[1]) || tmp_scelta.length() == 1) { credito = atoi(tmp_scelta.c_str()); if (credito == 5 || credito == 10 || credito == 20) //se è stato inserito un importo corretto Utente->AggCredito(credito); //viene aggiornato il credito else if (credito != 0) cout << "\nImporto non consentito, la ricarica non verrà effettuata!\n"; } } while(credito); } } } while (scelta != '2'); Sm.SetNewState(SCELTA); } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato NOLEGGIO void Distributore::Noleggio() { bool uscita = false; bool art_ritirato = false; //teniamo traccia dell'avvenuto noleggio perchè in caso affermativo //anzichè rivisualizzare il seguente menù torniamo al menù principale do { char scelta; string tmp_scelta; cout << "\n---------- NOLEGGIO ----------\n"; cout << " 1 - Visualizza per categoria\n"; cout << " 2 - Visualizza per genere\n"; cout << " 3 - Visualizza novità\n"; cout << " 4 - Torna al menù principale\n"; cout << "\n Scelta: "; cin >> tmp_scelta; //controllo dell'inserimento! if (tmp_scelta.length() == 1) scelta = tmp_scelta[0]; else scelta = '5'; switch(scelta) { case '1': { if (Sm.SetNewState(VISUAL_CATEGORIA)) art_ritirato = Visual_categoria(); break; } case '2': { if (Sm.SetNewState(VISUAL_GENERE)) art_ritirato = Visual_genere(); break; } case '3': { if (Sm.SetNewState(VISUAL_NOVITA)) art_ritirato = Visual_novita(); break; } case '4': { if (Sm.SetNewState(SCELTA)) uscita = true; break; } default: cout << "\nERRORE DI INSERIMENTO!!\n"; } } while (!uscita && !art_ritirato); } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato VISUALIZZA PER CATEGORIA bool Distributore::Visual_categoria() { char scelta; bool uscita = false; bool art_ritirato = false; do { //visualizzo il menù di scelta della categoria string tmp_scelta; ifstream File; //istanzio una variabile per la lettura del file scelto cout << "\n---- Scegli la categoria ----\n"; cout << " 1 - Film\n"; cout << " 2 - Concerti\n"; cout << " 3 - Documentari\n"; cout << " 4 - Ritorna al menù precendente\n\n"; cout << "Scelta: "; cin >> tmp_scelta; //controllo dell'inserimento! if (tmp_scelta.length() == 1) scelta = tmp_scelta[0]; else scelta = '5'; try { switch(scelta) { //sulla base della categoria scelta apro il relativo file case '1': { File.open("C:/Progetto/Film.txt"); break; } case '2': { File.open("C:/Progetto/Concerti.txt"); break; } case '3': { File.open("C:/Progetto/Documentari.txt"); break; } case '4': { if (Sm.SetNewState(NOLEGGIO)) uscita = true; break; } default: cout << "\nERRORE DI INSERIMENTO!!\n"; } if (!uscita && scelta != '5') { //se non ho scelto di tornare al menù precedente o non ho sbagliato inserimento... if(File.fail()) throw MyException("Errore in apertura del file della categoria desiderata"); int n_dvd = 0; bool disponibili = false; while(!File.eof()) { char appoggio[200]; int i = 0, j = 0; //indici usati per l'elaborazione della stringa appoggio File.getline(appoggio, 200); string str_appoggio(appoggio); //metto temporaneamente appoggio in una stringa... if (!str_appoggio.empty()) { //per evitare di elaborare l'ultima riga che è una stringa vuota e genera un errore anomalo! Articoli* articolo = articolo->factory(scelta, appoggio, i, j); //istanzio l'oggetto relativo alla categoria scelta dvd.push_back(articolo); //inserisco l'oggetto in un vettore di puntatori ad articoli if (dvd[n_dvd]->GetCopie_disponibili() > 0) { //vengono visualizzati solo gli articoli disponibili cout << '\n' << dvd[n_dvd]->GetCodice_articolo() << ' ' << dvd[n_dvd]->GetTitolo(); disponibili = true; } n_dvd++; } } if(!disponibili) //se non ho visualizzato nemmeno un articolo cout << "\nLa ricerca effettuata non ha trovato alcun risultato!"; cout << "\n\nScegli un articolo sulla base del codice(0 per tornare al menù precedente): "; string codice; cin >> codice; if (codice != "0") { //se non ho digitato 0 per tornare indietro unsigned int indice_dvd = Visualizza_scheda(codice); //visualizzo l'articolo,mem l'indice cui corrisponde sul vettore if (indice_dvd <= dvd.size()) //se voglio noleggiare l'articolo visualizzato if (Sm.SetNewState(RITIRA)) { art_ritirato = Ritira(indice_dvd); //concludo l'operazione di noleggio if (art_ritirato) Sm.SetNewState(SCELTA); else Sm.SetNewState(VISUAL_CATEGORIA); } } File.close(); //chiudo il file //dealloco la memoria precedentemente allocata per contenere gli articoli for(unsigned int i = 0; i < dvd.size(); i++) delete dvd[i]; dvd.clear(); //svuoto il vettore per permettere visualizzazioni successive o per uscire } } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } } while (!uscita && !art_ritirato); return art_ritirato; } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato VISUALIZZA PER GENERE bool Distributore::Visual_genere() { bool art_ritirato = false; int num_genere = 0; string codice; //variabile che usero per scegliere un genere do { char scelta; bool disponibili = false; //queste variabili tengono conto del numero dei generi per ogni categoria per creare un menù numerato dinamicamente int film = 0, concerti = 0, documentari = 0; vector <string> Generi; //vettore che contiene tutti i generi trovati nei tre file codice = "0"; num_genere = 0; cout << "\n------ Visualizzazione degli articoli per genere ------\n"; cout << "\nI genere disponibili sono:\n"; try { //Lettura dei generi dei film cout << " Film:\n"; ifstream File_f("c:/Progetto/Generi_film.txt"); if (File_f.fail()) throw("Errore durante l'apertura del file Generi_film.txt"); while(!File_f.eof()) { char appoggio[15]; File_f.getline(appoggio, 15); cout << " " << ++film << " - " << appoggio << '\n'; Generi.push_back(appoggio); } File_f.close(); //Lettura dei generi dei concerti cout << "\n Concerti:\n"; ifstream File_c("c:/Progetto/Generi_concerti.txt"); if (File_c.fail()) throw("Errore durante l'apertura del file Generi_concerti.txt"); while(!File_c.eof()) { char appoggio[15]; File_c.getline(appoggio, 15); cout << " " << film + (++concerti) << " - " << appoggio << '\n'; Generi.push_back(appoggio); } File_c.close(); //Lettura dei generi dei documentari cout << "\n Documentari:\n"; ifstream File_d("c:/Progetto/Generi_documentari.txt"); if (File_d.fail()) throw("Errore durante l'apertura del file Generi_documentari.txt"); while(!File_d.eof()) { char appoggio[15]; File_d.getline(appoggio, 15); cout << " " << film + concerti + (++documentari) << " - " << appoggio << '\n'; Generi.push_back(appoggio); } File_d.close(); //scelta del genere desiderato int n_dvd = 0; do { string temp; bool ok = true; cout << "\nScegli un genere inserendo il numero(0 per tornare al menù precedente): "; cin >> temp; for (unsigned int i = 0; i < temp.length(); i++) //controllo dell'input if (!isdigit(temp[i])) ok = false; if (ok) num_genere = atoi(temp.c_str()); else num_genere = -1; } while(num_genere < 0 || (num_genere > (film + concerti + documentari))); if (num_genere != 0) { //se non si è scelto di tornare al menù precedente ifstream FileArt; if (num_genere <= film) { //apro il file a seconda del genere scelto FileArt.open("c:/Progetto/Film.txt"); scelta = '1'; } else if (num_genere <= concerti + film) { FileArt.open("c:/Progetto/Concerti.txt"); scelta = '2'; } else { FileArt.open("c:/Progetto/Documentari.txt"); scelta = '3'; } if (FileArt.fail()) throw("\nErrore in apertura del file degli articoli"); while(!FileArt.eof()) { char appoggio[200]; int i = 0, j = 0; //indici usati per l'elaborazione della stringa appoggio FileArt.getline(appoggio, 200); string str_appoggio(appoggio); //metto temporaneamente appoggio in una stringa... if (!str_appoggio.empty()) { //per evitare di elaborare l'ultima riga che è una stringa vuota e genera un errore anomalo! Articoli* articolo = articolo->factory(scelta, appoggio, i, j); dvd.push_back(articolo); if (dvd[n_dvd]->GetCopie_disponibili() > 0 && (dvd[n_dvd]->GetGenere() == Generi[num_genere-1])) { //vengono visualizzati solo gli articoli disponibili tra quelli del genere scelto cout << "\n" << dvd[n_dvd]->GetCodice_articolo() << ' ' << dvd[n_dvd]->GetTitolo(); disponibili = true; } n_dvd++; } } FileArt.close(); if(!disponibili) cout << "\nLa ricerca effettuata non ha trovato alcun risultato!"; cout << "\n\nScegli un articolo sulla base del codice(0 per tornare al menù precedente): "; cin >> codice; if (codice != "0") { //se non ho digitato 0 per tornare indietro unsigned int indice_dvd = Visualizza_scheda(codice); //visualizzo l'articolo e mem l'indice a cui corrisponde sul vettore if (indice_dvd <= dvd.size()) //se voglio noleggiare l'articolo visualizzato if (Sm.SetNewState(RITIRA)) { art_ritirato = Ritira(indice_dvd); //concludo l'operazione di noleggio if (art_ritirato) Sm.SetNewState(SCELTA); else Sm.SetNewState(VISUAL_GENERE); //se non riesco a ritirare per mancanza di credito } } else //se voglio tornare al menù precedente Sm.SetNewState(VISUAL_GENERE); } else //se voglio tornare al menù del noleggio Sm.SetNewState(NOLEGGIO); } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } //dealloco la memoria precedentemente allocata per contenere gli articoli for(unsigned int i = 0; i < dvd.size(); i++) delete dvd[i]; dvd.clear(); //svuoto il vettore per permettere visualizzazioni successive o per uscire Generi.clear(); //rimozione degli elementi dal vettore } while((codice == "0" && num_genere != 0) || (!art_ritirato && num_genere != 0)); return art_ritirato; } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato VISUALIZZA NOVITA' bool Distributore::Visual_novita() { bool art_ritirato = false; string codice; const int LIMITE_NOVITA = 24 * 90 * 3600; //sono novità gli articoli presenti da non più di tre mesi espressi in secondi(24 ore * 90 giorni * 3600 secondi) do { cout << "\nVerranno visualizzati tutti gli articoli presenti in archivio da non più di tre mesi\n"; try { char scelta; int n_dvd = 0; bool disponibili = false; for(int i = 1; i <= 3; i++) { ifstream FileArt; if (i == 1) { //apro il file a seconda della categoria FileArt.open("c:/Progetto/Film.txt"); scelta = '1'; } else if (i == 2) { FileArt.open("c:/Progetto/Concerti.txt"); scelta = '2'; } else { FileArt.open("c:/Progetto/Documentari.txt"); scelta = '3'; } //lettura del file aperto if (FileArt.fail()) throw("\nErrore in apertura del file degli articoli!"); while(!FileArt.eof()){ char appoggio[200]; int i = 0, j = 0; //indici usati per l'elaborazione della stringa appoggio FileArt.getline(appoggio, 200); string str_appoggio(appoggio); //metto temporaneamente appoggio in una stringa... if (!str_appoggio.empty()) { //per evitare di elaborare l'ultima riga che è una stringa vuota e genera un errore anomalo! Articoli* articolo = articolo->factory(scelta, appoggio, i, j); dvd.push_back(articolo); time_t temp = time(NULL); //prendo l'ora corrente //vengono visualizzate le novità disponibili if ((dvd[n_dvd]->GetCopie_disponibili() > 0) && (difftime(temp, mktime(&dvd[n_dvd]->GetData_arrivo())) < LIMITE_NOVITA )) { cout << "\n" << dvd[n_dvd]->GetCodice_articolo() << ' ' << dvd[n_dvd]->GetTitolo(); disponibili = true; } n_dvd++; } } FileArt.close(); } //scelta dell'articolo if(!disponibili) cout << "\nLa ricerca effettuata non ha trovato alcun risultato!"; cout << "\n\nScegli un articolo sulla base del codice(0 per tornare al menù precedente): "; cin >> codice; if (codice != "0") { //se non ho digitato 0 per tornare indietro unsigned int indice_dvd = Visualizza_scheda(codice); //visualizzo l'articolo e mem l'indice a cui corrisponde sul vettore if (indice_dvd <= dvd.size()) //se voglio noleggiare l'articolo visualizzato if (Sm.SetNewState(RITIRA)) { art_ritirato = Ritira(indice_dvd); //concludo l'operazione di noleggio if (art_ritirato) Sm.SetNewState(SCELTA); else Sm.SetNewState(VISUAL_NOVITA); } } for(unsigned int i = 0; i < dvd.size(); i++) delete dvd[i]; dvd.clear(); //svuoto il vettore per permettere visualizzazioni successive o per uscire if (codice == "0") Sm.SetNewState(NOLEGGIO); } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } } while(!art_ritirato && codice != "0"); return art_ritirato; } //////////////////////////////////////////////////////////////////////////////// //metodo per la visualizzazione della scheda di un articolo contenente tutte le sue informazioni int Distributore::Visualizza_scheda(string Codice) { bool trovato = false; for (unsigned int indice = 0; indice < dvd.size(); indice++) { if (Codice == dvd[indice]->GetCodice_articolo()) { trovato = true; cout << "\nTitolo: " << dvd[indice]->GetTitolo(); cout << "\nAnno di uscita: " << dvd[indice]->GetAnno_uscita(); cout << "\nGenere: " << dvd[indice]->GetGenere(); //identificazione run-time del tipo di articolo per stampare le diverse informazioni specifiche if (typeid(*dvd[indice]) == typeid(Film)) { cout << "\nRegista: " << dvd[indice]->GetInfo_specifica1(); cout << "\nAttore Protagonista: " << dvd[indice]->GetInfo_specifica2(); } else if (typeid(*dvd[indice]) == typeid(Concerti)) { cout << "\nCantante: " << dvd[indice]->GetInfo_specifica1(); cout << "\nLuogo del concerto: " << dvd[indice]->GetInfo_specifica2(); } else { cout << "\nLuogo di registrazione: " << dvd[indice]->GetInfo_specifica1(); cout << "\nBreve descrizione: " << dvd[indice]->GetInfo_specifica2(); } char risp; string tmp_risp; do { cout << "\n\nVuoi noleggiare questo articolo? s/n\n Scelta: "; cin >> tmp_risp; if (tmp_risp.length() == 1) //controllo dell'input risp = tmp_risp[0]; else risp = '1'; } while (risp != 's' && risp != 'n'); if (risp == 's') return indice; } } if (!trovato) cout << "\nCodice errato! Ripetere l'operazione!\n"; return dvd.size()+1; } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato RITIRA bool Distributore::Ritira(int Codice) { bool noloOK = true; if(dvd[Codice]->AggCopie_disponibili(0)) {//decremento la disponibilità dell'articolo noleggiato if (Utente->GetCredito() >= 2) { cout << "\n\nArticolo Noleggiato! Verranno detratti 2 dal suo credito"; Utente->AggCredito(-2); try { //scrittura sul file noleggi.txt le informazioni relative al noleggio ofstream Noleggi; Noleggi.open("c:/Progetto/Noleggi.txt", ios::app); if (Noleggi.fail()) throw("Errore durante l'apertura del file Noleggi.txt"); string data_nol(GetCurrTime()); //prendo l'ora attuale Noleggi << "GG/MM/AAAA HH:MM:SS | " << Utente->GetCodice_cliente() << " | " << dvd[Codice]->GetCodice_articolo() << " | " << data_nol << endl; Noleggi.close(); //aggiornamento dell'archivio degli articoli fstream FileDvd; //apro il file identificando il tipo runtime if(typeid(*dvd[Codice]) == typeid(Film)) FileDvd.open("c:/Progetto/Film.txt"); else if(typeid(*dvd[Codice]) == typeid(Concerti)) FileDvd.open("c:/Progetto/Concerti.txt"); else FileDvd.open("c:/Progetto/Documentari.txt"); if(FileDvd.fail()) throw("Errore in apertura del file di archivio"); //leggo il file aperto e cerco l'articolo noleggiato per aggiornare le informazioni(copie disponibili) bool aggiornato = false; while(!FileDvd.eof() && !aggiornato){ char temp[200]; long posizione = FileDvd.tellp(); //memorizzo il puntatore alla posizione corrente FileDvd.getline(temp,200); //leggo una riga string appoggio(temp); int p = appoggio.find('$') + 2; //mi posiziono per scrivere sopra la vecchia disponibilità if(appoggio.substr(0, 5) == dvd[Codice]->GetCodice_articolo()) { //se la riga letta corrisponde al file noleggiato FileDvd.seekp(posizione + p); //mi riposiziono all'inizio della riga appena letta FileDvd.width(2); //sono riservate due cifre decimali per il campo delle copie disponibili FileDvd.setf(ios::left); //allineate a sinistra FileDvd << dvd[Codice]->GetCopie_disponibili(); //riaggiorno solo il campo della disponibilità aggiornato = true; //una volta aggiornato il file esco dal ciclo } } FileDvd.close(); } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } } else { cout << "\nIl suo credito non è sufficiente per il noleggio!\n"; noloOK = false; } } else { cout << "\nArticolo non disponibile, ripetere l'operazione"; noloOK = false; } return noloOK; } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato RESTITUZIONE void Distributore::Restituzione() { string codice; bool restituito = false; cout << "\nInserisci il codice dell'articolo che vuoi restituire(0 per tornare al menù principale): "; cin >> codice; if (codice != "0") { cout << "\nRicerca dell'articolo in corso..."; bool uscita = false; try { fstream FileNoleggi("c:/Progetto/Noleggi.txt"); if (FileNoleggi.fail()) throw("\nErrore in apertura del file Noleggi.txt"); while(!FileNoleggi.eof() && !uscita) { char appoggio[60]; long posizione = FileNoleggi.tellp(); //memorizzo la posizione FileNoleggi.getline(appoggio, 60); //leggo i dati dal file dei noleggi if (appoggio[0] == 'G') { //indica che l'articolo non è stato ancora restituito string datinolo(appoggio); //copio i dati letti su una stringa per l'elaborazione string cliente = datinolo.substr(22, 5); //estraggo il codice del cliente if (cliente == Utente->GetCodice_cliente()) { string cod_art = datinolo.substr(30, 5); //estraggo il codice dell'articolo if (cod_art == codice) { cout << ".TROVATO!\n"; string DataNolo = datinolo.substr(38, 19); //copio i dati relativi al momento del noleggio //calcolo del prezzo del noleggio string DataCorr(GetCurrTime()); //variabile in cui memorizzo la data della riconsegna int ore_nolo = OreNoleggio(DataNolo, DataCorr); cout << "\nL'articolo è stato noleggiato per " << ore_nolo << " ore"; int prezzo = ((ore_nolo >= 12) && (ore_nolo <= 24) || (ore_nolo > 24)) + 2 * (ore_nolo / 24) * (ore_nolo > 24); cout << "\nIn totale hai speso " << prezzo + 2 << " (2 erano stati detratti al momento del noleggio!)"; if (prezzo <= Utente->GetCredito()) { //se il credito è sufficiente restituito = true; Utente->AggCredito(-prezzo); FileNoleggi.seekp(posizione); //mi riposiziono all'inizio della riga //Inserisco data e ora della restituzione FileNoleggi << DataCorr; //aggiorno la disponibilità del file dell'articolo restituito fstream FileArticoli; char c = codice[0]; char scelta; switch (c) { //in base al primo carattere del codice articolo conosco la categoria di appartenenza case 'f': { FileArticoli.open("c:/Progetto/Film.txt"); scelta = '1'; break; } case 'c':{ FileArticoli.open("c:/Progetto/Concerti.txt"); scelta = '2'; break; } case 'd':{ FileArticoli.open("c:/Progetto/Documentari.txt"); scelta = '3'; break; } } if (FileArticoli.fail()) throw ("\nErrore in apertura del file degli articoli!\n"); while (!FileArticoli.eof() && !uscita) { char temp[200]; long posizione = FileArticoli.tellp(); FileArticoli.getline(temp,200); string appoggio(temp); //mi posiziono per scrivere sopra la vecchia disponibilita int p = appoggio.find('$') + 2; if(appoggio.substr(0, 5) == codice) { //ho trovato l'articolo noleggiato in archivio int i = 0, j = 0; Articoli* articolo = articolo->factory(scelta, appoggio, i, j); //istanzio un oggetto corrisp all'art da rest strftime(temp, 11, "%d/%m/%Y", &articolo->GetData_arrivo()); articolo->AggCopie_disponibili(1); FileArticoli.seekp(posizione + p); //incremento la disponibilità dell'articolo restituito FileArticoli.width(2); //sono riservate due cifre decimali per il campo delle copie disponibili FileArticoli.setf(ios::left); //allineate a sinistra FileArticoli << articolo->GetCopie_disponibili(); //riaggiorno solo il campo delle disponibilita delete articolo; uscita = true; } } FileArticoli.close(); Sm.SetNewState(SCELTA); } else { cout << "\nL'operazione non può essere completata!!\nRicaricare il credito\n"; if(Sm.SetNewState(GESTIONE_CREDITO)) { Gestione_credito(); uscita = true; } } } } } else if (!restituito) cout << "."; } if(!restituito && !uscita) { cout << ".NON TROVATO!"; Sm.SetNewState(SCELTA); } FileNoleggi.close(); } catch(MyException e) { cout << e.getError(); exit(1); //Termino il programma!!! } } else Sm.SetNewState(SCELTA); } //////////////////////////////////////////////////////////////////////////////// //implementazione dello stato USCITA void Distributore::Uscita() { cout << "\n" << Utente->GetNome() << ' ' << Utente->GetCognome() << " è uscito dal sistema...\n"; cout << "ARRIVEDERCI E GRAZIE!!!\n\n\n"; delete Utente; //viene invocato il distruttore che apporta le modifiche al file se necessario } //////////////////////////////////////////////////////////////////////////////// File MyException.h //Dichiarazione e implementazione inline della libreria di gestione delle eccezioni #ifndef MY_EXCEPTION #define MY_EXCEPTION #include <exception> using namespace std; class MyException : public exception { string tipoErr; public: MyException(string s) { tipoErr = s; } string getError() { return tipoErr; } }; #endif File StateMgr.h //dichiarazione della libreria che implementa lo state manager #ifndef STATEMGR_H #define STATEMGR_H #include <iostream> using namespace std; //Tipo di dato enumerato che rappresenta tutti gli stati del sistema enum State { IDLE, RICONOSCIMENTO, SCELTA, GESTIONE_CREDITO, NOLEGGIO, VISUAL_CATEGORIA, VISUAL_GENERE, VISUAL_NOVITA, RITIRA, RESTITUZIONE }; //classe state manager class StateMgr { State CurrState; //attributo per lo stato corrente void PrintCurrState() const; //metodo per la stampa dello stato corrente public: StateMgr(); //costruttore State GetCurrState() const { return CurrState; } //metodo per la lettura dello stato corrente bool SetNewState(State NewState); //metodo per cambiare di stato }; #endif File StateMgr.cpp //Implementazione della classe State Manager #include "StateMgr.h" //costruttore StateMgr::StateMgr() { //quando istanzio un oggetto del tipo StateMgr(accensione del sistema), //il sistema è automaticamente nello stato Idle CurrState = IDLE; } //////////////////////////////////////////////////////////////////////////////// //metodo per la stampa dello stato corrente void StateMgr::PrintCurrState() const { switch (CurrState) { case IDLE: cout << "\n--- STATO case RICONOSCIMENTO: cout << "\n--- STATO case SCELTA: cout << "\n--- STATO case GESTIONE_CREDITO: cout << "\n--- STATO case NOLEGGIO: cout << "\n--- STATO case VISUAL_CATEGORIA: cout << "\n--- STATO case VISUAL_GENERE: cout << "\n--- STATO case VISUAL_NOVITA: cout << "\n--- STATO case RITIRA: cout << "\n--- STATO case RESTITUZIONE: cout << "\n--- STATO } IDLE ---\n"; break; RICONOSCIMENTO ---\n"; break; SCELTA ---\n"; break; GESTIONE CREDITO ---\n"; break; NOLEGGIO ---\n"; break; VISUALIZZA PER CATEGORIA ---\n"; break; VISUALIZZA PER GENERE ---\n"; break; VISUALIZZA NOVITA' ---\n"; break; RITIRA ---\n"; break; RESTITUZIONE ---\n"; break; } //////////////////////////////////////////////////////////////////////////////// //metodo per il passaggio di stato bool StateMgr::SetNewState(State NewState) { bool trOK = false; //variabile per controllare la corretta transizione switch (NewState) { case IDLE: if (CurrState == SCELTA || CurrState == RICONOSCIMENTO) trOK = true; break; case RICONOSCIMENTO: if (CurrState == IDLE) trOK = true; break; case SCELTA: if (CurrState == GESTIONE_CREDITO || CurrState == RICONOSCIMENTO || CurrState == NOLEGGIO || CurrState == RITIRA || CurrState == RESTITUZIONE) trOK = true; break; case GESTIONE_CREDITO: if (CurrState == SCELTA || CurrState == RESTITUZIONE) trOK = true; break; case NOLEGGIO: if (CurrState == SCELTA || CurrState == VISUAL_CATEGORIA || CurrState == VISUAL_GENERE || CurrState == VISUAL_NOVITA) trOK = true; break; case VISUAL_CATEGORIA: if (CurrState == NOLEGGIO || CurrState == RITIRA) trOK = true; break; case VISUAL_GENERE: if (CurrState == NOLEGGIO || CurrState == RITIRA) trOK = true; break; case VISUAL_NOVITA: if (CurrState == NOLEGGIO || CurrState == RITIRA) trOK = true; break; case RITIRA: if (CurrState == VISUAL_CATEGORIA || CurrState == VISUAL_GENERE || CurrState == VISUAL_NOVITA) trOK = true; break; case RESTITUZIONE: if (CurrState == SCELTA) trOK = true; break; } if (trOK) CurrState = NewState; PrintCurrState(); return trOK; } //////////////////////////////////////////////////////////////////////////////// File TimeMgr.h //Dichiarazione della libreria per la gestione del tempo #ifndef TIMEMGR_H #define TIMEMGR_H #include <iostream> #include <ctime> using namespace std; string GetCurrTime(); //funzione che scrive su una stringa data e ora correnti int OreNoleggio(string DataNolo, string DataCorr); //funzione che esegue calcola la durata del noleggio tm Converti(string Data); //funzione che converte una stringa in un tm #endif File TimeMgr.cpp #include "TimeMgr.h" //funzione che scrive su una stringa data e ora correnti string GetCurrTime() { char timer[25]; time_t temp; temp = time(NULL); strftime(timer, 25, "%d/%m/%Y %H:%M:%S", localtime(&temp)); string ris(timer); return ris; //scrivo in timer la data atturale nel formato GG/MM/AAAA HH:MM:SS //metto la data attuale nella string di ritorno } //////////////////////////////////////////////////////////////////////////////// //funzione che calcola la durata del noleggio in ore int OreNoleggio(string DataNolo, string DataCorr) { //variabili contenenti la data attuale e di noleggio in strutture tm tm tempoC = Converti(DataCorr), tempoN = Converti(DataNolo); //ritorno la differenza in ore tra le due variabili return difftime(mktime(&tempoC), mktime(&tempoN))/3600; } //////////////////////////////////////////////////////////////////////////////// //funzione che converte una stringa in tm tm Converti(string Data) { tm tempo; string appoggio; //giorno appoggio = Data.substr(0,2); tempo.tm_mday = atoi(appoggio.c_str()); appoggio.erase(); //mese appoggio = Data.substr(3,2); tempo.tm_mon = atoi(appoggio.c_str())-1; appoggio.erase(); //anno appoggio = Data.substr(6,4); tempo.tm_year = atoi(appoggio.c_str())-1900; appoggio.erase(); //ore appoggio = Data.substr(11,2); tempo.tm_hour = atoi(appoggio.c_str()); appoggio.erase(); //minuti appoggio = Data.substr(14,2); tempo.tm_min = atoi(appoggio.c_str()); appoggio.erase(); //secondi appoggio = Data.substr(17,2); tempo.tm_sec = atoi(appoggio.c_str()); appoggio.erase(); //ora legale (-1 = assenza di informazioni) tempo.tm_isdst = -1; return tempo; } //////////////////////////////////////////////////////////////////////////////// TESTING DEL PROGRAMMA Per effettuare il testing del programma abbiamo scelto la tecnica “black-box” per evidenziare gli aspetti funzionali dell’applicazione. Come sappiamo questa tecnica non si interessa della struttura interna dell’applicazione (aspetto curato dalla tecnica di collaudo “white-box”), tuttavia nella fase di codifica ad ogni modulo implementato e integrato abbiamo eseguito numerosi e specifici controlli volti a percorrere più volte tutti i cammini logici del programma. Possiamo quindi affermare con certezza che non esiste nessuna istruzione che non viene eseguita almeno una volta e che non esistono cicli infiniti. Per eseguire la tecnica “black-box” dobbiamo navigare l’interfaccia utente sottoponendo il programma a dei sottoinsiemi di input che coprono tutta la suddivisione del dominio in classi di equivalenza valutando anche dove è necessario i valori limite. A programma avviato appare la schermata iniziale: Avvio del sistema in corso..... ********************************************************************************** ** Sistema VIDEONOLO disponibile, effettuare l'accesso per le vostre operazioni ** ********************************************************************************** Inserire codice utente: Evitiamo di ripetere questa schermata in tutte le fasi di test in modo da evidenziare soltanto le risposte all’input immesso. Tutte le prove sono state effettuate facendo loggare al sistema un utente creato appositamente per effettuare il testing e modificando manualmente il file quando necessario. 11111 | 11111 | Utente | Di Prova $ 41 Come primo test abbiamo verificato il comportamento del programma durante la fase di accesso al sistema: Inserire codice utente: caratteri Inserire codice utente: 12c45 Inserire codice utente: 12345 --- STATO RICONOSCIMENTO ----- STATO IDLE --********************************************************************************** ** Sistema VIDEONOLO disponibile, effettuare l'accesso per le vostre operazioni ** ********************************************************************************** Inserire codice utente: 11111 --- STATO RICONOSCIMENTO --Inserisci il Pin: Sopra sono riportate quattro prove di accesso al sistema comprendenti i tre inserimenti possibili: • Inserimento di un codice con lunghezza diversa da 5 caratteri o contenente almeno un carattere non numerico -> codice non accettato, richiesta nuovo codice. • Inserimento di un codice con lunghezza pari a 5 caratteri o contenente almeno un carattere non numerico -> codice non accettato, richiesta nuovo codice. • Inserimento di un codice con lunghezza pari a 5 caratteri e contenente solo caratteri numerici -> codice accettato, ricerca del codice nell’archivio (passaggio allo stato riconoscimento), codice non trovato in archivio, ritorno allo stato e alla schermata iniziale. • Inserimento di un codice corretto -> codice accettato, ricerca del codice nell’archivio (passaggio allo stato riconoscimento), codice trovato in archivio, richiesta del pin. Successivamente siamo passati al controllo della fase di richiesta pin: Inserisci il Pin: ciao Errore!! Pin Errato!! --- STATO IDLE --********************************************************************************** ** Sistema VIDEONOLO disponibile, effettuare l'accesso per le vostre operazioni ** ********************************************************************************** Inserire codice utente: 11111 --- STATO RICONOSCIMENTO --Inserisci il Pin: 11111 Accesso Effettuato: Utente Di Prova --- STATO SCELTA ------- MENU PRINCIPALE ----1 2 3 4 – GESTIONE CREDITO NOLEGGIO RESTITUZIONE USCITA Scelta: Gli unici due casi sensati sono l’immissione di un codice errato che fa tornare il sistema alla fase iniziale e l’immissione di un codice corretto che causa l’effettivo accesso al sistema visualizzando nome e cognome dell’utente e il menù principale dell’applicazione. A questo punto iniziamo l’effettiva navigazione dell’intero menu specificando la scelta di un qualsiasi sottomenù immettendo un indice sbagliato causa in tutti i casi la rivisualizzazione del menù come nell’esempio seguente : --- STATO SCELTA ------- MENU PRINCIPALE ----1 2 3 4 – GESTIONE CREDITO NOLEGGIO RESTITUZIONE USCITA Scelta: 5 ERRORE DI INSERIMENTO!! ----- MENU PRINCIPALE ----1 - GESTIONE CREDITO 2 - NOLEGGIO 3 - RESTITUZIONE 4 – USCITA Scelta: ciao ERRORE DI INSERIMENTO!! ----- MENU PRINCIPALE ----1 - GESTIONE CREDITO 2 - NOLEGGIO 3 - RESTITUZIONE 4 – USCITA Scelta: Passiamo adesso alla fase di gestione credito con il relativo menù; verranno analizzati in seguito tutti i casi più significativi: Scelta: 1 --- STATO GESTIONE CREDITO --Credito Residuo = 41 . Scegliere l'operazione da effettuare: 1 - Ricarica credito 2 - Torna al menù principale Scelta: 1 RICARICA DEL CREDITO: Importi consentiti 5, 10, 20 Euro Inserire l'importo (0 per uscire): 30 Importo non consentito, la ricarica non verrà effettuata! RICARICA DEL CREDITO: Importi consentiti 5, 10, 20 Euro Inserire l'importo (0 per uscire): 10 Il suo nuovo credito è 51 RICARICA DEL CREDITO: Importi consentiti 5, 10, 20 Euro Inserire l'importo (0 per uscire): 0 Credito Residuo = 51 . Scegliere l'operazione da effettuare: 1 - Ricarica credito 2 - Torna al menù principale Scelta: Come si può vedere l’applicazione si comporta coerentemente sia in caso inserimento di un importo non consentito che in caso di inserimento di un importo valido. Prendiamo adesso in considerazione il caso limite riguardante il massimo credito consentito (999 ). Quindi modifichiamo manualmente il file di testo ed effettuare una ricarica: --- STATO GESTIONE CREDITO --Credito Residuo = 990 . Scegliere l'operazione da effettuare: 1 - Ricarica credito 2 - Torna al menù principale Scelta: 1 RICARICA DEL CREDITO: Importi consentiti 5, 10, 20 Euro Inserire l'importo (0 per uscire): 10 La ricarica non verrà effettuata, il sistema non gestisce un credito maggiore a 999 ! RICARICA DEL CREDITO: Importi consentiti 5, 10, 20 Euro Inserire l'importo (0 per uscire): 5 Il suo nuovo credito è 995 Al logout del cliente viene aggiornato il file che modifica se necessario il campo contenete il credito: 11111 | 11111 | Utente | Di Prova $ 995 Nella fase di noleggio verifichiamo solo le diverse visualizzazioni degli articoli senza ripetere tutte le volte le fasi di visualizzazioni della scheda dell’articolo selezionato e l’accettazione o meno dell’effettivo noleggio: ---------- NOLEGGIO ---------1 - Visualizza per categoria 2 - Visualizza per genere 3 - Visualizza novità 4 - Torna al menù principale Scelta: 1 --- STATO VISUALIZZA PER CATEGORIA ------ Scegli la categoria ---1 - Film 2 - Concerti 3 - Documentari 4 - Ritorna al menù precendente Scelta: 1 f0001 Blow f0002 Codice Sworfishsh f0003 Instinct f0004 Vacanze di natale 2004 f0005 Kill bill v.1 f0006 Kill bill v.2 f0007 Trainspotting f0008 Platoon f0009 L'esorcista (versione integrale) f0010 L'era glaciale f0011 Pulp fiction Scegli un articolo sulla base del codice(0 per tornare al menù precedente): 0 ---- Scegli la categoria ---1 - Film 2 - Concerti 3 - Documentari 4 - Ritorna al menù precendente Scelta: 1 f0001 Blow f0003 Instinct f0004 Vacanze di natale 2004 f0005 Kill bill v.1 f0006 Kill bill v.2 f0008 Platoon f0009 L'esorcista (versione integrale) f0011 Pulp fiction Scegli un articolo sulla base del codice(0 per tornare al menù precedente): Tra la prima e la seconda visualizzazione dei film è stato modificato il file di archivio mettendo a 0 la disponibilità di 3 articoli che come si può notare non vengono visualizzati al secondo tentativo. Dato che le visualizzazioni per categoria si equivalgono, passiamo alla visualizzazione per genere: --- STATO VISUALIZZA PER GENERE -------- Visualizzazione degli articoli per genere -----I genere disponibili sono: Film: 1 - Commedia 2 - Azione 3 - Thriller 4 - Drammatico 5 - Horror 6 - Animazione 7 - Pulp Concerti: 8 - Rock 9 - Pop 10 - Reggae 11 - Soul 12 - Metal Documentari: 13 - Scienza 14 - Tecnologia 15 - Storia 16 - Natura 17 - Geologia 18 - Sport Scegli un genere inserendo il numero(0 per tornare al menù precedente): 0 --- STATO NOLEGGIO ------------ NOLEGGIO ---------1 - Visualizza per categoria 2 - Visualizza per genere 3 - Visualizza novità 4 - Torna al menù principale Scelta: 2 --- STATO VISUALIZZA PER GENERE -------- Visualizzazione degli articoli per genere -----I genere disponibili sono: Film: 1 - Commedia 2 - Azione 3 - Thriller 4 - Drammatico 5 - Horror 6 - Animazione 7 - Pulp Concerti: 8 - Rock 9 - Pop 10 - Reggae 11 - Soul 12 - Metal 13 - Lirica Documentari: 14 - Scienza 15 - Tecnologia 16 - Storia 17 - Natura 18 - Geologia 19 - Sport Scegli un genere inserendo il numero(0 per tornare al menù precedente): Anche in questo caso c’è stata una modifica dell’archivio tra il primo e il secondo tentativo, in particolare è stato aggiunto un nuovo concerto appartenente ad un genere che non era ancora presente in archivio(lirica). Come si può vedere la lista viene aggiornata dinamicamente ed il programma funziona correttamente: Scegli un genere inserendo il numero(0 per tornare al menù precedente): 13 c0008 Pavarotti & friend's Scegli un articolo sulla base del codice(0 per tornare al menù precedente): Passiamo infine alla visualizzazione delle novità: --- STATO VISUALIZZA NOVITA' --Verranno visualizzati tutti gli articoli presenti in archivio da non più di tre mesi f0001 Blow f0003 Instinct f0006 Kill bill v.2 c0003 Ben Harper Live To Mars c0005 The song remains the same c0008 Pavarotti & friend's d0008 Il mio anno più bello Scegli un articolo sulla base del codice(0 per tornare al menù precedente): 0 --- STATO NOLEGGIO ------------ NOLEGGIO ---------1 - Visualizza per categoria 2 - Visualizza per genere 3 - Visualizza novità 4 - Torna al menù principale Scelta: 3 --- STATO VISUALIZZA NOVITA' --Verranno visualizzati tutti gli articoli presenti in archivio da non più di tre mesi f0003 Instinct f0006 Kill bill v.2 c0003 Ben Harper Live To Mars c0005 The song remains the same c0008 Pavarotti & friend's d0008 Il mio anno più bello Scegli un articolo sulla base del codice(0 per tornare al menù precedente): Come al solito abbiamo effettuato una modifica manuale all’archivio tra i due tentativi cambiando la data di arrivo dell’articolo con codice f0001 che come previsto non compare nel secondo tentativo. Proviamo adesso a noleggiare un articolo: Scegli un articolo sulla base del codice(0 per tornare al menù precedente): c0005 Titolo: The song remains the same Anno di uscita: 2004 Genere: Rock Cantante: Led Zeppelin Luogo del concerto: Madison square garden Vuoi noleggiare questo articolo? s/n Scelta: s --- STATO RITIRA --Articolo Noleggiato! Verranno detratti 2 Il suo nuovo credito è 993 dal suo credito --- STATO SCELTA ------- MENU PRINCIPALE ----1 - GESTIONE CREDITO 2 - NOLEGGIO 3 - RESTITUZIONE 4 - USCITA Scelta: Questo è il caso di un noleggio completato senza problemi cui seguono l’aggiornamento istantaneo della disponibilità dell’articolo e la scrittura(anch’essa istantanea) di una nuova riga nell’archivio dei noleggi: GG/MM/AAAA HH:MM:SS | 11111 | c0005 | 29/06/2005 11:43:30 Proviamo adesso alcuni casi particolari: 1) Il cliente non ha i 2 necessari per il noleggio --- STATO GESTIONE CREDITO --Credito Residuo = 1 . ... c0001 c0002 c0003 c0004 c0005 c0006 c0007 c0008 Vasco Live @ S. Siro '03 Bob Marley The Legend Ben Harper Live To Mars Like A Virgin The song remains the same Live at the Hollywood bowl Live at Forum Pavarotti & friend's Scegli un articolo sulla base del codice(0 per tornare al menù precedente): c0007 Titolo: Live at Forum Anno di uscita: 1999 Genere: Metal Cantante: Metallica Luogo del concerto: Milano Vuoi noleggiare questo articolo? s/n Scelta: s --- STATO RITIRA --Il suo credito non è sufficiente per il noleggio! 2) Si vuole noleggiare un articolo non disponibile anche se questo non era stato visualizzato f0001 f0003 f0004 f0005 f0006 f0008 f0009 f0011 Blow Instinct Vacanze di natale 2004 Kill bill v.1 Kill bill v.2 Platoon L'esorcista (versione integrale) Pulp fiction Scegli un articolo sulla base del codice(0 per tornare al menù precedente): f0007 Codice errato! Ripetere l'operazione! Testiamo adesso la fase di riconsegna distinguendo 3 casi: 1) Riconsegna avvenuta senza errori: Prima della riconsegna sul file noleggi appariva la seguente riga GG/MM/AAAA HH:MM:SS | 11111 | c0007 | 17/06/2005 16:47:35 --- STATO RESTITUZIONE --Inserisci il codice dell'articolo che vuoi restituire(0 per tornare al menù principale): c0007 Ricerca dell'articolo in corso..............TROVATO! L'articolo è stato noleggiato per 283 ore (2 erano stati detratti al momento del noleggio!) In totale hai speso 25 Il suo nuovo credito è 18 Dopo la restituzione la riga viene così modificata 29/06/2005 12:04:15 | 11111 | c0007 | 17/06/2005 16:47:35 2) Riconsegna di un articolo già restituito o mai noleggiato: --- STATO RESTITUZIONE --Inserisci il codice dell'articolo che vuoi restituire(0 per tornare al menù principale): c0007 Ricerca dell'articolo in corso..................NON TROVATO! 3) Riconsegna di un articolo senza aver il credito necessario: --- STATO RESTITUZIONE --Inserisci il codice dell'articolo che vuoi restituire(0 per tornare al menù principale): f0004 Ricerca dell'articolo in corso.................TROVATO! L'articolo è stato noleggiato per 282 ore In totale hai speso 25 (2 erano stati detratti al momento del noleggio!) L'operazione non può essere completata!! Ricaricare il credito --- STATO GESTIONE CREDITO --Credito Residuo = 18 . Scegliere l'operazione da effettuare: 1 - Ricarica credito 2 - Torna al menù principale Scelta: Istruzioni per la modifica manuale degli archivi Poiché in archivio sono presenti solo un numero di articoli e clienti necessario a svolgere test significativi può essere interessante modificare manualmente l’archivio al fine di aggiungerne degli altri. In questo caso vanno seguite alcune semplici regole fondamentali per il corretto funzionamento del programma. Queste regole sono dettate dal formato standard che abbiamo dato ai file di testo e che ci ha permesso di semplificare notevolmente la logica di controllo e l’accesso ai file(operazioni che inizialmente ci hanno creato non pochi problemi): I file Film.txt, Documentari.txt, Concerti.txt, Clienti.txt devono essere editati separando i campi con il carattere ‘|’ preceduto e seguito da uno spazio. Il solo campo relativo al credito del cliente ed alla disponibilità dei vari articoli va preceduto dal carattere ‘$’ preceduto e seguito da uno spazio. Al campo disponibilità vanno assegnati comunque due spazi con allineamento a sinistra. Al campo credito dell’utente vanno assegnati comunque tre spazi con allineamento a sinistra. Tutte le righe dei file sopraccitati (anche l’ultima) devono terminare con il carattere ‘\n’ (andata a capo). Quando si aggiunge un articolo bisogna controllare se sono presenti in archivio altri articoli dello stesso genere ed in caso negativo va aggiunto il nuovo genere nell’apposito file(Generi_film.txt, Generi_concerti.txt o Generi_documentari.txt). In questi file l’ultima riga non va terminata con il carattere ‘\n’. L’intero programma, completo di archivio, è dentro la cartella progetto. Per funzionare tale cartella deve risiedere in c:\ .