edutecnica

Classi astratte

      

Facciamo qualche considerazione sul programma per il calcolo di area e perimetro di quadrati (classe Q) e rettangoli (classe R) che abbiamo visto finora.
Le due classi sono legate da un vincolo di ereditarietà, perché il quadrato è considerato un caso specifico di rettangolo. Se tra le figure geometriche di nostro interesse volessimo introdurre il cerchio sicuramente non potremmo annoverarlo in un legame di ereditarietà col quadrato o col rettangolo. I metodi per il calcolo dell'area e del perimetro di un cerchio sono totalmente differenti da quelli del rettangolo, eppure area e perimetro sono delle proprietà caratteristiche sia dei rettangoli che dei cerchi che di tutte le altre figure geometriche. Rettangoli, quadrati e cerchi sono particolari figure geometriche.

Possiamo, allora, concepire una classe capostipite F (figura geometrica) che tenga conto di queste proprietà comuni (area, perimetro) delle figure geometriche, questa classe, poi, deve imporre che tutte le sue sottoclassi implementino necessariamente dei metodi per il calcolo di queste proprietà. La soluzione a questo problema è quello di definire astratti i metodi per il calcolo dell'area e del perimetro.

Un metodo astratto è un metodo con solo la segnatura, ma privo di implementazione.
Quest'ultima viene fornita dalle sottoclassi della classe nella quale è stato definito il metodo astratto.

Una classe che possiede anche solo un metodo astratto è detta classe astratta.

Una classe astratta, fornisce solo delle linee guida sui metodi e gli attributi che dovranno essere implementati nelle sottoclassi. Le sottoclassi (a meno che non siano a loro volta astratte) devono necessariamente implementare (e quindi ridefinire) i metodi astratti.

class GEO {
public static void main (String args []) {
R r = new R(3, 2);//Rettangolo R
r.stampaArea();
r.stampaPeri();
System.out.println();
Q q=new Q(3);//Quadrato Q
q.stampaArea();
q.stampaPeri();
System.out.println();
C c=new C(4);//Cerchio R
c.stampaArea();
c.stampaPeri();
}//fine main
}// fine classe

abstract class F {
abstract double area();
abstract double peri();
public void stampaArea(){System.out.println(area());}
public void stampaPeri(){System.out.println(peri());}
}//fine classe F

class R extends F{
private double b,h,a,p;
public double area(){return b*h;}
public double peri(){return 2*b+2*h;}
R(double x, double y) {
  b=x;h=y;
  area();peri();
}//costruttore
public double getb() { return b;}
public double geth() { return h;}
public double geta() { return a;}
public double getp() { return p;}
}//fine classe R

class Q extends R{
private double l;
Q(double lato){
  super(lato,lato);
  l=lato;
}
public double getl() { return l;}
}//fine classe Q

class C extends F{
private double r;
public double area(){return 3.14*r*r;}
public double peri(){return 2*3.14*r;}
C(double ray) {
  r=ray;
  area();peri();
}//costruttore
public double getr() { return r;}
}//fine classe C

Tutte le figure geometriche hanno un area e un perimetro; i metodi
double area();
double peri();

contengono il procedimento per il calcolo dell'area e del perimetro che dipende dal tipo concreto di figura, quindi non siamo in grado di definirli nella classe Figura (F). Essi sono definiti come metodi astratti (abstract).

I metodi astratti sono metodi di cui è specificato il prototipo ma non l'implementazione.

Una classe contenente metodi astratti dev'essere dichiarata astratta (abstract).
Una classe astratta non può essere istanziata, ma può essere estesa.
Le sottoclassi di una classe astratta devono fornire l'implementazione di tutti i metodi astratti (salvo che siano anch'esse astratte).
Una classe astratta può contenere anche metodi implementati.
Una classe astratta ha un costruttore di default privo di argomenti.
Sono classi concrete: le classi che non sono astratte.

Nei diagrammi UML le classi astratte sono descritte come le classi concrete ma il nome è indicato in italico. I metodi astratti sono descritti come i metodi concreti ma il prototipo è indicato in italico .

Le classi astratte forniscono (come quelle concrete) un supertipo comune i cui valori sono tutte le possibili istanze di tutte le sottoclassi.

Il seguente programma mostra come all'interno di una classe astratta sia possibile implementare metodi concreti, in questo caso il metodo maxA() che ha il compito di confrontare tra loro la superficie di varie figure geometriche.

class GEO2 {
public static void main (String args []) {
R r = new R(5, 2, "r");//Rettangolo R
Q q=new Q(4,"q");//Quadrato B
C c=new C(3,"c");//Cerchio R
System.out.println(r);
System.out.println(q);
System.out.println(c);
System.out.println(r.maxA(c));
System.out.println("area cerchio:"+c.getA());
System.out.println("area quadrato:"+q.getA());
System.out.println("area rettangolo:"+r.getA());
}//fine main
}// fine classe

