edutecnica

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.