Newsgroup: it.comp.java
Titolo: Composizione ed Ereditarieta'... un caso semplice

Bruce Eckel dice nel suo libro che la "composizione" (includere in un
oggetto, oggetti di altre classi) dovrebbe essere preferita alla
"ereditarieta'" (estendere una classe da un'altra classe).
A meno che non si voglia fare del "polimorfismo".
Ora ho un dubbio fondamentale:
Se non faccio uso del polimorfismo (sono ancora ai primi passi)...
la mia classe Finestra che "estende" Frame DOVREBBE essere convertita
in una classe GUI che non estende nulla ma che ha al suo interno un
oggetto Frame???
Lo stesso dicasi per tante altre classi in cui ho pensato (credendo di
far bene) di specializzare una classe esistente con l'ereditarieta'.
Cosa dice a riguardo chi ha piu' esperienza di me?
Grazie...
Luigi

I problemi dell'ereditarietà sono:
- la classe figlia eredita delle funzionalità senza (in teoria) sapere 
come sono implementate
- può re-implementarne alcune
- scassando tutto !!
Pensa ad es. ai metodi paint() repaint() etc. di awt e compagnia bella; 
se non ci fossero miriadi di tutorial e javadoc sarebbe molto più 
difficile implementare una JFrame senza che l'interfaccia si blocchi.
E' molto più affidabile (anche dal punto di vista "umano") affidarsi a 
delle funzionalità consolidate quali sono quelle "pubblicate" da una 
classe concreta, e "comporle" per crearne di nuove.
-- 
                 ''~``
                ( o o )
+---------.oooO--(_)--Oooo.----+
 
http://www.jroller.com/page/megadix/
 
http://www.mict.it/

 oggetto Frame???
Per un caso curioso stavo proprio sfogliando in questo momento il libro di
Eckel sui Design Patterns, in cui viene riportata l'affermazione di cui
parli.
In linea di massima capisco cosa voglia dire; ho imparato a programmare ad
oggetti un po' da autodidatta, e quando ho veramente capito il potere del
polimorfismo ho anche capito che fino ad allora non avevo programmato ad
oggetti.
Penso che l'affermazione possa essere un po' dura, ma serva a scoraggiare
chi, cominciando con la programmazione ad oggetti, utilizza l'ereditarietà
a tutto spiano senza soffermarsi a pensare quando serve e quando no.
Poi una volta che uno ha capito bene tutti i meccanismi credo possa
scegliere come ritiene più comodo; ovviamente se lavora ad un progetto di
grandi dimensioni gli converrà cercare di mantenere il codice il più pulito
possibile, senza creare fra le classi legami che non hanno motivo di
esistere (ed es. ereditarietà superflua, per l'appunto) e che possono
complicare la struttura del programma.
Credo che però la GUI sia un caso a parte (infatti da qualche parte ho
letto che la progettazione di GUI non fornisce affatto dei buoni esempi di
progettazione ad oggetti). 
Suppniamo di dover usare in un programma una finestra di dialogo che
contiene determinati pulsanti e determinate scritte. Un approccio può
essere quello di creare una sottoclasse di JDialog e nel costruttore
configurare la JDialog come vogliamo noi. Quando ci serve quel particolare
dialog ci basta creare un'istanza della nostra nuova classe. 
In realtà però non è necessaria l'erediterietà per fare questo, sarebbe
sufficiente scrivere un metodo di servizio che, ogni qualvolta ci serve,
costruisce una generica JDialog e la configura nel modo appropriato.
Quindi in qualche modo l'ereditarietà è superflua.
Questo almeno è quello che penso io.
In realtà io polimorfeggio a go go :)
Claudia
-- 
"sul viale del tramonto il telefonino non prende"

 - scassando tutto !!
Però in realtà in questo caso staresti usando il polimorfismo, quindi
staresti usando l'ereditarietà a pieno diritto (secondo l'affermazione di
Eckel riportata da Luigi)
Trovo curioso che tu citi come problemi quelli che sono i punti di forza
della programmazione ad oggetti!


 
Certo, certo, solo che la faccenda non è così semplice...
Il famoso libro "Design Pattern" della GoF ne parla in modo molto 
realista nel capitolo "Putting reuse to work", al paragrafo, appunto 
"Inheritance Vs. Composition".
Personalmente uso moltissimo l'ereditarietà, ma solo perchè devi 
scrivere di meno :)


 ha scritto nel messaggio news:1tjhts5kiay5c.g2xinxfh0hn6$.dlg@40tude.net...
