Programmazione.html.it

  Home page

   Guida Base

   Guida al Java

   Guida al C

   Guida al C++

   Guida al Delphi

   Guida a VB .NET

   Guida al Visual Basic

   Guida al Python

   Guida al'UML

  Forum di discussione
  HTML.it




Guida a Python
  LEZIONE 15:  Classi e cenni di programmazione ad oggetti


Nei capitoli introduttivi abbiamo detto che Python è un linguaggio orientato agli oggetti. In realtà esso permette sia la programmazione tradizionale (procedurale) che il nuovo paradigma ad oggetti. Quindi python si inquadra nei linguaggi ibridi, come il C++.
In questo capitolo cercheremo di introdurre teoricamente i concetti della programmazione orientata agli oggetti, accompagnando ogni concetto con qualche piccolo esempio. Solo in seguito trasferiremo i concetti teorici in python, illustrando la sintassi necessaria all'utilizzo degli oggetti in python stesso.

Cenni di programmazione orientata agli oggetti

La programmazione tradizionale si è sempre basata sull'utilizzo di strutture dati (come le liste, le tuple ecc...) e su funzioni e procedure (tutti concetti gia' visti in precedenza).
Questo metodo di sviluppo del software viene detto funzionale (o procedurale), con esso si organizza un intero programma in moduli che raccolgono gruppi di funzioni. Ogni funzione accede ad uno o più gruppi di dati.

I metodi di sviluppo funzionale hanno pero' notevoli debolezze:
  • A causa dei stretti legami tra le funzioni e i dati, si arriva ad un punto in cui ogni modifica software provoca degli effetti collaterali su altri moduli con enormi difficolta di debug della applicazione.
  • Difficolta' di riutilizzo del software. Ogni volta che si vuole riciclare una funzione bisogna apportare delle modifiche strutturali per adeguarla alla nuova applicazione.
La programmazione orientata agli oggetti è un modo alternativo di scomposizione di un progetto software: in essa l'unità elementare di scomposizione non è più l'operazione (la procedura) ma l'oggetto, inteso come modello di un'entità reale (un oggetto del mondo reale).
Questo approccio porta ad un modo nuovo di concepire un programma: il software è ora costituito da un insieme di entità (gli oggetti) interagenti, ciascuna provvista di una struttura dati e dell'insieme di operazioni che l'oggetto è in grado di effettuare su quella struttura. Poiché ciascun oggetto incapsula i propri dati e ne difende l'accesso diretto da parte del mondo esterno, si è certi che cambiamenti del mondo esterno non influenzeranno l'oggetto o il suo comportamento. D'altra parte per utilizzare un oggetto basta conoscere che dati esso immagazzina e che operazioni esso fornisce per operare su questi dati, senza curarsi dell'effettiva realizzazione interna dell'oggetto stesso.

Questo nuovo modo di sviluppare programmi software è più vicino alla nostra realtà quotidiana. Pensiamo ad un oggetto del mondo reale, ad esempio una automobile.
Una automobile ha un insieme di caratteristiche: il colore , la cilindrata ecc... Inoltre essa dispone di operazioni da svolgere esclusivamente con essa, ad esempio:
  • accensione.
  • cambio della marcia.
  • parcheggio.
È giusto correllare le sue caratteristiche e le suo operazioni in una sola entita' (un solo oggetto).

Inoltre la programmazione ad oggetti favorisce la programmazione di gruppo, poiché gli oggetti non possono dipendere dall'implementazione di altri oggetti, ma solo dalle loro operazioni, perciò un programmatore può sviluppare un oggetto senza preoccuparsi della struttura degli altri elementi che compongono il sistema.



Vediamo ora i principali concetti su cui si basa questo modo di programmare:

Classe

Una classe definisce un tipo di oggetto. Se intendo creare un nuovo oggetto devo indicare al programma le sue caratteristiche.
Per fare questo devo definire una classe di appartenza.
In particolare la classe definisce:
  • Le variabili contenute nell'oggetto, che si chiamano dati membri
  • Le funzioni contenute nell'oggetto, che si chiamano metodi. Queste funzioni permettono di svolgere operazioni solo sui dati membri dell'oggetto stesso. Ogni metodo agisce esclusivamente sui dati membri della classe di appartenza. Questo vincolo rappresenta la grande forza della programmazione ad oggetti, la quale costringe il programmatore ad organizzare il software per componenti riciclabili ben distinti.

Ad esempio, se volessi costruire un programma che conserva un archivio di persone, potrei creare la classe delle "persone".
Tale classe potrebbe avere i seguenti dati membri:
  • nome della persona
  • cognome della persona
  • indirizzo
  • telefono
  • stato civile
Inoltre si potrebbero definire i seguenti metodi:
  • cambia l'indirizzo della persona
  • cambia il telefono della persona
  • cambia lo stato civile della persona
Questi metodi agiscono esclusivamente sui dati membri della classe.

Oggetti

Una volta definita una classe, siamo in grado di creare tanti oggetti che riflettono le caratteristiche della classe stessa.
Infatti gli oggetti non sono altro che "istanze" della classe. Tornando al nostro esempio delle persone, possiamo creare i seguenti oggetti:
  • Stefano Riccio
  • Rossi Mario
  • Elena Bianchi
Queste sono tre istanze della classe persona. Ogni oggetto ha una copia dei dati membri dove conserva tutte le proprie informazioni.

Incapsulamento

