Sviluppo

Java e la programmazione ad oggetti

Marco Turchetto
 

Introduzione 

Questo articolo nasce con lo scopo di fornire una minima conoscenza di base necessaria per poter comprendere uno degli strumenti di cui si parla molto in questo periodo: Java, il linguaggio sviluppato e distribuito da Sun Microsystems, disponibile per numerose piattaforme fra le quali figura il caro buon vecchio OS/2.

Mi sembra un tantino fuorviante (e presuntuoso) attaccare a parlare sin dal primo articolo di questo linguaggio di programmazione senza nemmeno fornire qualche nozione di base. Come avrete intuito dal titolo parlerò, più che di Java, della programmazione ad oggetti. Essendo Java un ottimo esponente di questa categoria di linguaggi, mi sembra doveroso trattare qualche principio di base della OOP (Object Oriented Programming).

Ma cosa c'entra Java con OS/2?

Domanda legittima. Java c'entra, eccome. Prima di tutto, bisognerebbe specificare che Java non è solo un linguaggio ma anche un interprete/ambiente di esecuzione. Rimando a uno dei prossimi articoli una definizione più ortodossa, per il momento l'importante è sottolineare che Java non si pone allo stesso livello di linguaggi come C/C++, Pascal, Visual Basic o altri. Il modo di procedere nella programmazione è simile (si scrive il sorgente, lo si compila), la differenza è al momento dell'esecuzione: il compilatore Java (javac), infatti, non produce un codice eseguibile specifico per ogni piattaforma, bensì un particolare codice (bytecode), in grado di essere eseguito su qualsiasi piattaforma!

La maggior parte dei lettori avrà sentito queste parole parecchie volte (una in più quindi non farà male) ma magari non avrà riflettuto sulle implicazioni. Nel momento in cui un programma non necessita di una particolare piattaforma software (= sistema operativo = Microsoft per l'80% o forse più dell'installato), l'utente è libero di scegliere quella che più lo soddisfa. L'importante è che Sun supporti il sistema operativo prescelto. Dato che qui si parla di OS/2, e che OS/2 è supportato, anche se non ai massimi livelli, molti utenti magari costretti dal mercato a scegliere altri sistemi operativi, potranno tornare (o passare) a OS/2. Al momento la situazione non è proprio così semplice (anzi, negli uffici italiani la parola Java non è ancora entrata), ma le premesse ci sono tutte, tanto che Microsoft sta correndo in fretta ai ripari, ma questo non è argomento pertinente alla rivista.

OOP: concetti base

Innanzi tutto diamo un'occhiata alla parola stessa: programmazione orientata agli oggetti. Gli oggetti giocano come prevedibile un ruolo fondamentale: sono i mattoni con i quali vengono costruiti i programmi.

Scopo della programmazione ad oggetti è di rendere più intuitiva l'attività del programmatore, ma anche dell'analista software. Ogni oggetto software tende a rappresentare un oggetto del mondo reale, con le proprie caratteristiche e le proprie funzioni.

Oggetti

Ma cos'è alla fine un oggetto? Un oggetto (detto anche classe) è in soldoni un insieme di dati e di funzioni che operano su questi dati. Potrebbe essere considerato in prima analisi come una struttura a record dotata della capacità di modificare il proprio contenuto. Ma le proprietà interessanti degli oggetti non finiscono qui: gli oggetti possono avere delle "parentele", possono comunicare fra loro, modificare il loro comportamento a seconda di chi invoca i loro servizi, e altre proprietà assolutamente nuove per chi proviene da ambienti di programmazione strutturata.

Un esempio

Per semplificare l'esposizione, introdurrò subito un esempio: pensiamo ad un'automobile, concetto familiare a tutti i lettori (potrei sceglierne molti altri magari più adatti, ma essendo un appassionato del settore non resisto mai…).
Ogni automobile ha determinate caratteristiche: colore, numero di porte, cilindrata, segmento di appartenenza, casa costruttrice e così via. Un'automobile però non è solo un oggetto statico, ma può svolgere alcune azioni, quali svoltare a destra, accelerare, frenare, cambiare rapporto eccetera. Bene, la prima lista raggruppa i dati dell'oggetto auto, mentre la seconda rappresenta i metodi.

Dati

I dati costituiscono lo stato di un oggetto, in altre parole lo caratterizzano e permettono di distinguerlo da un altro. Si tratta in definitiva delle "vecchie" variabili, con in più alcune proprietà che andremo a descrivere più avanti.

Metodi

I metodi, o funzioni, o messaggi, caratterizzano invece il comportamento di un oggetto, definiscono cioè come questo oggetto si comporta, come modifica i propri dati o come interagisce con altri oggetti. In definitiva, i dati costituiscono la parte statica di un oggetto, mentre i messaggi costituiscono quella dinamica.
Vediamo ora alcune interessanti proprietà degli oggetti.

Ereditarietà

