PDA

View Full Version : [OOP] Ereditarietà multipla solo tramite interfacce... preché?


astorcas
12-02-2008, 15:31
Ciao a tutti,
credo che sia noto a tutti che linguaggi come Java e C# non consentono l'ereditarietà multipla (tramite classi).... ma perchè?
Capisco che potrebbe venir fuori un bel casino con i nomi delle variabili e metodi che potrebbero essere troppo simili (se non uguali) fra una classe madre e l'altra.... ma c'è altro?
Qualcuno potrebbe fornirmi un link o indicarmi qualche libro da leggere che potrebbe chiarirmi le idee?
O magari avete qualche esempio lampante?

Thanks! :fagiano:

gugoXX
12-02-2008, 19:35
Il problema si chiama "Diamond Problem", ed e' parente del dubbio da te sollevato.
Immagina una classe A, padre di tutti, con il metodo virtuale Pippo();
B e C sono due altre classi figlie di A, ed entrambe implementano il metodo Pippo(), ma in modo diverso.
Se poi D deriva da entrambe le classi B e C, essa e' ovviamente anche di tipo A, e pertanto avra' anch'essa il metodo Pippo.
Ma se non effettua l'override di Pippo, qualora un'istanza di tale classe chiamasse il metodo, quale versione verrebbe eseguita?

Se cerchi in giro lo trovi.

astorcas
13-02-2008, 01:24
Il problema si chiama "Diamond Problem", ed e' parente del dubbio da te sollevato.
Immagina una classe A, padre di tutti, con il metodo virtuale Pippo();
B e C sono due altre classi figlie di A, ed entrambe implementano il metodo Pippo(), ma in modo diverso.
Se poi D deriva da entrambe le classi B e C, essa e' ovviamente anche di tipo A, e pertanto avra' anch'essa il metodo Pippo.
Ma se non effettua l'override di Pippo, qualora un'istanza di tale classe chiamasse il metodo, quale versione verrebbe eseguita?

Se cerchi in giro lo trovi.

Grazie! In effetti anche per il compilatore non deve essere un lavoro piacevole....

cdimauro
13-02-2008, 07:24
Infatti in caso di "conflitto" dev'essere il programmatore a "sbrogliare la matassa", indicando quale dei due metodi dev'essere chiamato. :D

Comunque i "conflitti" dovuti a metodi con lo stesso nome possono verificarsi anche con le interfacce. ;)

gugoXX
13-02-2008, 08:24
Infatti in caso di "conflitto" dev'essere il programmatore a "sbrogliare la matassa", indicando quale dei due metodi dev'essere chiamato. :D

In questi casi ammettendo ambiguita' che si possono risolvere solo venendo meno ad alcune semantiche per le quali il modello ad oggetti e' largamente diffuso, secondo me significa che il modello ad oggetti e' piu' debole.
Ci si puo' convivere, ma forzando il programmatore a specificare quale funzione usare tra quelle a disposizione e'un po' come avere delle funzioni esterne alla classe.


Comunque i "conflitti" dovuti a metodi con lo stesso nome possono verificarsi anche con le interfacce. ;)

Questo invece non e' vero, almeno in C#.
Le interfaccie sono solo dei contratti tra compilatore e programmatore, senza codice.
Aggiungendo una interfaccia ad una classe solo si costringe tale classe ad avere implementati alcuni metodi particolari, e si potra' fare successivamente affidamento sull'esistenza di tali metodi.
Se per un motivo o per l'altro l'implementazione del metodo dell'interfaccia la tua classe gia' ce l'ha (nel nostro caso appunto perche' l'ha erediata) tanto di meglio.
Ma non si hanno conflitti.


class padre
{
public int pippo = 0;

public virtual int MetodoCritico(int variabile)
{
return 0;
}
}

interface HabemusCriticum
{
int MetodoCritico(int variabile);
}

class NonCentraNulla:HabemusCriticum
{
public int robadinoncentranulla=-1;

#region HabemusCriticum Members

public int MetodoCritico(int variabile)
{
throw new Exception("The method or operation is not implemented.");
}

#endregion
}

