Casi d'uso di ereditarietà multipla


15

Java omette l'ereditarietà multipla in quanto evita l'obiettivo di progettazione di mantenere il linguaggio semplice .

Mi chiedo se Java (con il suo ecosistema) sia davvero "semplice". Python non è complesso e ha eredità multipla. Quindi, senza essere troppo soggettivo, la mia domanda è ...

Quali sono i tipici schemi di problemi che beneficiano di un codice progettato per fare un uso pesante dell'ereditarietà multipla


8
Java omette moltissime cose perfettamente buone per pochissime ragioni. Non mi aspetterei una giustificazione decente per l'MI.
DeadMG

2
L'eredità multipla di Python è sicuramente il territorio dei draghi . Il fatto che utilizzi la risoluzione dei nomi approfondita, da sinistra a destra, presenta problemi significativi sia per la manutenibilità che per la comprensione. Mentre può essere utile nelle gerarchie di classi superficiali, può essere incredibilmente contro-intuitivo in quelle profonde.
Mark Booth,

Penso che il motivo per cui Java non contenga eredità multipla sia che gli sviluppatori Java volevano che il loro linguaggio fosse facile da imparare. L'eredità multipla, sebbene incredibilmente potente in alcuni casi, è difficile da comprendere e ancora più difficile da usare con buoni risultati; non è la cosa con cui vorresti confrontarti con una matricola della programmazione. Voglio dire: come spieghi l'eredità virtuale a qualcuno che sta lottando con il concetto stesso di eredità? E, dal momento che anche l'ereditarietà multipla non è esattamente banale dal punto di vista degli implementatori, è probabile che gli sviluppatori Java dispongano di una vittoria.
cmaster - ripristina monica il

Java è tipicamente digitato. Python non lo è. Ciò rende l'ereditarietà multipla molto più semplice da implementare e comprendere in Python.
Jules

Risposte:


11

Professionisti :

  1. A volte consente una modellazione più ovvia di un problema rispetto ad altri modi per modellarlo.
  2. Se i diversi pappagalli hanno uno scopo ortogonale, può consentire un qualche tipo di composizione

Contro:

  1. Se i diversi genitori non hanno uno scopo ortogonale, rende il tipo difficile da capire.
  2. Non è facile capire come sia implementato in una lingua (qualsiasi lingua).

In C ++ un buon esempio di ereditarietà multipla utilizzata per comporre caratteristiche ortogonali è quando si utilizza CRTP per, ad esempio, configurare un sistema componente per un gioco.

Ho iniziato a scrivere un esempio ma penso che valga la pena guardare un esempio del mondo reale. Alcuni codici di Ogre3D usano l'ereditarietà multipla in un modo piacevole e molto intuitivo. Ad esempio, la classe Mesh eredita sia da Resources che da AnimationContainer. Le risorse espongono l'interfaccia comune a tutte le risorse e AnimationContainer espone l'interfaccia specifica per la manipolazione di una serie di animazioni. Non sono correlati, quindi è facile pensare a una Mesh come a una risorsa che può contenere anche una serie di animazioni. Ti senti naturale, vero?

Puoi guardare altri esempi in questa libreria , come il modo in cui l'allocazione della memoria è gestita in modo granuloso facendo in modo che le classi ereditate dalle varianti di una classe CRTP sovraccarichino di nuovo ed eliminino.

Come detto, i problemi principali con l'ereditarietà multipla sorgono dalla miscelazione di concetti correlati. Fa sì che il linguaggio debba impostare implementazioni complesse (vedere il modo in cui C ++ consente di giocare con il problema del diamante ...) e l'utente non è sicuro di ciò che sta accadendo in tale implementazione. Ad esempio, leggi questo articolo che spiega come è implementato in C ++ .

Rimuoverlo dalla lingua aiuta a evitare le persone che non sanno come la lingua è rafforzata per rendere le cose cattive. Ma costringe a pensare in un modo che, a volte, non sembra naturale, anche se si tratta di casi limite, accade più spesso che potresti pensare.


apprezzerei molto se abbellissi la tua risposta con un problema di esempio - questo renderà più chiari termini come "scopo ortogonale" - ma grazie
treecoder

Ok fammi provare ad aggiungere qualcosa.
Klaim,

Ogre3D non è un posto in cui cercare ispirazione per il design: hai visto la loro infezione da Singleton?
DeadMG

Innanzitutto, l'erede singleton non è in realtà un singleton, la costruzione e la distruzione sono esplicite. Successivamente, Ogre è un livello su un sistema hardware (o il driver grafico, se si preferisce). Ciò significa che dovrebbe esserci solo una rappresentazione univoca per le interfacce di sistema (come Root o altri). Possono rimuovere alcuni singleton ma non è questo il punto. Ho evitato volontariamente di sottolineare questo per evitare di avere una discussione a troll, quindi per favore, guarda gli esempi che ho indicato. Il loro uso di Singleton potrebbe non essere perfetto ma è chiaramente utile nella pratica (ma solo per il loro tipo di sistema, non tutto).
Klaim,

4

Esiste un concetto chiamato mixins che viene utilizzato pesantemente in linguaggi più dinamici. L'ereditarietà multipla è un modo in cui i mixin possono essere supportati da una lingua. I mixin sono generalmente usati come modo per una classe di accumulare diversi elementi di funzionalità. Senza ereditarietà multipla, è necessario utilizzare l'aggregazione / delega per ottenere un comportamento di tipo mixin con una classe, che è un po 'più pesante di sintassi.