La definizione di classe permette di realizzare l'incapsulamento. Esso consiste nella protezione dei dati membri dagli accessi non desiderati.
Questo avviene perché dall'esterno di un oggetto si può accedere ad un dato membro solo mediante un metodo e non direttamente usando il nome del dato membro stesso. Ad esempio, se volessi modificare l'indirizzo di una persona non posso semplicemente assegnare il nuovo indirizzo al nome della variabile (dato membro), come si farebbe nella programmazione tradizionale.
Bensi' sono obbligato ad utilizzare l'apposito metodo.

In realtà l'incapsulamento non è obbligatorio. Si può decidere, singolarmente su ogni dato membro, se renderlo protetto oppure no.
Generalmente gli oggetti hanno un insieme di metodi e dati che sono resi di dominio pubblico, mentre altri sono inaccessibili dall'esterno. Questo principio è conosciuto con il nome di "information hiding" (mascheramento dell'informazione) e fa si che un oggetto sia diviso in due parti ben distinte: una interfaccia pubblica e una rappresentazione privata.


Il mascheramento dell'informazione permette di rimuovere dal campo di visibilità esterno all'oggetto alcuni metodi o dati che sono stati incapsulati nell'oggetto stesso. L'incapsulamento e il mascheramento dell'informazione lavorano insieme per isolare una parte del programma o del sistema dalle altre parti, permettendo così al codice di essere modificato ed esteso senza il rischio di introdurre indesiderati effetti collaterali.
Questo metodo risulta molto utile per costruire librerie software da rilasciare a terze parti. Infatti, chi utilizza la libreria vede solamente l'interfaccia pubblica e ignora tutto il resto.

Ereditarieta'

Un altro concetto importantissimo della programmazione ad oggetti e l'ereditarieta'.
Con essa è possibile definire una classe sfruttando le caratteristiche di un'altra classe (che diventa la classe madre).
Ad esempio, se volessimo creare la classe degli studenti, dovremmo costruire una classe simile a quella delle persone, con l'aggiunta di qualche informazione in più.


Per evitare di scrivere tutto nuovamente, posso indicare al programma di ereditare tutte le caratteristiche dalla classe "persona" e aggiungere alcuni dati membri e alcuni metodi.
In questo modo posso affermare che gli studenti sono delle persone (frase filosofica !), quindi ereditano da essi le loro caratteristiche. Inoltre posso aggiungere ad esempio i seguenti dati membri specifici degli studenti:
  • scuola di appartenza
  • classe di appartenza
Inoltre si potrebbero definire i seguenti metodi specifici della classe studenti:
  • cambia scuola
  • promosso
L'ereditarieta' permette di utilizzare anche i metodi delle classi progenitrici, cosi per cambiare il nome di uno studente posso semplicemente utilizzare il metodo apposito della classe persona.

Un metodo, in una classe discendente, può nascondere la visibilità di un metodo di una classe antenata, semplicemente definendo il metodo con lo stesso nome. Lo stesso nome può così essere utilizzato in modo appropriato per oggetti che sono istanze di classi diverse. Ereditando dalle classi antenate i metodi che operano correttamente anche nelle classi discendenti, ed eliminando quei metodi che devono agire in modo differente, il programmatore estende realmente una classe antenata senza doverla completamente ricreare.

Ereditarieta' singola e multipla

La forma più comune di creazione di una classe è la specializzazione, mediante la quale viene creata una nuova classe poiché quella esistente è troppo generica. In questo caso è possibile utilizzare il meccanismo dell'ereditarieta' singola. Questo è il caso dell'esempio proposto delle persone e degli studenti.
Un altro tipico metodo di creazione è la combinazione con la quale la nuova classe è creata combinando gli oggetti di altre classi. In questo caso, se esiste, si utilizza il meccanismo dell'ereditarieta' multipla.
Per porre in relazione gli oggetti, sono quindi utilizzati due tipi di ereditarietà: quella singola e quella multipla. Attraverso l'ereditarieta' singola, una sottoclasse può ereditare i dati membri ed i metodi da un'unica classe, mentre con l'ereditarieta' multipla una sottoclasse può ereditare da più di una classe.
L'ereditarietà singola procura i mezzi per estendere o per perfezionare le classi, mentre l'ereditarieta' multipla fornisce in più i mezzi per combinare o unire classi diverse.

Le opinioni riguardo la necessita' o meno del meccanismo dell'ereditarieta' multipla sono piuttosto controverse.
I contrari sostengono che si tratta di un meccanismo non strettamente necessario, piuttosto complesso e difficile da utilizzare correttamente. Quelli a favore dicono esattamente l'opposto ed infatti sostengono che l'ereditarieta' multipla è una caratteristica fondamentale di un linguaggio object oriented.
L'ereditarieta'à multipla incrementa sicuramente le capacita' espressive di un linguaggio, ma comporta un costo elevato in termini di complessita' della sintassi e di overhead di compilazione e di esecuzione.

Python permette l'utilizzo della ereditarieta' multipla, ma non intendo discuterne ulteriormente a causa della sua complessita' e del suo scarso utilizzo pratico.

Polimorfismo e overloading degli operatori

I linguaggi object-oriented permettono la creazione di gerarchie di oggetti (mediante l'ereditarieta') cui sono associati metodi che hanno lo stesso nome, per operazioni che sono concettualmente simili ma che sono implementate in modo diverso per ogni classe della gerarchia.
Di conseguenza, la stessa funzione richiamata su oggetti diversi, può causare effetti completamente differenti. Questa funzionalita' viene chiamata polimorfismo.

Un caso molto interessante di polimorfismo è rappresentato dall'overloading degli operatori. Con questa tecnica è possibile definire degli operatori matematici sugli oggetti che permettono di compiere operazioni sulle istanze in base al tipo di oggetto con il quale lavorano.
Approfondiremo questo aspetto in seguito.



Torna a inizio pagina