class figlio:padre,HabemusCriticum
{
public int altro = -1;
public int AltroAncora(float ciccio)
{
return 0;
}
}


Questo codice compila, e la classe figlio ha una sola implementazione di MetodoCritico, pur essendo contemporaneamente sia derivata da padre e sia implementando l'interfaccia HabemusCriticum.

k0nt3
13-02-2008, 09:05
@gugoXX
e se due interfaccie hanno un metodo con lo stesso nome, stessi parametri, ma tipi di ritorno diversi? :fagiano:
mi sa che il compilatore si offende in ogni caso, ma dovrei provare. in java bisogna stare attenti anche con la dichiarazione delle eccezioni.
questo non toglie IMHO che ereditarietà singola + interfacce porta nella maggiorparte dei casi a soluzioni più eleganti e più semplici da comprendere. poi anche il compilatore può togliersi questo peso :p

fek
13-02-2008, 09:51
Infatti in caso di "conflitto" dev'essere il programmatore a "sbrogliare la matassa", indicando quale dei due metodi dev'essere chiamato. :D

Comunque i "conflitti" dovuti a metodi con lo stesso nome possono verificarsi anche con le interfacce. ;)

No Cesare perche' c'e' sempre e solo una implementazione eseguibile che viene "esposta" al mondo attraverso due o piu' interfacce, ma il metodo da invocare non e' ambiguo.

PS. Ragassuoli, interfacce senza la i :)

fek
13-02-2008, 09:55
questo non toglie IMHO che ereditarietà singola + interfacce porta nella maggiorparte dei casi a soluzioni più eleganti e più semplici da comprendere. poi anche il compilatore può togliersi questo peso :p

Esistono situazioni come il virtual multiple dispatch, quando il dispach non e' solo sull'oggetto ma anche sugli argomenti del metodo, che possono essere risolti in maniera piu' semplice e elegante attraverso l'ereditarieta' multipla. Per fortuna sono rari e non me ne e' praticamente mai capitato uno per strada.

gugoXX
13-02-2008, 10:42
Se hai tempo potresti buttare giu' un paio di esempi?
Stavo cercando appunto un caso in cui l'eredita' multipla potesse avere vantaggi rispetto alle interfacce (senza la i), ma non me ne e' venuto in mente neppure uno che non riuscissi a risolvere comunque elegantemente con un buon disegno ad interfacce.

fek
13-02-2008, 12:57
Se hai tempo potresti buttare giu' un paio di esempi?
Stavo cercando appunto un caso in cui l'eredita' multipla potesse avere vantaggi rispetto alle interfacce (senza la i), ma non me ne e' venuto in mente neppure uno che non riuscissi a risolvere comunque elegantemente con un buon disegno ad interfacce.

Il tempo e' l'unica cosa che non ho mai. No anche i soldi.
A parte gli scherzi, si', cerco di buttare giu' un esempio ma mi devo ristudiare il problema prima, perche' l'ultima mi si e' arrotolato il cervello e non e' un design che si usa spesso.

Ricordamelo pero'.

VICIUS
13-02-2008, 13:22
@gugoXX
e se due interfaccie hanno un metodo con lo stesso nome, stessi parametri, ma tipi di ritorno diversi? :fagiano:
Se intendi due funzioni con stessa signature ma con tipo di ritorno diverso in c# non è possibile farlo quindi il problema non si pone. Per essere più chiaro questo esempio non è c# valido.
public class MediaAritmetica
{
public double media(int x, int y)
{
;
}

public float media(int x, int y)
{
;
}
}

wingman87
13-02-2008, 13:36
Credo che lui intendesse due interfacce distinte con ognuna un metodo con stesso nome e parametri ma tipo di ritorno diverso e poi una terza classe che le eredita

VICIUS
13-02-2008, 13:43
Credo che lui intendesse due interfacce distinte con ognuna un metodo con stesso nome e parametri ma tipo di ritorno diverso e poi una terza classe che le eredita
Dovrebbe comunque implementare i metodi dell'interfaccia nella stessa classe e arriverebbe a quella situazione. Io la prima volta che ci sono cascato o pensato bene di usare una partial class ma il compilatore non è stato così tonto da cascarci :D