| In linea di massima capisco cosa voglia dire; ho imparato a programmare ad
| oggetti un po' da autodidatta, e quando ho veramente capito il potere del
| polimorfismo ho anche capito che fino ad allora non avevo programmato ad
| oggetti.
Sono la tua fotocopia: mi è capitato l'identica cosa!
Per di più:
Iniziai con C++ pensando che scrivere classi
significava scrivere in OOP, ma in realtà programmavo
in maniera procedurale.
Poi cominciai a programmare in Java e un pomeriggio
mi si balenò davanti a me l'idea CHIARA del polimorfismo.
Oggi non programmo più! Ma so che cosa è il polimorfismo! :-)
BTW il C++ non è un buon strumento per apprendere l'OOP
(virtual, non virtual, pure virtual, overload degli operatori, ...che casino!)
TORNANDO al discorso Eckeliano, proporrei questa idea:
Pro e Contro dell'utilizzo dell'ereditarietà per composizione:
Contro:
    - è meno semplice scrivere codice rispetto all'ereditarietà pura;
    - non posso utilizzare il polimorfismo se non implementando interfacce.
Pro:
    - di default la mia classe non espone alcun metodo dei componenti,
       ma sono io che decido quali esporre (contrariamente a quanto
       accade nell'ereditarietà pura che fa in modo che per default la
       mia classe espone tutti i metodi pubblici della classe base).
Conclusione: tenendo conto dei pro e contro, l'uso di una tecnica
o dell'altra è una scelta progettuale (in base alle esigneze). Mia esperienza
professionale: non ho mai avuto bisogno di nascondere per default i metodi
pubblici della classe base e via via scoprirli. E voi?
Ciao,
GianpieroP
-----BEGIN PGP SIGNATURE-----
Version: PGP 8.0
iQA/AwUBQHWtuZ4sPKh9SaicEQJKOACgieq0RUusAPrzQpYQ5Nx7pEVFKGkAoL4J
EKgLCbQG2la0WU7NDd9mS/19
=L2sR
-----END PGP SIGNATURE-----

 
Io perň farei un'osservazione:
- in C++ devi dichiarare quali metodi POSSONO essere riscritti (virtual 
se non erro, eventualmente con un'implementazione di base)
- in Java devi dichiarare quali NON POSSONO essere riscritti
In particolare mi riferisco ai metodi protected: in molti casi li si 
vuole rendere disponibili alle sotto-classi, ma senza che queste li 
ridefiniscano in qualche maniera. In questo caso bisogna dichiararli 
final , ovviamente.
Ora facciamo un piccolo campionamento:
- quante volte definiamo dei metodi utili alle sotto-classi ?
- quanti di questi possono essere ridefiniti SENZA PROBLEMI ?
- quanti č meglio che rimangano cosě come sono ?
etc. etc. etcě


 interno un oggetto Frame???
Non esageriamo, e' perfettamente lecito utilizzare l'ereditarieta' per
modellare un rapporto di *specializzazione* fra due entita'. Window
e' un Frame con qualche servizio aggiuntivo, e quindi il rapporto di
ereditarieta' e piu' che appropriato in questo caso.
[cut]
[*] Le regole del linguaggio che governano l'uso dei tipi
[**] Se vuoi maggiori info cerca "Liskov substitution priciple" oppure
fatti un giro sull'home page di Robert Martin
-- 
{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}-{}
SunGray aka Gabriele Lana
contact me @ 
gabrie...@fastwebnet.it
See you space cowboy ...

Luigi wrote:
Aggiungo a tutto quello che è stato detto alcune considerazioni di
progettazione.
Se abbiamo due classi A e B, A deve estendere B se e solo se A _e' un_ B.
Questa frasettina che si sente spesso anche come relazione isa è la chiave
di tutto, secondo me.
Se A è un B ha perfettamente senso che l'interfaccia di A esponga tutti i
metodi di B eventualmente ridefiniti.
L'ereditarietà non è un modo per riusare il codice (se non nel senso di cui
sopra). Se A non è un B ma ha bisogno di un B allora un oggetto di tipo B
deve essere incapsulato in A. Questa è una relazione di tipo _ha un_ non _è
un_ e non è una differenza marginale :).
Dunque bisogna astrarre dalla stretta necessità di chiamare un metodo e
ragionare sul tipo.
Ciao
Matteo
-- 
chi sono e soprattutto chi non sono:
http://www.ilnomedeldominio.it
un piccolo programma per studenti universitari:
http://inutility.ilnomedeldominio.it

 (per citare un post del thread) "ereditare per scrivere di meno".