Uno dei concetti chiave della programmazione ad oggetti è l'ereditarietà: in sostanza, ogni oggetto può fare da "padre" ad un altro, che erediterà i dati e le funzioni del genitore. Il grado di parentela teorico tra gli oggetti è pressoché infinito: all'interno di un'applicazione si possono generare "alberi genealogici" profondi e ramificati a piacere. Attraverso questa proprietà degli oggetti, è possibile specializzare per affinamenti successivi un determinato oggetto (modalità "top-down", dal generico al particolare).
Ma vediamo come si applica questo nuovo concetto all'esempio precedente. Supponiamo di avere un'applicazione che gestisce un database di autoveicoli, dal camion a rimorchio alla monoposto di Formula 1. Potremo avere alla base della nostra famiglia di oggetti un generico oggetto Autoveicolo, fornito di alcune caratteristiche presenti in ogni tipo di auto, quali cilindrata, lunghezza, colore eccetera, e alcune azioni "di base", come svolta a destra, accelera, frena e così via. A questo punto possiamo cominciare a "specializzare" la nostra gerarchia, definendo per esempio una sottoclasse VeicoloCommerciale, una VeicoloDaCompetizione e un'altra VeicoloStradale. Ognuna di queste classi erediterà i metodi e i dati definiti nella classe Autoveicolo, con l'interessante possibilità di ridefinire alcuni metodi della classe padre.

Overloading

Questa caratteristica degli oggetti è conosciuta anche con il termine di overloading. Supponiamo che la classe padre Autoveicolo definisca un generico metodo frena. Gli oggetti di tipo VeicoloDaCompetizione avranno sicuramente un metodo frena differente da quelli di tipo VeicoloCommerciale. La classe Autoveicolo, in una buona organizzazione gerarchica di classi, dovrebbe essere definita come astratta, nel senso che fornisce un modello alle classi che ereditano da essa, senza però dare una reale implementazione del codice, demandata appunto ai figli. Essi dovranno quindi ridefinire (o definire) i metodi del padre che intenderanno modificare. Questo meccanismo è disponibile a qualsiasi livello della gerarchia. Si noti che comunque si ridefiniscano i singoli metodi di una classe ereditata, il comportamento "esterno" della classe sarà in ogni caso lo stesso: ci sarà sempre un metodo sterza, frena e accelera, sebbene ogni classe fornisca la propria versione specializzata.
Un'altra forma altrettanto interessante di overloading permette di definire diversi comportamenti di un singolo metodo all'interno della stessa classe. Sarà sufficiente definire lo stesso metodo con lo stesso nome ma con una lista di parametri differente: quando andremo a richiamare un metodo con un particolare parametro, sarà compito del compilatore andare a vedere quale metodo utilizzare. Pensiamo per esempio ad un metodo cambiaColore, sempre nella nostra classe Autoveicolo. A seconda della parte del veicolo che si vorrà colorare, si dovranno eseguire operazioni differenti: colorare un copricerchio è diverso dal colorare una portiera. Attraverso l'overloading, potremo definire due diversi metodi, cambiaColore(Cerchio c, Colore col) e cambiaColore(Portiera p, Colore col). Al momento di utilizzare queste funzioni, il compilatore esaminerà i parametri contenuti nella chiamata e richiamerà la funzione appropriata (o segnalerà un errore nel caso non sia stata definita).

Incapsulamento

Al programmatore è consentito controllare il modo in cui i dati e i metodi di un oggetto possono essere acceduti dall'esterno. Vi è infatti la possibilità di definire alcuni dei membri di un oggetto (sia dati che funzioni) come inaccessibili al di fuori di esso (data hiding). Chiameremo questi membri privati. In generale, un metodo o un dato privato sarà accessibile solo all'interno della classe che ne contiene la definizione. Ad esempio, potrei decidere di rendere disponibile, in un'ipotetica sottoclasse di VeicoloDaCompetizione, Formula3, variabili come nomePreparatore o nomeTeam, ma nascondere da sguardi indiscreti rapportoCompressione o settaggiCamber.
E' dunque possibile nascondere alcune caratteristiche a chi non deve potervi accedere, lasciando invece libero accesso alle altre, in modo che ad esempio si possa modificare il nome del preparatore di una vettura di Formula 3, ma non i settaggi della macchina, modificabili solo all'interno della stessa classe.
E' in generale buona abitudine nella programmazione ad oggetti definire tutti i dati come privati, e consentire di leggerne o modificarne il contenuto attraverso delle funzioni pubbliche, proteggendo dunque i dati di ogni oggetto da modifiche indesiderate.

Un po' di codice

Vediamo ora, pur rimanendo a un livello molto generico, come si rendono alcuni concetti visti finora in Java.

Prima di tutto, classe e oggetto non sono proprio la stessa cosa, almeno in Java. In particolare, si considera una classe il "contenitore" di dati e metodi, mentre un oggetto è un'istanza di una classe. La creazione degli oggetti avviene come in C++, dichiarando una variabile del tipo desiderato e creandola con l'operatore new. Le classi in Java sono identificate dalla parola chiave class. Per creare un oggetto (o un'istanza) di classe VeicoloDaCompetizione scriverò:

VeicoloDaCompetizione McLarenF1 = new VeicoloDaCompetizione();

Per dichiarare una variabile o un metodo (ma anche una classe) come privata si utilizzerà la parola private, al contrario per renderla visibile dall'esterno utilizzerò public. L'ereditarietà è realizzata mediante la keyword extends, seguita dal nome della classe antenata, mentre per l'overload delle funzioni di una classe è sufficiente ridefinirle, assicurandosi che abbiano gli stessi parametri presenti nella sopraclasse che per prima definisce le funzioni.

Per concludere

Sperando di avervi fornito una seppur sommaria infarinatura sulla OOP e di non avervi annoiato troppo nel fare ciò, vi saluto e vi invito a contattarmi in email per qualsiasi richiesta o suggerimento, il mio indirizzo è mt527784@silab.dsi.unimi.it.


[Pagina precedente] [Sommario] [Pagina successiva]