Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2007-2008.

Lezione 03:

CLIENTSHIP E UGUAGLIANZA TRA OGGETTI

Clientship - Classe che ne usa un'altra

Nella classe rettangolo (ved. lezione scorsa) gli attributi erano a valori di tipi base (interi).
In generale gli attributi possono essere a valori oggetti di altre (o anche della stessa!) classe.

Clientship (clientela) = quando una classe definisce un attributo che e' di tipo classe.
La classe A che ha un attributo di classe B e' cliente di B.
Un oggetto di classe A ha per sotto-componente un oggetto di classe B.

Esempio: classe RectanglePair (coppie di rettangoli) ha attributi di classe Rectangle, e' cliente di Rectangle.

class RectanglePair
{
  Rectangle first;
  Rectangle second;
  ...
}

Nota per i costruttori

Ogni classe ha un costruttore di default che crea una nuova istanza con tutti gli attributi inizializzati a valore nullo, poi posso definire un costruttore io... (ved. lezione scorsa).
Valore nullo e' zero per i tipi numerici, false per il tipo boolean. Per i tipi classe e' null, l'oggetto nullo.

Varie nozioni di uguaglianza tra oggetti

Uguaglianza come identita'

Tra oggetti e' definito l'operatore booleano ==.

Tale operatore confronta gli OID, cioe' le identita' dei due oggetti (le locazioni di memoria).

Risulta vero se hanno la stessa identita' = stessa locazione di memoria.
Facciamo riferimento al frammento di codice in lezione scorsa. Es. dopo istruzione 4, r1==r3 vale vero.

Se due oggetti hanno stato uguale (stesso contenuto della locazione di memoria) ma non stessa identita' (non stessa locazione) risulta falso. Es. dopo istruzione 3, r1==r2 vale falso.

Uguaglianza come uguale stato

Se voglio funzione che controlli quando due oggetti diversi hanno uguale stato, cioe' che confronti il contenuto delle locazioni di memoria, devo definirla.

Esempio:

  boolean isEqual(Rectangle r) 
  { return ( (r.length==length) && (r.width==width) ); }
Si chiama con r1.isEqual(r2); Uno dei due rettangoli e' argomento privilegiato (asimmetria).

Alternativa con metodo di classe:

  static boolean isEqual(Rectangle r1, Rectangle r2)
  { return ( (r1.length==r2.length) && (r1.width==r2.width) ); }
Simmetria di trattamento fra i due argomenti. Si chiama con Rectangle.isEqual(r1,r2); Oppure con r.isEqual(r1,r2); dove r e' qualsiasi istanza della classe Rectangle (ma quest'ultimo modo e' sconsigliato).

Shallow e deep equality

Essendo gli attributi length e width di tipi base (tipo int), le locazioni di memoria di due rettangoli contengono valori base. Percio' l'operatore == nel corpo di isEqual non pone problemi.

Se invece gli attributi fossero oggetti di una classe, si porrebbe il problema se la loro uguaglianza vada controllata con == direttamente oppure di nuovo con isEqual. Otteniamo due diverse nozioni di uguaglianza:

Esempio: coppie di rettangoli

class RectanglePair
{
  Rectangle first;
  Rectangle second;
  ...
  boolean shallowEqual(RectanglePair rp)
  { return ( (first==rp.first) && (second==rp.second) ); }
  boolean deepEqual(RectanglePair rp)
  { return ( first.isEqual(rp.first) && second.isEqual(rp.second) ); }
}
Shallow: risulta vero se rp e' coppia con esattamente gli stessi rettangoli che ha questa coppia.
Deep: risulta vero se rp e' coppia con rettangoli uguali a quelli che ha questa coppia.

Copia di oggetti

Come ci sono varie nozioni di uguaglianza, ci sono anche varie nozioni di copia di oggetti.

  1. Assegnazione. Es. r3=r1 fa puntare la variabile r3 allo stesso oggetto puntato da r1. Dopo, vale r1==r3.
  2. Shallow copy. Crea nuovo oggetto con stesse sotto-componenti. Dopo, vale shallow equality.
  3. Deep copy. Crea nuovo oggetto copiando ricorsivamente le sotto-componenti. Dopo, vale deep equality.

Come per shallow / deep equality, se voglio shallow / deep copy devo definire io una funzione che esegue la copia.

Caso facile: classe con attributi tipi base

Shallow e deep copy coincidono.
Esempio: classe Rectangle, funzione di copia che crea altro rettangolo uguale a questo:

 Rectangle copy() { return new Rectangle(length,width); }
Poi posso chiamare r3 = r1.copy(); Dopo, r1==r3 vale falso, ma Rectangle.isEqual(r1,r3) vale vero.

Di solito si scrive un costruttore di copia:

 Rectangle(Rectangle r) { length = r.length; width = r.width; }
Poi posso chiamare r3 = new Rectangle(r1); Costruisce un oggetto copia.

Caso piu' difficile: classe con attributi tipi classe

Se gli attributi sono a loro volta oggetti, la copia puo' essere shallow o deep.

Possiamo scrivere funzioni di copia shallow e deep per classe RectanglePair.