Il problema a cui ci si riferisce di solito con "attenzione
all'ereditarietà" è quello della "fragile base class".
Un ottimo esempio si trova in Effective Java. Sostanzialmente il problema
consiste nel dover conoscere dei dettagli implementativi per utilizzare
correttamente l'ereditarietà (l'ereditarietà rompe l'incapsulamento - è un
altro modo comune di riferirsi al problema).
Un dettaglio implementativo che bisogna conoscere è quanti metodi
"virtuali" (ossia i normali metodi java, quelli esplicitamente dichiarati
tali in c++) si chiamano fra loro, o non si potrà ereditare correttamente
senza rompere il codice.
Il bivio non ha soluzioni di sorta: o il programmatore della classe da
estendere (classe A per brevità) ha esposto questo dettaglio
implementativo, o non lo ha fatto.
Se lo ha esposto sta tranquillo chi eredita (classe B per brevità), meno
chi ha scritto A perchè non potrà modificare questo dettaglio.
Se non lo ha esposto, B NON puo' in alcun modo ereditare correttamente,
poichè una futura versione di A potrebbe rompere la classe B SENZA
modificare la sua parte pubblica.
L'esempio di Josh Bloch è sostanzialmente questo:
public class InstrumentedHashSet extends HashSet {
 
  private int counter;
  public InstrumentedHashSet() {
  }
  public InstrumentedHashSet(Collection c) {
    super(c);
  }
  public InstrumentedHashSet(int initCap, int loadFactor) {
    super(initCap, loadFactor);
  }
  public boolean add(Object o) {
    counter++;
    return super.add(o);
  }
  public boolean addAll(Collection c) {
    counter += c.size(); // *
    return super.addAll(c);
  }
  public void printHowMany(PrintStream stream) {
    stream.print("Number of elements: ");
    stream.println(counter);
  }
}
La classe ha lo scopo di contare quante volte si tenta di inserire degli
elementi nella collection.
La classe, così com'è, non funziona, poichè in Hashset i metodi add e
addAll, entrambi polimorfici, si chiamano fra loro. Dunque il counter
viene incrementato due volte. Per incrementarlo una volta sola ovviamente
basta eliminare la riga con l'*.
Pero' cosi'siamo legati ad un dettaglio implementativo della classe
HashSet: ossia che addAll usi internamente add. Se questo è reso esplicito
nella doc della classe, ok per noi, poco ok per il programmatore di
HashSet che non potrà cambiare questo dettaglio (lo ha in effetti reso
"quasi" parte della interfaccia pubblica, documentandolo).
Se non lo ha documentato invece, possiamo solo sperare che nella prossima
release di Java continui ad essere cosi', o la ns classe
InstrumentedHashSet cesserà di funzionare di colpo non contando tutti i
tentativi (quelli con addAll)
Per questo motivo, noto in letteratura come Fragile Base Class,
bisognerebbe favorire la composizione che non soffre di questo problema.
Questo non vuol dire non usare il polimorfismo, ovviamente: ci sono le
interfacce per questo.
La classe di cui sotto per eesmpio è immune dal problema di fragilità di
cui sopra, usa la composizione ed implementa l'interfaccia Set, dunque è
polimorfa
public class InstrumentedHashSet2 implements Set {
  private final Set delegate;
  private int counter;
  public InstrumentedHashSet2(Set s) {
    delegate  = s;
  }
  public boolean add(Object o) {
    counter++;
    return delegate.add(o);
  }
  public boolean addAll(Collection c) {
    counter += c.size();
    return delegate.addAll(c);
  }
  public void printHowMany(PrintStream stream) {
    stream.print("Number of elements: ");
    stream.println(counter);
  }
  public int size() {
    return delegate.size();
  }
  public boolean isEmpty() {
    return delegate.isEmpty();
  }
  public boolean contains(Object o) {
    return delegate.contains(o);
  }
  public Iterator iterator() {
    return delegate.iterator();
  }
  public Object[] toArray() {
    return delegate.toArray();
  }
  public Object[] toArray(Object a[]) {
    return delegate.toArray(a);
  }
  public boolean remove(Object o) {
    return delegate.remove(o);
  }
  public boolean containsAll(Collection c) {
    return delegate.containsAll(c);
  }
  public boolean retainAll(Collection c) {
    return delegate.retainAll(c);
  }
  public boolean removeAll(Collection c) {
    return delegate.removeAll(c);
  }
  public void clear() {
    delegate.clear();
  }
}
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 ragionare sul tipo.
Purtroppo il problema della classe base fragile rende vano anche questo
ciao
uL


 Purtroppo il problema della classe base fragile rende vano anche questo
