Fondamenti di Informatica 2 Ingegneria Informatica (A-I) Prof. M.T. PAZIENZA a.a – 3° ciclo
Programmazione orientata agli oggetti Concetto base : separare i dettagli secondari dell'implementazione (ad esempio, la struttura dati impiegata) dalle proprietà fondamentali per un uso corretto (ad esempio, le funzioni che possono avere accesso ai dati): ogni utilizzo della struttura dati così come le routine interne di gestione avvengono attraverso un'interfaccia specifica sottostante.
Programmazione orientata agli oggetti Ottimizzare la risoluzione di classi di problemi richiede la definizione di proprietà essenziali (criteri di astrazione) che si devono implementare in una classe di problemi ben specificata (dominio del problema).
Programmazione orientata agli oggetti L’astrazione può essere effettuata a due livelli ASTRAZIONE SUI DATI consiste nell'astrarre le entità (oggetti) costituenti il sistema, descritte in termini di una struttura dati e delle operazioni possibili su di essa. ASTRAZIONE SUL CONTROLLO consiste nell'astrarre una data funzionalità dai dettagli della sua implementazione
Programmazione orientata agli oggetti Il concetto di tipo di dato in un linguaggio di programmazione tradizionale (procedurale) è quello di insieme dei valori che può assumere una informazione (una variabile). Nella Programmazione ad oggetti, il tipo di dati astratto (ADT) / oggetto estende questa definizione, includendo anche l'insieme di tutte e sole le operazioni possibili su dati di quel tipo. La struttura dati è incapsulata nelle operazioni su di essa definite.
Programmazione orientata agli oggetti La programmazione ad oggetti consente di manipolare gruppi di variabili correlate e non singole variabili. Le variabili vengono aggregate in oggetti. La programmazione ad oggetti si basa sull’incapsulazione delle variabili all’interno degli oggetti e sulla creazione di codice che descrive il comportamento interno ed esterno di tali oggetti.
Programmazione orientata agli oggetti L’incapsulamento è la proprietà per cui la struttura interna dell’oggetto è nascosta agli altri oggetti. La comunicazione tra oggetti avviene tramite l’invio di messaggi. L’implementazione dei messaggi avviene tramite le chiamate a funzioni dei metodi degli oggetti. L’incapsulamento fa sì che le operazioni dell’oggetto non possano essere in nessun modo influenzate da altri oggetti.
Programmazione orientata agli oggetti Un oggetto è rappresentato da variabili e da comportamenti assunti, ed ha un tipo (classe) Una classe rappresenta l’astrazione di oggetti aventi le stesse caratteristiche nel dominio del problema da risolvere
Programmazione orientata agli oggetti Le caratteristiche che definiscono una classe sono: gli attributi o dati membro, che sono le variabili associate ad una classe i metodi o funzioni membro, che sono le procedure o servizi che la classe mette a disposizione Le classi definiscono gli ADT e da esse si istanziano (cioè si creano) gli oggetti
Programmazione orientata agli oggetti 1.Identificare le classi e gli oggetti che descrivono il problema e la soluzione 2.Identificare e descrivere i dati membro di ciascun oggetto 3.Identificare ciascun membro funzione/metodo che può agire su ciascun tipo di oggetto 4.Per ciascuna funzione membro / metodo, descrivere i suoi scopi, argomenti, pre- condizioni e post-condizioni
Programmazione orientata agli oggetti Un linguaggio di programmazione supporta tecniche basate sugli oggetti se offre meccanismi espliciti per la definizione di entità (oggetti) che incapsulano una struttura dati nelle operazioni possibili su di essa.
C++ Il C++ estende tali meccanismi nella direzione della tipizzazione dei dati, offrendo la possibilità di definire istanze (cioè, variabili) di un dato tipo astratto. Il linguaggio presenta costrutti per la definizione di classi e di oggetti. Una classe è una implementazione di un tipo di dati astratto. Un oggetto è una istanza di una classe.
Programmazione orientata agli oggetti Programmazione orientata agli oggetti (bottom-up): individuazione delle entità astratte (oggetti) facenti parte del sistema, delle loro proprietà e delle interrelazioni tra di esse. Riutilizzabilità degli oggetti in diverse architetture software Programmazione tradizionale (top-down): individuazione delle funzionalità del sistema da realizzare raffinamenti successivi, da iterare finché la scomposizione del sistema individua sottosistemi di complessità accettabile
Concetti chiave Classe Oggetto Ereditarietà Polimorfismo “Binding” dinamico
Tipi di dato Un tipo di dato costituisce la rappresentazione concreta di un concetto. Es.: il tipo (predefinito) float, insieme alle operazioni +, -, *, /, rappresenta una versione limitata, ma concreta, del concetto matematico di numero reale.
Classe La classe e' un'astrazione che descrive le proprietà di tutti gli oggetti caratterizzati da: –uno stesso insieme di operazioni (protocollo od interfaccia) –una stessa struttura interna –uno stesso comportamento e che consente la creazione di un numero qualunque di istanze (oggetti).
Ereditarietà L’ereditarietà è il meccanismo mediante il quale una classe acquisisce tutte le caratteristiche di un’altra classe definita in precedenza La classe che eredita le caratteristiche è chiamata sottoclasse La classe che fornisce le proprie caratteristiche viene chiamata superclasse Una sottoclasse contiene tutto ciò che si trova nella sua superclasse Una classe può ereditare le proprie caratteristiche da più superclassi (ereditarietà multipla)
Polimorfismo Il polimorfismo è la capacità che hanno oggetti di classi diverse (ma correlate per il fatto di derivare da una classe base comune) di rispondere in maniera diversa ad uno stesso messaggio. Es.: se la classe rettangolo deriva dalla classe quadrilatero, un oggetto rettangolo è una versione più specifica di un oggetto quadrilatero. Il calcolo del perimetro è un’operazione che ha senso sulla classe quadrilatero, ma che assume una forma diversa per un rettangolo. Il polimorfismo si implementa attraverso classi virtuali.
Dichiarazione di una classe In C++ le classi sono identiche alle strutture con la differenza formale (oltre ad una sostanziale) di essere introdotte dalla parola-chiave class anziché struct Es.: class CPoint { float x; float y; } ; ogni istanza della classe CPoint rappresenta un punto nel piano e i suoi membri sono le coordinate cartesiane del punto.
Specificatori di accesso Specificano chi può accedere ai dati membri: private protected public Possono essere inseriti più volte all'interno della definizione di una classe. Differenza sostanziale fra classe e struttura: i membri di una struttura sono, per default, pubblici, mentre quelli di una classe sono, per default, protetti.
Data Hiding L'istanza di una classe é regolarmente visibile all'interno del proprio ambito, ma i suoi membri protetti non lo sono: non é possibile, da programma, accedere direttamente ai membri protetti di una classe. Es.: class Persona { int soldi; public: char telefono[20] ; char indirizzo[30]; }; istanza della classe Persona: Giuseppe il programma può accedere a Giuseppe.telefono e Giuseppe.indirizzo, ma non a Giuseppe.soldi.
Funzioni membro Le funzioni membro per una classe definiscono il comportamento che esibiscono gli oggetti di quella classe. Il comportamento di un oggetto prevede azioni del tipo: Permettere l’accesso ai data member di quell’oggetto Permettere l’aggiornamento dei data member di quell’oggetto Permettere che i data member di quell’oggetto siano testati in modi diversi Visualizzare i data member di quell’oggetto
Funzioni membro I membri protetti non sono accessibili direttamente, ma possono essere raggiunti indirettamente, tramite le funzioni-membro. Queste funzioni possono essere, come ogni altro membro, pubbliche o protette, ma, in ogni caso, possono accedere a qualunque altro membro della classe, anche ai membri protetti. Mentre una funzione-membro protetta può essere chiamata solo da un'altra funzione-membro, una funzione-membro pubblica può anche essere chiamata dall'esterno, e pertanto costituisce il tramite fra il programma e i membri della classe (interfaccia).
Dichiarazione di classe (esempio) class contatore { int val;//private per default public: void reset(); void incr(); void decr(); int visual(); }; Viene creata una classe di nome contatore costituita da : un intero di nome val e un insieme di operazioni (protocollo o interfaccia) che rappresentano che cosa può fare un oggetto di questa classe (azzerarsi, incrementarsi, decrementarsi, visualizzare il valore)
L’operatore ::risolutore di ambito Per indicare come opera un oggetto della classe, si deve scrivere il codice delle funzioni: void contatore::reset() { val=0;} void contatore::incr() { val++;} void contatore::decr() { val--;} int contatore::visual() { return val;} Preponendo al nome della funzione il nome della classe, seguito dall'operatore di risoluzione dell'ambito di azione (::), si fa in modo che la funzione venga riconosciuta come metodo di una classe (distinta dalle normali funzioni) e che la sua classe di appartenenza venga univocamente identificata (anche nel caso di più funzioni con lo stesso nome).
Dichiarazione di classe (esempio) class complesso { double re, im; public: void iniz_compl (double r, double i) { re=r; im=i;}; double reale() { return re;}; double immag() { return im;}; }; Viene creata una classe di nome complesso costituita da una variabile reale ed una immaginaria e un insieme di operazioni (protocollo o interfaccia) che rappresentano che cosa può fare un oggetto di questa classe. E’ possibile mantenere distinta la definizione delle funzioni dalla loro dichiarazione all’interno della classe.
Dichiarazione di classe (esempio) class complesso { double re, im; public: void iniz_compl (double r, double i); double reale() ; double immag(); }; void complesso::iniz_compl (double r, double i) { re=r; im=i;} double complesso:: reale() { return re;}; double complesso:: immag() { return im;}; Il nome della funzione deve essere preceduto dal nome della classe e dall’operatore di risolutore d’ambito o di visibilità ::
Dichiarazione di classe (esempio) Se, ad esempio, si volesse cambiare la realizzazione del tipo, basterebbe cambiare la parte privata della classe e la realizzazione (ma non la dichiarazione) delle funzioni membro. Poiché l’interfaccia non è cambiata, i programmi che usano la classe complesso restano immutati
Operazioni predefinite sulle classi Oltre alla selezione di funzioni membro e di campi dato, esistono altre funzioni predefinite sugli oggetti classe: inizializzazione assegnazione ad un altro oggetto uso come argomento di una funzione ritorno da una funzione Le operazioni di confronto non sono definite per gli oggetti classe.
Dichiarazione di oggetti Una volta definita la classe, si possono istanziare un numero qualunque di oggetti e questi avranno la stessa rappresentazione concreta e potranno eseguire le sole operazioni descritte nella classe. Es: contatore c1,c2; definisce due oggetti/istanze di tipo contatore ognuno dei quali ha la propria copia di val e può eseguire solo le operazioni reset(), incr(), decr(), visual().
Accesso ai membri di una classe Per usare un oggetto precedentemente definito, basta scrivere il suo nome seguito dall’operatore. e dall'operazione che deve eseguire: Es. c1.reset(); Questa istruzione ha il significato di invio del messaggio all'oggetto c1 di eseguire l'operazione reset() che nel nostro caso pone a zero il campo val dell'oggetto c1.
Costruttori e distruttori Gli oggetti si comportano come normali variabili rispetto alla visibilità ed al ciclo di vita. In presenza della dichiarazione: contatore c1; il compilatore costruirà un oggetto c1 il cui contenuto in questo esempio è indefinito, non essendo stato inizializzato. Analogamente all'uscita dalla funzione in cui c1 è stato dichiarato, vi sarà la sua distruzione e ciò in perfetto accordo con il ciclo di vita delle variabili locali. In entrambi i casi, il compilatore invoca un costruttore ed un distruttore di default.
Costruttori I costruttori (funzioni membro pubbliche di ciascuna classe) devono sottostare alle seguenti regole (rif. all’esempio della classe CPoint): –devono avere lo stesso nome della classe Es: CPoint::CPoint(); –non bisogna specificare il tipo di ritorno (neanche void); –ammettono argomenti e defaults Es: CPoint::CPoint(const float a, const float b=0.0f); –possono esistere più costruttori, in overload, in una stessa classe. Il C++ li distingue in base alla lista degli argomenti
Costruttori I costruttori sono eseguiti automaticamente nel momento in cui l'oggetto è dichiarato nel programma (per questo motivo i costruttori devono essere definiti pubblici). Es. : CPoint::CPoint() { x=3.5f ; y=2.1f } … CPoint p; Nel momento in cui è eseguita l'istruzione di cui sopra: p.x=3.5f e p.y=2.1f
Costruttori con argomenti Nel caso in cui un costruttore ammetta argomenti, i loro valori devono essere specificati nella stessa dichiarazione dell'oggetto: Es.: CPoint::CPoint(const float a, const float b) { x=a ; y=b; } CPoint p = CPoint(3.0f,1.2f); Il costruttore entra in azione quando viene allocata la memoria per l'oggetto. Es: Cpoint* ptr; (il costruttore non è ancora eseguito) ptr = new CPoint; (adesso sì)
Costruttori Dichiarazione e inizializzazione possono coesistere in un'unica istruzione: Cpoint* ptr = new CPoint(0.0f,0.0f); –alloca memoria per un oggetto della classe CPoint, –ne restituisce l'indirizzo, –che usa per inizializzare il puntatore ptr, –e inizializza l'oggetto eseguendo il costruttore CPoint con gli argomenti fissati ai valori 0.0f e 0.0f
Utilità di costruttori e distruttori In C++ ogni oggetto ha una sua precisa connotazione, caratterizzata da proprietà e metodi. I costruttori e i distruttori (entrambi funzioni membro pubbliche di ciascuna classe) hanno un campo di applicazione molto più vasto della semplice inizializzazione o rilascio di aree di memoria: possono servire ogni qual volta un oggetto richieda ben definite operazioni iniziali e finali, incapsulate nell’oggetto stesso.
Programmazione orientata agli oggetti modularità : le classi sono i moduli del sistema software; coesione dei moduli : una classe è un componente software ben coeso in quanto rappresentazione di una unica entità; disaccoppiamento dei moduli : gli oggetti hanno un alto grado di disaccoppiamento (i metodi operano sulla struttura dati interna ad un oggetto); il sistema complessivo viene costruito componendo operazioni sugli oggetti; riuso : l'ereditarietà consente di riusare la definizione di una classe nel definire nuove (sotto)classi; information hiding : sia le strutture dati che gli algoritmi possono essere nascosti alla visibilità dall'esterno di un oggetto; estensibilità : l'ereditarietà, il polimorfismo ed il binding dinamico agevolano l'aggiunta di nuove funzionalità, minimizzando le modifiche da applicare al sistema per estenderlo.
Librerie di classi Quando, nella dichiarazione di una classe, si lasciano solo i prototipi dei metodi, si suole dire che viene creata un'intestazione di classe. E’ buona consuetudine creare librerie di classi, separando in due gruppi distinti: le intestazioni, distribuite in header-files, il codice delle funzioni, compilate separatamente e distribuite in librerie in formato binario; Ai programmatori che utilizzano le classi non interessa sapere come sono fatte le funzioni di accesso, ma solo come usarle: separazione tra il punto di vista di chi realizza la classe ed il punto di vista di chi usa la classe.
Esempio Librerie di classi File contator.h class contatore { int val; public: contatore(); contatore(int); ~contatore(); void reset(); void incr(); void decr(); int visual(); }; File contator.cpp #include"contator.h" contatore::contatore() {val=0;} contatore::contatore(int a) {val=a;} contatore::~contatore() {cout<<"distruttore"<<endl; } void contatore::reset() { val=0; } void contatore::incr() { val++; } void contatore::decr() { val--; } int contatore::visual() { return val; }
Esempio File prova.cpp #include #include"contator.h" main() { contatore c1; contatore c2(12); c1.inc(); c2.dec(); cout<<"c1="<<c1.visual()<<endl; cout<<"c2="<<c2.visual()<<endl; }