Clientship - Classe che usa se stessa

Una classe puo' essere cliente di se stessa. Puo' avere come attributi oggetti della stessa classe (classe definita ricorsivamente).

Esempio: il treno coi vagoni

Un treno e' fatto di tanti vagoni. Ogni vagone ha un numero e puo' "agganciare" un altro vagone.
Il treno aggancia il primo vagone. Ha inoltre un tipo di treno (regionale, intercity ecc.), un numero di treno, una stazione di partenza e una di arrivo.

class Vagone
{
  int numero;      /* numero di carrozza */
  Vagone prossimo; /* collegamento al prossimo vagone */
  Vagone(int num)  /* crea il vagone isolato */
  { numero = num; prossimo = null; }
  void aggancia(Vagone vag) { prossimo = vag; }
}

class Treno
{
  static final int REGIONALE = 1;
  static final int INTERCITY = 2;
  /* eccetera per altri tipi di treno... */
  int tipo;     /* tipo di treno */
  int numero;   /* numero del treno */
  Vagone primo; /* collegamento al primo vagone */
  String partenza; /* nome della stazione di partenza */
  String arrivo;   /* nome della stazione di arrivo */
  Treno(int t, int n, String par, String arr)
  /* crea treno senza vagoni */
  {
    tipo = t;
    numero = n;
    partenza = par;  arrivo = arr;
    primo = null;
  }
  void aggancia(Vagone vag) { primo = vag; }
}

La classe Treno e' cliente di Vagone. Vagone e' cliente di se stessa. Vagone e' classe ricorsiva perche' contiene riferimento a se stessa.

Esempio di uso:

1: Treno t = new
     Treno(Treno.INTERCITY, 1267, "Genova", "Ventimiglia");
2: Vagone ultimo;
3: Vagone v1 = new Vagone(210);
4: t.aggancia(v1);
5: ultimo = v1;
6: v1 = new Vagone(211);
7: ultimo.aggancia(v1);
8: ultimo = v1;
   /* posso attaccare altri vagoni ripetendo le linee di
      codice 6,7,8 con diversi numeri di carrozza */
Aggancio prima il primo vagone e poi dietro tutti gli altri, ogni volta agganciando un nuovo vagone all'ultimo vagone. La variabile ultimo punta sempre l'ultimo vagone e viene spostata ogni volta. La variabile v1 serve a creare un nuovo vagone, che poi sara' agganciato e diventera' l'ultimo.

Attenzione:

Stampare un treno

Il treno stampa il suo tipo, il suo numero, le due stazioni di partenza e arrivo, poi dovrebbe stampare i vagoni...
Il treno dice al primo vagone di stampare se stesso. Questo pensera' a far stampare i vagoni che gli si trovano agganciati dietro, fino all'ultimo.
Ogni vagone per stamparsi stampa il suo numero di carrozza, poi se ha agganciato un altro vagone dice a questo di stamparsi, se invece non ha agganciato nulla (ultimo vagone) non fa altro.

Nella classe Vagone:

  void print()
  {
    System.out.println("carrozza numero " + numero);
    if (prossimo!=null)  prossimo.print();
  }

La stampa di un vagone e' funzione ricorsiva. Ciascun vagone stampa quello seguente, il quale stampa quello dopo ancora... e cosi' ricorsivamente... ci si ferma quando si incontra il vagone nullo.

Nella classe Treno:

  void print()
  {
    System.out.print("Treno ");
    switch (tipo_treno)
    {  case REGIONALE: System.out.print("regionale"); 
                       break;
       case INTERCITY: System.out.print("intercity");
                             break;
       default: System.out.print("non classificato");
                break;
    }
    System.out.println(" numero " + numero + " vagoni:");
    if (primo!=null) primo.print();
  }

Il treno come sotto-classe di vagone

Il treno ha tutto cio' che ha un vagone: un numero, un vagone agganciato, funzioni per aggancio e sgancio. In piu' ha tipo del treno e le stazioni di partenza e arrivo.
Possiamo rifare il treno come sottoclasse del vagone.

class Treno extends Vagone
{
  static final int REGIONALE = 1;
  static final int INTERCITY = 2;
  /* eccetera per altri tipi di treno... */
  int tipo_treno;
  // il numero viene ereditato dal vagone
  // per primo vagone viene usato l'attributo "prossimo" del vagone
  String partenza;
  String arrivo;
  Treno(int t, int n, String par, String arr)
  /* crea treno senza vagoni */
  {
    super(n); // chiama costruttore del vagone
    tipo = t;
    partenza = par;  arrivo = arr;
  }
  // metodo aggancia e' ereditato da Vagone
  void print()
  /* metodo print e' ereditato da Vagone ma re-implementato */
  {
    System.out.print("Treno ");
    switch (tipo)
    {  case REGIONALE: System.out.print("regionale");
                       break;
       case INTERCITY: System.out.print("intercity");
                        break;
       default: System.out.print("non classificato");
                break;
    }
    System.out.println(" numero " + numero + " vagoni:");
    if (prossimo!=null) prossimo.print();
  }
}

Vale la relazione "is-a" per treno e vagone: un treno e' anche un vagone!