E' vero, ma lo stesso Bloch dice di progettare le classi per essere
ereditate o di impedire l'ereditarietà. Quindi si puo' (dovrebbe) o evitare
che un metodo ridefinibile in una sottoclasse chiami un altro metodo dello
stesso tipo, o documentare questa chiamata, o dichiarare la classe final (o
avere solo costruttori privati) impedendo l'ereditarietà.
Secondo me un buona progettazione evita questi problemi.
Il casino sorge se il codice da estendere l'ha scritto qualcun altro che non
c'ha pensato :)


 classe B SENZA modificare la sua parte pubblica.
Concordo con quello che hai detto, ma vorrei aggiungere che non e' un
problema dell'ereditarieta' come costrutto, ma di modellazione
dell'astrazione che dovrebbe essere la base di future implementazioni
concrete (vedi articolo di Carlo Pescio sul tema: 
www.eptacom.net
).
Si *deve* ammettere che il livello d'accoppiamento fra una classe base
e la sua derivata e' alto e che il riuso (effetto collaterale
dell'ereditarieta' e non il contrario) e' di tipo white box. 
Mi sembra un po' assurdo il dover pensare all'utilizzo
dell'ereditarieta' solo nel caso in cui ci si possa *fidare* della
stabilita' della classe dalla quale si deriva.
Piu' che un principio/consiglio questo di Eckel mi sembra un
workaround da usarsi solo in alcuni casi.
[cut]
[NOTA] In questi casi la delegazione come costrutto di aggregazione di
oggetti/classi sarebbe molto meglio, peccato che Java non la
supporta.


 workaround da usarsi solo in alcuni casi.
non è un consiglio di Eckel (che non è in grado di formulare questo tipo
di consigli, e al massimo li riporta)
"favour composition over inheritance" si trova già nel GoF, 1994, nella
parte introduttiva. Probabilmente i paper originali sulla fragilità delle
classi base sono di fine anni '80, ma non ho sottomano il materiale.
ciao
uL


 ereditate o di impedire l'ereditarietà. 
Hai ragione. La mia esperienza è che la maggior parte delle volte le
classi che si trovano in giro comunemente dovrebbero essere final, non lo
sono, e NON sono fatte per essere ereditate. 
Se in effetti ti metti a considerare tutti i casi che devi supportare
perchè la tua classe possa essere estesa, il costo di sviluppo sale e
molto probabilmente è conveniente rinunciare e renderla final (eventuale
supporto a Serializable e Cloneable, documentazione di OGNI metodo con
esposizione di dettagli interni, ecc.)
Ovviamente Bloch programma librerie Java, e per lui il problema è molto
piu' impellente: ha come utenti programmatori che vogliono poter estendere
o perlomeno sapere che la classe non puo' essere estesa.
La maggioranza del codice "di tutti i giorni" nessuno lo estenderà mai
(per sua fortuna :) ) quindi forse non renderlo final non è poi 'sto gran
peccato.
