PROGRAMMAZIONE A OGGETTI (OOP) Lezione 4 prj Mesa (Prof. Ing N. Muto) In questa lezione applicheremo tutti i concetti teorici fin qui esposti, sarà presentato codice sviluppato in C# sviluppato con l'IDE (Integrated Development Environnemt) di Visual Studio Express Edition 2008, liberamente scaricabile ed installabile. Programmazione a oggetti in C# Dopo aver affrontato i concetti di base e le caratteristiche portanti della OOP è ora arrivato il momento di imparare concretamente come programmare ad oggetti con un linguaggio reale ed in particolare il C#. Questo linguaggio è la “dote” che ha portato in casa Microsoft Anders Hejlsberg l'inventore di “Delphi”, un linguaggio in Object Pascal che ha avuto un enorme successo. La sintassi del C# prende spunto sia da quella di Delphi che di C++, di Java e di Visual Basic per gli strumenti di programmazione visuale e per la sua semplicità. Il risultato è un linguaggio con meno simbolismo rispetto a C++, meno elementi decorativi rispetto a Java, ma comunque orientato agli oggetti in modo nativo. Rispetto al C ed al C++, C# risulta più semplice e sicuro, in particolare possiamo elencare le seguenti differenze: • I puntatori possono essere utilizzati solo in particolari blocchi di codice marcati come "unsafe". • In molte operazioni aritmetiche vengono controllati eventuali "overflow". • Gli oggetti dinamici non vengono deallocati esplicitamente; la loro rimozione viene gestita automaticamente (implicitamente) dal "garbage-collector" quando non esistono più riferimenti a tali oggetti. Questa gestione evita i due problemi ben noti dei dangling pointer e del memory leak, anche se con un'ovvia riduzione delle prestazioni. • Come in Java, è possibile ereditare da una sola classe (diversamente da quanto avviene in C++) ma è possibile implementare un numero indefinito di interfacce. • Le sole conversioni implicite consentite sono quelle "safe", ovvero che non espongono al rischio di perdita di dati causata dalla diversa tipologia di dato. Ad esempio non sono consentite conversioni implicite fra integer e boolean o fra enumerati ed integer. • C# non possiede i "template" (tipici del C++) ma nella versione 2.0 sono stati introdotti i "generic". Rispetto a Java invece possiamo elencare questi punti: Java utilizza i commenti Javadoc-sintax per generare la documentazione dal codice sorgente, mentre C# utilizza la sintassi XML nei commenti per lo stesso scopo. • Quello che in Java è chiamato package, in C# viene chiamato namespace o "spazio di nomi". Un ulteriore livello di organizzazione in C# è costituito dagli "assembly", che possono contenere al proprio interno diversi spazi di nomi. Dopo queste indicazioni tecniche specifiche per il cui approfondimento rimandiamo ai link presenti in Internet, veniamo finalmente ad usare il C# per creare una classe. Per prima cosa vediamo la “sintassi” ossia come si scrive o meglio “implementa” una classe: public class retta { } //qui vanno aggiunte le proprietà e i metodi... La classe precedente è quindi una classe “vuota”, serve solo a far vedere la forma; ora vediamo invece un esempio di classe completa: //questa classe rappresenta l'entità “retta” espressa nella forma implicita ax + by +c = 0; class retta { // questa parte contiene la dichiarazione delle proprietà o attributi public double a; public double b; public double c; private double m; //questo attributo è dichiarato private (privato) per nasconderlo al resto del codice //qui di seguito è presente il costruttore della classe public retta(double pa, double pb, double pc ) { a= pa; b= pb; c= pc; } //questo metodo consente di calcolare il coefficiente angolare noti i coefficienti della retta in forma implicita public double CalcolaM(){ if(a!=0) { m=-b/a; return m; } else { Console.WriteLine("Impossibile eseguire il calcolo"); return 0; } } } Precisiamo alcune cose che abbiamo visto nel codice sopra riportato: la parola chiave “private” detto anche modificatore di visibilità, rende gli attributi ed i metodi a cui è applicata visibili e quindi utilizzabili SOLO dai metodi della classe stessa. Se non si specifica alcun modificatore, si presuppone che valga “private”. Il metodo “public retta(double pa, double pb, double pc ) ” è SPECIALE e si chiama COSTRUTTORE della classe. Come si può notare ha lo stesso nome della classe, non restituisce alcun valore ed è di tipo “public”. Il suo scopo è quello di “istanziare” la classe, ossia creare l'oggetto reale a partire dalla “ricetta” definita nella classe. Proprio perché è un codice che viene eseguito nel momento iniziale della “vita” di un oggetto, è adatto anche a contenere delle istruzioni (azioni) che vanno compiute subito dopo la creazione dell'oggetto stesso, come ad esempio caricare in alcune variabili dei valori iniziali. Infatti, se riguardiamo le istruzioni in cui si creano gli attributi, notiamo che non viene specificato alcun valore iniziale. Vediamo ora come si effettua l'istanza della classe, ossia si crea un oggetto realmente esistente nella memoria centrale del calcolatore, a partire dalla sua ricetta, ossia dalla classe: namespace RisolutoreSis1G { class Program { static void Main(string[] args) { Double A=0; Double B=0; Double C=0; Double XSoluzione = 0; Double YSoluzione = 0; Console.WriteLine("* PROGRAMMA PER LA RISOLUZIONE DI SISTEMI DI DUE RETTE *"); Console.WriteLine("Inserisci a, b, c per la prima retta"); A=Convert.ToDouble(Console.ReadLine()); B=Convert.ToDouble(Console.ReadLine()); C=Convert.ToDouble(Console.ReadLine()); retta r1 = new retta(A,B,C); //questa istruzione crea l'oggetto r1 a partire dalla classe “retta” Console.WriteLine("Inserisci a, b, c per la seconda retta"); A = Convert.ToDouble(Console.ReadLine()); B = Convert.ToDouble(Console.ReadLine()); C = Convert.ToDouble(Console.ReadLine()); retta r2 = new retta(A, B, C); //questa istruzione crea l'oggetto r2 sempre a partire dalla classe “retta” //controllo che le rette non siano coincidenti if((r1.a/r2.a==r1.b/r2.b)&&(r1.b/r2.b==r1.c/r2.c)) { Console.WriteLine("Rette coincidenti: Infinite soluzion1!"); } //controllo che le rette non siano parallele else if (r1.CalcolaM() == r2.CalcolaM()) //questa istruzione usa il metodo CalcolaM { Console.WriteLine("Rette parallele: Nessuna soluzione!"); } else { //Eseguo il calcolo per trovare l'intersezione YSoluzione = (((r2.a*r1.c)/r1.a)-r2.c)/(-r2.a*r1.b+r2.b); XSoluzione = -r1.c / r1.a - r1.b / r1.a * YSoluzione; Console.WriteLine("Le soluzioni sono X= {0} Y={1}", XSoluzione, YSoluzione); } Console.ReadLine(); } } Commentiamo le parti evidenziate in giallo che sono relative alla creazione dell'oggetto: retta r1 = new retta(A,B,C); r1: è il nome della variabile composta che stiamo per creare; si tratta dell'OGGETTO. retta: è il nome della classe che stiamo usando per creare l'oggetto r1 new: è un operatore che, partendo dalla ricetta “retta” è in grado di creare l'oggetto r1. Notare che new ha SEMPRE bisogno del costruttore e se avessimo creato una classe senza un costruttore dichiarato esplicitamente, new avrebbe comunque usato un costruttore creato implicitamente dal compilatore. Una volta creato l'oggetto ci rimane da capire come si usa e per questo basta esaminare la riga di codice evidenziata in verde e qui riportata: else if (r1.CalcolaM() == r2.CalcolaM()) Ciò che notiamo è l'utilizzo del “.” ossia del carattere punto: r1.CalcolaM() è come dire, usa l'oggetto r1 ed in particolare “accedi” al metodo CalcolaM() Analogamente possiamo accedere al metodo CalcolaM() dell'altro oggetto retta r2. Ovviamente questo è possibile perché il metodo CalcolaM() è dichiarato “public”! Cosa accadrebbe infatti se provassimo a scrivere: ... if( r1.m == r2.m )... Accadrebbe che il compilatore ci avviserebbe che m non è accessibile! Come applicazione concreta di quanto fin qui spiegato si lasciano i seguenti esercizi da sviluppare con l'ambiente di sviluppo gratuito Visual Studio Express Edition 2008 o successivi, che sono gratuiti e completi. Esercizio 1: Definire una classe per gestire un cellulare. Disegnare anche il modello UML. La classe deve comprendere almeno tre attributi di cui uno almeno private e contenere due metodi oltre al costruttore. Esercizio 2: Definire una classe per gestire il numero complesso nella forma: Z = a+jb. Disegnare anche il modello UML. La classe deve contenere due metodi in grado di calcolare e restituire il modulo e l'argomento del numero complesso.