Classi e OOP Introduzione (parte 3/4)
a cura di Enrico Barillari e Sabrina Cosolo (requisiti: minima esperienza di programmazione)Applichiamo l'ereditarietà e creiamo una nuova classe
La classe Persona implementa alcune caratteristiche base che permettono di descrivere un individuo, ma gli individui possono essere suddivisi in classi in base ad astrazioni più specifiche; quella che intendiamo implementare nel nostro esempio è un astrazione di tipo lavorativo, ovvero creiamo una classe che identifica un tipo di lavoratore, ed essendo il lavoratore una persona, questa classe è figlia, o erede di persona. Quale lavoratore conosciamo meglio di noi stessi? Magari il vigile che ci fa le multe e ci toglie punti sulla patente chissà, però abbiamo deciso di occuparci dei Programmatori.Il programmatore, ha alcune peculiarità proprie da aggiungere a quelle della classe persona, vediamo di individuarle:
- Il programmatore ha degli Skill, ovvero un livello di esperienza nel proprio lavoro.
- Il programmatore conosce dei linguaggi di programmazione.
- Il programmatore può conoscere il linguaggio SQL per l'interrogazione dei database.
- Il programmatore ha iniziato a fare il programmatore in una certa data.
A questo punto dopo aver definito le caratteristiche dovremo definire il necessario a manipolarle:
- Gli skill saranno semplificati da un singolo valore di livello, questo valore sarà selezionabile tra una lista di valori discreti, perciò ci permetterà di introdurre le enumerazioni.
- I linguaggi conosciuti, sono una lista, non una serie di valori discreti, pertanto dovrà essere definita utilizzando una collection, ottimo modo per parlare di array e collezioni.
- La conoscenza di SQL come linguaggio sarà un semplice si/no, quindi un campo booleano.
- La data di inizio attività sarà un semplice campo data, con dei controlli naturalmente.
Per alcune di queste basterà una proprietà apposita, per i linguaggi servirà una collezione e serviranno dei metodi di inserimento, reperimento, modifica, cancellazione dei valori stessi.
Cominciamo dunque a lavorare sulla nostra classe, creando il file che la contiene. Per far questo, in Visual studio faremo come per la creazione della prima classe, tasto destro sul progetto Persona, opzione aggiungi, aggiungi classe. Chiameremo il file che ospiterā la classe «Programmatore.cs», ovviamente. Visual Studio provvederà a crearci la bozza della classe.
Definiamo l'enumerazione dei livelli di conoscenza
Enrico ha scritto una enumerazione di tipo sanguigno, molto italiana; io, nonostante sappia che mi odierà per questo , ho deciso invece di utilizzare una enumerazione ispirata a Guerre Stellari, in onore del buon Maurizio Brasca.Una enumerazione è una serie di costanti numeriche che naturalmente hanno uno scopo comune e vengono identificate tramite un nome. (Sono stata abbastanza nebulosa? Scriviamo il codice che è più facile...)
public enum Skills : int { aspirante = 0, novizio, allievo, padawan, cavaliere, maestro, yoda };L'enumerazione ci servirà a poter effettuare test e verifiche sulle capacità del programmatore senza doverci ricordare qual è il valore che indica i suoi skill ma utilizzando semplicemente il nome associato a tale valore:
if (pg.Skill >= Skills.cavaliere) { MessageBox.Show( "Puoi Proseguire con il prossimo argomento." ); } else { Messagebox.Show( "E' meglio studiare ancora un po'" ); }Avendo indicato il primo valore e non indicato valore per i successivi, il numero associato al nome viene incrementato automaticamente di uno.
Proviamo a vedere come funziona aggiungendo alcune cose al nostro progetto di test. Per prima cosa, aggiungiamo due menuItem al menu Programmatore che chiameremo mnuEnum e mnuTestProgrammatore, poi aggiungiamo una form che chiameremo frmEnum e sarà uguale alla form di test della classe Persona.
Quindi gli daremo un titolo opportuno, aggiungeremo una label che chiameremo lblEnum. Poi torneremo sul designer della form principale e facendo doppio click sul menuItem mnuEnum ed inseriamo il codice per chiamare la form di test:
private void menuEnum_Click(object sender, System.EventArgs e) { frmEnum frm = new frmEnum(); frm.MdiParent = this; frm.Show(); }A questo punto, modifichiamo la form di test enumerazione inserendo il seguente codice nella sua routine Form _load:
private void frmEnum_Load(object sender, System.EventArgs e) { lblEnum.Text = ""; foreach(int i in Enum.GetValues(typeof(Skills))) { lblEnum.Text += i.ToString() + " " + Enum.GetName(typeof(Skills), i) + Environment.NewLine; } }
Se eseguiamo il progetto di test e selezioniamo l'opzione programmatore test enumerazione otterremo il seguente risultato: La classe erede
Iniziamo ora a sviluppare la nostra classe erede, partendo dalla sua dichiarazione e dal suo costruttore base.public class Programmatore : Persona { private ArrayList mLinguaggiPrg; private Skills mSkill; private bool mSqlKnowledge; private DateTime mBeginDate; public Programmatore() : base() { InitProgrammatore( new string[] {"Nessuno"}, Skills.aspirante, false, DateTime.Now ); } private void InitProgrammatore(string [] pLinguaggiPrg, Skills pSkill, bool pSqlKnowledge, DateTime pBeginDate ) { this.mLinguaggiPrg = new ArrayList(); this.mLinguaggiPrg.AddRange( pLinguaggiPrg ); this.mSkill = pSkill; this.mSqlKnowledge = pSkill; this.mBeginDate = pBeginDate; } }Osservando il codice scritto, possiamo notare come, accanto al nome della classe abbiamo inserito il nome della classe genitore separato da duepunti: questa sintassi di dichiarazione indica che la classe eredita nel nostro caso da Persona.
All'interno della classe, abbiamo definito una variabile di tipo ArrayList; l'arraylist è una collezione di dati fornita dal Framework .NET; per poterla utilizzare, aggiungiamo in testa alla classe la clausola using del namespace che la contiene ovvero:
using System.Collections;Dato che anche in questo caso creeremo più di un costruttore, abbiamo aggiunto subito la funzione di ottimizzazione dell'inizializzazione variabili member.
Per poter vedere cosa succede ai dati della nostra classe figlia prima di sviluppare gli altri costruttori e gli altri metodi inseriamo una ulteriore funzione, la funzione di override della funzione ToString di Persona.
public override string ToString() { StringBuilder lg = new StringBuilder(""); for( int i=0; i<this.mLinguaggiPrg.Count; i++ ) { if( i>0 ) lg.Append("\n"); lg.Append( this.mLinguaggiPrg[i] ); } StringBuilder sb = new StringBuilder( base.ToString() ); sb.AppendFormat("\nSkill: {0} \nLinguaggi: {1} \nConosce SQL: {2}"+ "\nData inizio attivitā: {3}", this.mSkill.ToString(), lg.ToString(), this.mSqlKnowledge.ToString(), this.mBeginDate.ToShortDateString() ); return(sb.ToString()); }Per sviluppare questo metodo, utilizziamo un oggetto che si chiama StringBuilder e introduciamo la parolina base a cui abbiamo accennato nelle pagine precedenti.
Abbiamo già usato lo StringBuilder anche nella classe Persona, qui ne abbiamo utilizzato alcune caratteristiche in più perciò lo introduciamo per bene.
Quello che abbiamo fatto utilizzando lo StringBuilder poteva essere allo stesso modo ottenuto utilizzando l'operatore di concatenazione fra stringhe quindi:string str = "\nSkill: " + this.mSkill.ToString(); etc.Perché allora usare un oggetto specifico? Per un problema di velocità, infatti all'interno del .NET Framework, le stringhe sono immutabili una volta inizializzate: cioè, se io scrivo:
string str = "Stringa 1 " + "Stringa 2";Il Framework creerà una stringa con il primo valore, poi una con il secondo e infine una terza in cui inserirà la loro somma distruggendo poi le altre due. Questo significa che l'operazione è lenta. Lo stringBuilder invece è dinamico, si allarga in base alle stringhe che io gli metto dentro ed è quindi più veloce e, alla fine, il suo metodo ToString non fa altro che produrmi una stringa con il suo contenuto.
Le due funzioni più usate dello stringBuilder sono Append e AppendFormat, la prima aggancia in fondo allo stringbuilder una stringa, la seconda aggancia una stringa con segnalini di formattazione e relativi valori da formattare (Sull'argomento 'segnalini di formattazione' consultate la guida di C#). Questo ci dà modo di creare la stringa contenente i valori della classe Programmatore e usarla nel programma di test.Per creare il programma di test, aggiungiamo una nuova form al nostro progetto Persona e chiamiamola frmProgrammatore, diamole un titolo opportuno ed inseriamo una label come abbiamo fatto per la classe Persona. Poi andiamo sul designer della form base frmMenu e facciamo doppio click sul menuItem mnuTestProgrammatore andando ad inserire il codice per chiamare la nuova form:
private void mnuTestProgrammatore_Click(object sender, System.EventArgs e) { frmProgrammatore frm = new frmProgrammatore(); frm.MdiParent = this; frm.Show(); }A questo punto, nell'evento load della form appena creata andiamo ad creare l'istanza della classe Programmatore e visualizziamo il risultato:
private void frmMain_Load(object sender, System.EventArgs e) { try { Programmatore p = new Programmatore(); p.Nome = "Paolo"; p.Cognome = "Rossi"; p.Indirizzo = "Via Garibaldi, 5"; p.Citta = "Milano"; p.DataNascita = "12/9/1960"; this.lblPersona.Text = p.ToString(); } catch( Exception ex ) { MessageBox.Show( ex.Message, "ERRORE", MessageBoxButtons.OK,MessageBoxIcon.Error ); } }
Eseguendo il programma di test e selezionando l'opzione di menu apposita, otterremo...
un programmatore appena assunto come tale che deve iniziare a imparare a programmare...
base e this: due concetti importanti
Avevamo promesso, alcune righe or sono, di parlare della parola base, che compare proprio nella funzione che ci permette di visualizzare il contenuto della classe ed esattamente qui:StringBuilder sb = new StringBuilder( base.ToString() );Cosa significa questa istruzione? a parte il fatto che chiama uno dei costruttori dello StringBuilder - precisamente quello con definizione: public StringBuilder (string pStr ); - la stringa passata come parametro è il valore di ritorno della funzione base.ToString(). Infatti, l'immagine del risultato ci mostra che la chiamata a ToString di Programmatore visualizza i valori prodotti da Persona.Tostring(), a cui vengono accodati i valori propri di Programmatore. Potete quindi dedurre che base.ToString() significa: esegui la funzione ToString() della classe genitrice.
Ma la parola base non compare solo in quel punto: se avete prestato attenzione al codice del costruttore, avete di certo notato che accanto alla dichiarazione del costruttore compare una coda:
public Programmatore() : base()Ancora una volta la parola base; in questo caso significa, « Guarda che prima di eseguire ciò che hai dentro, devi eseguire il tuo corrispondente nella classe genitrice ed esattamente il costruttore senza parametri».
Ora, avendo a disposizione base e this, possiamo affermare che: in una classe erede di un'altra classe, è possibile fare riferimento specifico ad un metodo o una proprietà della classe genitrice utilizzando la parola chiave base.NomeMetodo() oppure base.NomeProprietà(), anche se questi ultimi fossero stati « sovrascritti » (override) nella classe erede. Mentre utilizzando la parola chiave this.NomeMetodo() facciamo esplicitamente riferimento al metodo che si trova nella classe erede.
Quando per qualunque motivo possa sussistere la possibilità o il dubbio che un metodo o una proprietà siano presenti e nella classe erede e in quella genitrice, è sempre opportuno specificare quale si intende utilizzare.
Per terminare la trattazione della clausola base, scriviamo un secondo costruttore per Programmatore, in cui facciamo riferimento ad un diverso costruttore della classe base per vedere quale è la sintassi:
public Programmatore( string pNome, string pCognome, string[]pLinguaggiPrg, Skills pSkill, bool pSqlKnowledge, DateTime pBeginDate ) : base(pNome, pCognome) { InitProgrammatore( pLinguaggiPrg, pSkill, pSqlKnowledge, pBeginDate ); }Come potete notare, per riferirci al costruttore a due parametri, li inseriamo semplicemente nel riferimento a base come se chiamassimo la funzione stessa.
Sviluppo della classe erede
Completiamo ora la nostra classe erede inserendo le proprietà per poter modificare le sue variabili member e i metodi per manipolare la collezione dei linguaggi.public ArrayList LinguaggiPrg { get { return( this.mLinguaggiPrg ); } set { this.mLinguaggiPrg = value; } } public Skills Skill { get { return( this.mSkill ); } set { this.mSkill = value; } } public bool SqlKnowledge { get { return( this.mSqlKnowledge ); } set { this.mSqlKnowledge = value; } } public DateTime BeginDate { get { return( this.mBeginDate ); } set { this.mBeginDate = value; } }Non ci sono particolari da spiegare sulle proprietà che abbiamo aggiunto, dato che sono la semplice esposizione dei valori delle variabili member. Facciamo solo notare che una property può essere sia un valore semplice come un booleano o una data, sia un oggetto complesso come il nostro ArrayList dei Linguaggi di programmazione.
public void AggiungiLinguaggio( string pLinguaggioPrg ) { if( !this.mLinguaggiPrg.Contains( pLinguaggioPrg ) ) { this.mLinguaggiPrg.Add( pLinguaggioPrg ); } } public void CliearLinguaggi() { this.mLinguaggiPrg.Clear(); }Anche queste due funzionalità sono molto semplici, la prima aggiunge un linguaggio, la seconda cancella l'intera collection, è naturalmente possibile in una classe di uso reale implementare anche qualche funzionalità più sofisticata, come ad esempio la cancellazione di un elemento della classe o l'estrazione e la ricerca di un elemento, ma per il nostro esempio ci limitiamo all'essenziale. Facciamo notare a coloro che sono davvero agli inizi, come il test inserito su Aggiungi linguaggio usa il «!» ovvero l'operatore not logico per esprimere la negazione del test ovvero: « Se non č contenuto nella lista allora aggiungilo ».
Prima di passare alla quarta parte del nostro articolo modifichiamo il nostro form di esempio per provare l'ebbrezza di creare un Programmatore.
Allargheremo la nostra form spostando la label che abbiamo inserito e aggiungeremo i controlli necessari a creare un programmatore. Non inseriremo controlli di validazione o altro, solo tre bottoni, uno per aggiungere i linguaggi alla lista provvisoria inserita in una listbox, uno per cancellarla se necessario, e il terzo per creare la classe, valorizzare le proprietà e visualizzarle sulla nostra label di test.
Questo è il risultato finito:![]()
Adesso che abbiamo terminato di trattare gli eredi, passiamo a vedere come gestire un Team di programmatori, parlando in modo più esteso delle collezioni di cui abbiamo visto il primo esempio nella lista linguaggi.
Precedente Successiva