+1 questo è in realtà un buon motivo per avere ereditarietà multipla. I mixin hanno connotazioni aggiuntive ("questa classe non dovrebbe essere usata come stand-alone")
ashes999

2

Penso che la scelta si basi principalmente su problemi dovuti al problema dei diamanti .

Inoltre, è spesso possibile eludere l'uso dell'eredità multipla mediante delega o altri mezzi.

Non sono sicuro del significato della tua ultima domanda. Ma se è "in quali casi è utile l'ereditarietà multipla?", In tutti i casi in cui si desidera avere un oggetto A con funzionalità di oggetti B e C, in sostanza.


2

Non approfondirò qui, ma puoi sicuramente comprendere l'eredità multipla in Python tramite il seguente link http://docs.python.org/release/1.5.1p1/tut/multiple.html :

L'unica regola necessaria per spiegare la semantica è la regola di risoluzione utilizzata per i riferimenti agli attributi di classe. Questo è il primo in profondità, da sinistra a destra. Pertanto, se un attributo non viene trovato in DerivedClassName, viene cercato in Base1, quindi (ricorsivamente) nelle classi di base di Base1 e solo se non viene trovato lì, viene cercato in Base2 e così via.

...

È chiaro che l'uso indiscriminato dell'eredità multipla è un incubo per la manutenzione, data la dipendenza da Python nelle convenzioni per evitare conflitti accidentali di nomi. Un problema noto con ereditarietà multipla è una classe derivata da due classi che hanno una classe base comune. Mentre è abbastanza facile capire cosa succede in questo caso (l'istanza avrà una singola copia di `` variabili di istanza '' o attributi di dati utilizzati dalla classe base comune), non è chiaro che queste semantiche siano in alcun modo utile.

Questo è solo un piccolo paragrafo ma abbastanza grande da chiarire i dubbi, credo.


1

Un punto in cui l'ereditarietà multipla sarebbe utile è una situazione in cui una classe implementa diverse interfacce, ma si desidera avere alcune funzionalità predefinite integrate in ciascuna interfaccia. Ciò è utile se la maggior parte delle classi che implementano alcune interfacce vogliono fare qualcosa allo stesso modo, ma a volte è necessario fare qualcosa di diverso. Puoi avere ogni classe con la stessa implementazione, ma ha più senso spingerla verso l'alto in una posizione.


1
Ciò richiederebbe un'ereditarietà multipla generalizzata o semplicemente un mezzo con cui un'interfaccia può specificare comportamenti predefiniti per metodi non implementati? Se le interfacce potessero specificare solo implementazioni predefinite per i metodi che esse stesse implementano (al contrario di quelle che ereditano da altre interfacce), una tale funzione eviterebbe completamente i problemi del doppio diamante che rendono difficile l'ereditarietà multipla.
supercat

1

Quali sono i tipici schemi di problemi che beneficiano di un codice progettato per fare un uso pesante dell'ereditarietà multipla?

Questo è solo un esempio, ma trovo inestimabile migliorare la sicurezza e mitigare le tentazioni di applicare le modifiche a cascata in tutti i chiamanti o sottoclassi.

Dove ho trovato l'ereditarietà multipla incredibilmente utile anche per le interfacce più astratte e senza stato è l'idioma di interfaccia non virtuale (NVI) in C ++.

Non sono nemmeno classi di base veramente astratte tanto quanto interfacce che hanno solo un po 'di implementazione per far rispettare gli aspetti universali dei loro contratti, in quanto non stanno realmente restringendo la generalità del contratto, ma addirittura migliorandolo .

Esempio semplice (alcuni potrebbero verificare che un handle di file passato sia aperto o qualcosa del genere):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

In questo caso, forse fviene chiamato da mille posti nella base di codice, mentre f_implviene sovrascritto da un centinaio di sottoclassi.

Sarebbe difficile fare questo tipo di controllo di sicurezza in tutti i 1000 posti che chiamano fo in tutti i 100 posti che hanno la precedenza f_impl.

Semplicemente rendendo questo punto di accesso alla funzionalità non virtuale, mi dà un posto centrale per eseguire questo controllo. E questo controllo non riduce minimamente l'astrazione, in quanto sta semplicemente affermando un prerequisito necessario per chiamare questa funzione. In un certo senso, sta probabilmente rafforzando il contratto fornito dall'interfaccia e alleggerendo l'onere di controllare l' xinput per assicurarsi che sia conforme ai presupposti validi in tutti i 100 punti che lo sovrascrivono.

È qualcosa che vorrei che ogni lingua avesse, e desiderasse anche, anche in C ++, che fosse un po 'più di un concetto nativo (es: non richiederci di definire una funzione separata da sovrascrivere).

Ciò è estremamente utile se non lo facevi assertin anticipo e ti rendevi conto che ne avevi bisogno in seguito quando alcuni punti casuali nella base di codice stavano incontrando valori negativi a cui venivano passati f.


0

Primo: copie multiple della classe base (un problema C ++) e stretto accoppiamento tra classe base e classi derivate.

Secondo: eredità multipla da interfacce astratte


stai suggerendo che non è utile in nessun contesto? E che tutto può essere progettato / codificato comodamente senza di esso? Si prega inoltre di approfondire il secondo punto.
treecoder
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.