abstract class F {
abstract double getA();
abstract String getN();
public String maxA(F x){
  String st;
  if(this.getA()>x.getA())st=" ha area maggiore di ";
  else st=" ha area minore di ";
  st=this.getN()+st+x.getN();
  return st;
}//fine maxA
}//fine classe F

class R extends F{
private double b,h,a;
private String n;
R(double x, double y, String nome) {
  b=x;h=y;n=nome;
  a=b*h;
}//costruttore
public double getA() { return a;}
public String getN() { return n;}
public String toString(){
  String st="il rettangolo "+n+" ha base:"+b+" e altezza:"+h;
  return st;
}//fine toString
}//fine classe R

class Q extends R{
private double l;
private String n;
Q(double lato, String nome){
  super(lato,lato,nome);
  l=lato;
  n=nome;
}//fine costruttore
public String toString(){
  String st="il quadrato "+n+" ha lato:"+l;
  return st;
}//fine toString
}//fine classe Q

class C extends F{
private double r,a;
private String n;
C(double ray, String nome) {
  r=ray;n=nome;
  a=3.14*r*r;
}//costruttore
public double getA() { return a;}
public String getN() { return n;}
public String toString(){
  String st="raggio:"+r;
  return st;
}//fine toString
}//fine classe C


Interfacce

      

E' noto che in Java non è possibile estendere più di una classe, come in C++.
Non è dunque consentita la scrittura:

class C extends A, B {
. . .
}

Questa peculiarità viene definita ereditarietà multipla. Si crea, infatti una ambiguità se nelle classi genitrici viene contenuto uno stesso metodo m().

Infatti, una volta istanziato l'oggetto c della classe C, quale metodo m() deve essere applicato? Quello della classe A o quello della classe B?

Comunque, per soddisfare alle esigenze legate alla comodità dell'ereditarietà multipla viene messo a disposizione uno strumento potente: le interfacce.
Le differenze fra una interfaccia e una classe sono:



1 Viene usata la parola interface invece che class.
A differenza delle classi astratte una interfaccia non può avere costruttori.

2 Non viene fornita l'implementazione di nessun metodo di un'interfaccia ed implicitamente tutti i metodi di una interfaccia sono pubblici ed astratti anche se il programmatore non lo dichiara esplicitamente. Le notazioni public ed abstract,possono dunque, essere omesse.
Una interfaccia, è dunque, un contenitore di metodi astratti, non contiene variabili istanza.

3 Una classe può implementare un'interfaccia: per farlo deve fornire un'implementazione di tutti i metodi dell'interfaccia e usare la parola riservata implements nell'intestazione, subito dopo extends, altrimenti diventa astratta.

Apparentemente, una interfaccia sembra semplicemente una classe costituita da metodi astratti;
ma le altre differenze fra classe ed interfaccia sono:

4 Le interface non si possono istanziare, ma una classe può implementare una (o più) interfacce.

5 Una classe può ereditare (extends) da un'unica sopraclasse diretta, ma può implementare (implements) più interfacce.

Qui di seguito un esempio di realizzazione di un programma simile ai precedenti con l'utilizzo di una interfaccia Scalatura con il mantenimento della classe astratta Figura.



class geo {
public static void main (String args []) {
Quadrato q = new Quadrato(2);
q.stampaArea();
q.redux(50);
q.stampaArea();
}//fine main
}// fine classe geo

interface Scalatura {
public void redux (int scala); //se scala=100 la figura rimane invariata
}//fine interfaccia Scalatura

abstract class Figura {
abstract double area();
public void stampaArea() { System.out.println(area()); }
}//fine classe Figura

class Cerchio extends Figura implements Scalatura {
private int ray;
Cerchio(int r) {ray=r;}//costruttore
public double area(){return ray*ray*Math.PI;}
public void redux (int scala) {ray=ray*scala/100;}
}//fine classe Cerchio

class Quadrato extends Figura implements Scalatura {
private int lato; Quadrato(int l) {lato=l;}//costruttore
public double area(){return lato*lato;}
public void redux (int scala) {lato=lato*scala/100;}
}//fine classe Quadrato


Qui si vede come l'interfaccia, obbliga (se no dà un errore di compilazione) le classi che implementano l'interfaccia a contenere il metodo redux che può avere anche un corpo diverso nelle due classi implementate.
Quindi, possiamo dire che in Java non è supportata l'ereditarietà multipla, ma una classe può però implementare più interfacce definendo per essa diversi comportamenti.