k0nt3
13-02-2008, 14:05
Dovrebbe comunque implementare i metodi dell'interfaccia nella stessa classe e arriverebbe a quella situazione. Io la prima volta che ci sono cascato o pensato bene di usare una partial class ma il compilatore non è stato così tonto da cascarci :D

infatti supponevo che il compilatore si sarebbe offeso :fagiano: il mio era un discorso più teorico che pratico

astorcas
13-02-2008, 14:34
Caspita mi avete illuminato!

Ma allora in caso di ereditarietà multipla (vedi C++) che cavolo fa il compilatore se una classe estende 2 classi aventi stesso metodo con comportamento completamente diverso? Bisogna specificare esplicitamente quale dei due utilizzare, si ha errore in compilazione o cos'altro? In effetti potrei provare da me....

marco.r
13-02-2008, 23:49
Se hai tempo potresti buttare giu' un paio di esempi?
Stavo cercando appunto un caso in cui l'eredita' multipla potesse avere vantaggi rispetto alle interfacce (senza la i), ma non me ne e' venuto in mente neppure uno che non riuscissi a risolvere comunque elegantemente con un buon disegno ad interfacce.

Un esempio che mi viene in mente e' quando hai da interfacciare delle classi con middleware tipo CORBA o ICE. In quel caso alle interfacce CORBA (/ICE) corrisponde non una interfaccia pure, ma una classe con del codice per il marshalling e per la gestione dell'oggetto stesso.
Supponi di avere la seguente situazione

Robot
/ \
/ \
/ \
FlyingRobot GenericRobot
\ / \
R2D2 HK-47

dove Robot e FlyingRobot sono le due classi che rappresentano l'interfaccia col mondo esterno, mentre in GenericRobot fattorizzi del codice comune a tutte le sottoclassi di Robot... in questo caso vuoi avere la possibilita' della ereditarieta' multipla visto che da un lato non vuoi ripetere il codice in tutte le sottoclassi, e dall'altro FlyingRobot e' una classe con solo una parte dei metodi astratti, e sulla quale hai poco margine di manovra.
A me questo sembra un caso in cui tutte le alternative sembrano meno semplici e pulite.

marco.r
13-02-2008, 23:55
Esistono situazioni come il virtual multiple dispatch, quando il dispach non e' solo sull'oggetto ma anche sugli argomenti del metodo, che possono essere risolti in maniera piu' semplice e elegante attraverso l'ereditarieta' multipla. Per fortuna sono rari e non me ne e' praticamente mai capitato uno per strada.

Il multiple dispatch torna comodo in quei contesti in cui devi implementare in modo elegante operatori binari tra tipi diversi che fanno parte di una stessa famiglia, ad esempio la somma tra un razionale e un immaginario, senza dover implementare quintali di operatori di conversione, magari da dover chiamare esplicitamente.
Per inciso qualcuno conosce qualche linguaggio che supporti il multiple dispatch in modo "naturale", oltre a CLOS ? :confused:

cdimauro
14-02-2008, 07:33
No Cesare perche' c'e' sempre e solo una implementazione eseguibile che viene "esposta" al mondo attraverso due o piu' interfacce, ma il metodo da invocare non e' ambiguo.
Io mi riferivo a questo:
Credo che lui intendesse due interfacce distinte con ognuna un metodo con stesso nome e parametri ma tipo di ritorno diverso e poi una terza classe che le eredita
:)

cdimauro
14-02-2008, 07:36
Un esempio che mi viene in mente e' quando hai da interfacciare delle classi con middleware tipo CORBA o ICE. In quel caso alle interfacce CORBA (/ICE) corrisponde non una interfaccia pure, ma una classe con del codice per il marshalling e per la gestione dell'oggetto stesso.
OT Sei il primo qui che vedo che conosce ICE. :p

Se non sono indiscreto, posso chiederti per cosa lo usate? :)
Supponi di avere la seguente situazione

Robot
/ \
/ \
/ \
FlyingRobot GenericRobot
\ / \
R2D2 HK-47

