Classi e oggetti
Le innovazioni concettuali nel settore informatico sono sempre state caratterizzate
dalla nascita di paradigmi. Un paradigma (o modello) di programmazione fornisce
un metodo per:
● Concettualizzare il processo di computazione.
● Organizzare e strutturare i compiti che
un calcolatore deve svolgere
Nel corso del tempo sono stati diversi i paradigmi di programmazione che si sono messi in evidenza:
● Imperativo (Pascal, C,. . .)
● Funzionale (Lisp, Javascript,. . .)
● Logico (Prolog)
L'OOP (Object Oriented Programming) è un ulteriore paradigma di programmazione che si è imposto a partire dall’ultima decade del secolo scorso.
La OOP (programmazione orientata agli oggetti) è un’evoluzione dei precedenti linguaggi imperativi, nel senso che aggiunge nuovi concetti a quanto era stato precedentemente collaudato e definito nei linguaggi imperativi: concetti di variabile, costante, strutture di controllo, dati strutturati (array, record, etc..).
L’idea di base della programmazione ad oggetti è quella di strutturare i programmi in modo da riflettere l’ordine delle cose del mondo reale. Infatti, in informatica, come nel mondo reale si possono creare oggetti semplici, ognuno dei quali ha caratteristiche e funzionalità ben determinate. Gli oggetti semplici possono poi essere combinati tra loro per formare oggetti complessi con altri ruoli rigorosamente definiti.
Fino all'inizio dei primi anni '90 il modello di programmazione più diffuso
era quello imperativo basato sulla distinzione tra algoritmo e basi di dati:
Algoritmi + strutture di dati = programmi
solo per citare un famoso libro di testo scritto nel 1976 da Niklaus Wirth,
padre del linguaggio Pascal.
Cambiando impostazione, nella OOP, questa uguaglianza è stata riformulata
come:
Dati + metodi = oggetti
ottenendo come risultato una maggiore riusabilità
del software, cioè, permettendo di scrivere un programma facilmente modificabile
ed ampliabile da qualunque programmatore e non solo da chi lo ha concepito.
Inoltre con l'uso di oggetti si incrementa la sicurezza
del software, perché ciascun oggetto può essere usato e riutilizzato senza
dover agire sui suoi componenti interni ma solo interagendo con la sua interfaccia
esterna.
Uno dei principali concetti della OOP e quello di classe da cui deriva conseguentemente il concetto di oggetto.
Una classe è un modello astratto, generico, per una famiglia di oggetti con caratteristiche comuni che definisce implicitamente un tipo di dato.
Un oggetto (o istanza) è la rappresentazione concreta e specifica di una classe.
La classe è, dunque solo un modello formale che specifica lo stato e il comportamento di tutte le sue istanze (oggetti).
Una classe si compone di due parti, quella in cui si definiscono gli attributi e quella in cui vengono definiti i metodi.
Gli attributi specificano le caratteristiche che tutti gli oggetti della classe devono possedere. Il loro valore, in un certo istante, determinano lo stato di un singolo oggetto della classe.
I metodi specificano le funzionalità che la classe offre, cioè le operazioni che un certo oggetto è potenzialmente in grado di compiere. I metodi corrispondono ai comportamenti di un oggetto e sono riferibili all modifica dello stato di un oggetto (metodi set) o alla comunicazione dello stato dell'oggetto all'esterno (metodi get).
E' importante non confondere la nozione di metodo della OOP con quello
di funzione presente nella programmazione imperativa.
I metodi sono simili alle funzioni (o alle procedure)
ma sono definiti e quindi accessibili solo all'interno
di una classe.
La sintassi generale usata per dichiarare ed istanziare un oggetto è simile a quella già vista per dichiarare vettori e matrici:
nomeClasse nomeOggetto= new nomeClasse(parametri)
ad esempio
Q q; ⟶ dichiarazione
dell'oggetto q di classe (tipo) Q
q=new Q();⟶ allocazione in memoria dell'oggetto
q
vengono composte con notazione più sintetica:
Q q=new Q();
Finora l'unica classe che abbiamo considerato era quella principale, quella che contiene il metodo statico main() che viene eseguito per primo, assieme ad altri eventuali metodi statici. Più in generale un programma Java può essere costituito da più classi. Il seguente programma, istanzia un oggetto di classe P.
class oggetto {
public static void main(String[] args) {
P p=new P();//__> oggetto p creato
}//fine__main
}//fine class
class P {
P(){//costruttore
System.out.println("oggetto p creato");
};
}//fine classe P
In fase di compilazione si nota un segnale di warning che dice:
The value of the local variable p is not used
Questo, già ci può far sospettare che l'oggetto p
di classe P, creato, sia una variabile appartenente
ad un nuovo tipo di dato: il tipo P, appena introdotto,
appunto. All'interno della classe P si nota il metodo
costruttore P().
Il costruttore di una classe è un metodo speciale che deve avere lo stesso nome della classe e che viene eseguito quando si crea un nuovo oggetto attraverso l'operatore new.
Il costruttore viene usato per inizializzare gli attributi dell'oggetto. Può anche non essere presente esplicitamente nella definizione della classe ma deve essere considerato un metodo che esiste comunque, ma è "vuoto", cioè non contiene nessuna istruzione di inizializzazione degli attributi.
Come si vede nell'esempio seguente dove l'oggetto p, instanziato, invoca un metodo diverso dal costruttore (che non è scritto) e che stampa a video una generica stringa attraverso il metodo pubblico saluta().
class oggetto {
public static void main(String[] args) {
P p=new P();
p.saluta();//__> ciao
}//fine__main
}//fine class
class P {
public void saluta(){
System.out.println("ciao");
}//fine metodo saluta
}//fine classe P
I metodi costruttori inizializzano i valori degli attributi di un nuovo oggetto, creano eventuali altri oggetti necessari all'oggetto originale e, in generale, compiono tutte le operazioni necessarie alla creazione dell'oggetto in questione.
In generale, per inizializzare gli attributi il costruttore riceve dei parametri. Il programma seguente, istanzia i due oggetti it (italiano) ed en (english) che hanno il compito di salutare nelle rispettive lingue. I due oggetti vengono istanziati con un parametro stringa che inizializza l'attributo privato msg in modo differente.
class oggetto {
public static void main(String[] args) {
P it=new P("ciao");
it.saluta();//__> ciao
P en=new P("hallo");
en.saluta();//__> hallo
}//fine__main
}//fine class
class P {
private String msg;
P(String s){msg=s;}
public void saluta(){
System.out.println(msg);
}//fine metodo saluta
}//fine classe P
Può esserci anche più di un costruttore come si vede nell'esempio seguente:
class oggetto {
public static void main(String[] args) {
P it=new P("ciao");
it.saluta();//__> ciao
P en=new P("hallo","George");
en.saluta();//__> hallo George
}//fine__main
}//fine class
class P {
private String sub;
private String msg;
P(String s){msg=s;sub="";}
P(String s1,String s2){
msg=s1;
sub=s2;
}
public void saluta(){
System.out.println(msg+" "+sub);
}//fine metodo saluta
}//fine classe P
Viene eseguito un costruttore o l'altro a secondo di come viene istanziato l'oggetto: l'oggetto it viene istanziato con un solo parametro, quindi, il metodo eseguito sarà: P(String s) l'oggetto en viene istanziato con due parametri, pertando sarà eseguito il metodo P(String s1,String s2).
Questo sistema di chiamata è generale per tutti i metodi (statici o di istanza come in questo caso) ed noto con l'appellativo di overloading di metodi.
L'overloading consiste nella possibilità di definire più metodi con lo stesso nome ma con segnatura diversa.
Per segnatura (o firma) di un metodo si intende il suo nome (identificatore), l'eventuale tipo di valore ritornato e la lista ordinata con il tipo e i nomi dei suoi parametri.
I modificatori di accesso public e private
hanno un'importanza rilevante in questo contesto.
Un'entità di tipo private
è visibile solo all'interno della classe in cui è stata dichiarata.
Questo è il caso dell'attributo privato msg.
Un'invocazione a questa variabile, eseguita da un oggetto istanziato nel
main, come System.out.print(it.msg); genera infatti
un errore di compilazione. Un'entità di tipo
public è accessibile sia dall'esterno che dall'interno
della classe nella quale è stata dichiarata. Se il modificatore di
visibilità viene omesso, si sottintende implicitamente esso sia public.
In generale la struttura di una classe secondo una rappresentazione UML (Unified Modeling Language) è la seguente:
Per accedere a metodi o agli attributi di istanza si utilizza la "dot form":
riferimentoOggetto.nomeCampo
dove con nomeCampo si intende un qualsiasi metodo o attributo pubblico.
Di seguito è riportato l'esempio di una classe R
che istanzia un oggetto r che qualifica un rettangolo
di base ed altezza assegnate.
Si evidenziano i quattro attributi privati
b=base
h=altezza
a=area
p=perimetro
accessibili solo all'interno della classe.
Il costruttore R(int x, int y) inizializza i valori
di b ed h, poi possono essere
invocati i metodi privati area() e peri()
incaricati di computare i valori di a e p.
class GEO {
public static void main (String args []) {
R r = new R(3, 2);
System.out.println(r.getb());
System.out.println(r);
r.seth(3);
System.out.println(r);
}//fine main
}// fine classe
class R {
private int b,h,a,p;
R(int x, int y) {
b=x;h=y;
area();
peri();
}//costruttore
private void area(){a=b*h;}
private void peri(){p=2*b+2*h;}
public void setb(int x){b=x;area();peri();}
public void seth(int y){h=y;area();peri();}
public int getb() { return b;}
public int geth() { return h;}
public int geta() { return a;}
public int getp() { return p;}
public String toString() {
String st="base:"+getb()+" altezza:"+geth()+"\n";
st+="area:"+geta()+" perimetro:"+getp();
return st;
}//fine toString()
}//fine classe R
Si riconoscono due metodi modificatori
: setb(int x) e seth(int y)
usati per reimpostare i valori della base e dell'altezza e conseguentemente
area e perimetro di un oggetto rettangolo già esistente.
Si riconoscono i metodi query getb(),
geth(),..etc. che semplicemente visualizzano lo stato
di un oggetto senza modificarne il valore.
Questo costituisce uno dei principi fondamentali della OOP, l'information
hiding (occultamento dell'informazione) che consiste nel tenere il
più possibile nascosto lo stato dell'oggetto.
Infatti, se per consuetudine, consentiamo l'accesso diretto alla stato dell'oggetto
tramite la "dot form" permettendo di scrivere nel main (fruitore
dell'oggetto r) r.b=4, la base del rettangolo potrebbe
cambiare senza che conseguentemente si modifichino i corrispondenti valori
di area e perimetro.
La regola dell'information hiding afferma che l'unico modo per modificare o interrogare lo stato di un oggetto è quello di servirsi dei suoi metodi.
Il diagramma UML della classe R potrebbe essere il seguente
Si vedono contrassegnati col prefisso "-" i metodi e gli attributi privati, mentre col simbolo "+" sono contrassegnati i metodi pubblici. Il metodo toString() è molto utile e può essere ridefinito in una qualunque classe per restituire una stringa rappresentativa dello stato interno incapsulato in un oggetto.
Con gli elementi che abbiamo raccolto possiamo ora scrivere un programma
di test che ci permetta di inserire e di rappresentare i dati di un magazzino
di prodotti. La struttura dei dati viene gestita dal vettore T
che ha un max di 5 elementi.
Ogni elemento è un oggetto di classe A che prevede
3 attributi privati
desk : la descrizione dell'oggetto (vite, dado, ruota,..etc.)
prz: il prezzo dell'oggetto (numero intero)
qta: la quantità presente in magazzino (numero intero)
import java.util.Scanner;
class record {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
String d;
int p,q,i=0;
A T[]=new A[5];
do{
System.out.print("desk:");
d=in.nextLine();
if(d.length()==0)break;
else{
System.out.print("prezzo:");
p=in.nextInt();
System.out.print("quantità:");
q=in.nextInt();
T[i]=new A(d,p,q);
in.nextLine();
}//fine else
i++;
}while(i<T.length);
for(i=0;i<T.length;i++)
if(T[i]!=null)System.out.println(T[i]);
in.close();
}//fine__main
}//fine__class
class A {
private String desk;
private int prz;
private int qta;
A(String d, int p, int q){
desk=d; prz=p; qta=q;
}
public String toString() {
String st=desk+" "+prz+" "+qta;
return st;
}//fine__toString()
}//fine__classe A
Lasciamo allo studente la possibilità di ampliare il codice con gli opportuni
metodi get e set che potrebbero
permettere di migliorare le interrogazioni e gli aggiornamenti dell'archivio.