R2D2 :rotfl: Il forum ormai ha preso una "brutta" piega... :asd:

fek
14-02-2008, 09:41
Io mi riferivo a questo:

:)

Se hanno tipo di ritorno diverso sono due metodi diversi e sono illegali quindi il problema non si pone :)
(Non in C++ se i due tipi di ritorno sono compatibili)

fek
14-02-2008, 09:43
Il multiple dispatch torna comodo in quei contesti in cui devi implementare in modo elegante operatori binari tra tipi diversi che fanno parte di una stessa famiglia, ad esempio la somma tra un razionale e un immaginario, senza dover implementare quintali di operatori di conversione, magari da dover chiamare esplicitamente.


In questo scenario, io continuerei a preferire gli operatori, ma siamo davvero al caso limite.

cdimauro
14-02-2008, 10:01
Se hanno tipo di ritorno diverso sono due metodi diversi e sono illegali quindi il problema non si pone :)
Sì, lo so che non si pone: si è scelto di rimuovere alla radice il problema, che è quello su cui volevo far puntare l'attenzione.
(Non in C++ se i due tipi di ritorno sono compatibili)
Però col C++ puoi sempre specificare quale metodo invocare, se non lo sono. :)

fek
14-02-2008, 10:41
Però col C++ puoi sempre specificare quale metodo invocare, se non lo sono. :)


No, non puoi:


int X() {}
void X() {}


Genera errore:
1>.\FranTestbed.cpp(30) : error C2556: 'void X(void)' : overloaded function differs only by return type from 'int X(void)'

gugoXX
14-02-2008, 10:47
1>.\FranTestbed.cpp(30) : error C2556: 'void X(void)' : overloaded function differs only by return type from 'int X(void)'

che fai, sono le 11:00 e pensi gia' al letto? :p :D

cdimauro
14-02-2008, 11:19
No, non puoi:


int X() {}
void X() {}


Genera errore:
1>.\FranTestbed.cpp(30) : error C2556: 'void X(void)' : overloaded function differs only by return type from 'int X(void)'
Mi riferivo a una classe che eredita il metodo int X() dalla classe A e void X() da B: non si può fare lo stesso?

fek
14-02-2008, 11:27
Mi riferivo a una classe che eredita il metodo int X() dalla classe A e void X() da B: non si può fare lo stesso?

Cesare, quale parte di No non e' ancora chiara? :D
E' legale solo se i tipi di ritorno sono covarianti.

cdimauro
14-02-2008, 11:40
OK, ricordavo diversamente. Grazie per la precisazione (e scusa la cocciutaggine, ma pensavo, sbagliando, che non ci fossimo capiti). :)

sercharlie
16-03-2008, 15:44
Alcune volte ... mi sono trovato anche in Java a desiderare l'ereditarietà multipla per riutilizzare i metodi di due classi genitori differenti, ma debbo dire che la situazione si può anche risolvere scrivendo - e prevedendo - opportuni metodi statici all'interno delle supposte superclassi.

Con riferimento a questo esempio qui (http://www.tgofbs.it/?p=74) io semplicemente scriverei:

public abstract class Poligono {
public abstract double getArea();
public abstract double getPerimetro();
}
public class Rettangolo extends Poligono {
public double getArea() { return getArea(this); }
public double getPerimetro() { return getPerimetro(this); }
static double getArea(Poligono p) { ... }
static double getPerimetro(Poligono p) { ... }
}
public class Rombo extends Poligono {
public double getArea() { return getArea(this); }
public double getPerimetro() { return getPerimetro(this); }
static double getArea(Poligono p) { ... }
static double getPerimetro(Poligono p) { ... }
}
public class Quadrato extends Poligono {
public double getArea() { return Rettangolo.getArea(this); }
public double getPerimetro() { return Rombo.getPerimetro(this); }
}

Col risultato di riutilizzare tutto il codice scritto, poiché Quadrato per calcolare l'area chiama il metodo statico di Rettangolo mentre per calcolare il perimetro (mera moltiplicazione della lunghezza del lato per 4) chiama il metodo statico di Rombo.