Casi d'uso dell'ereditarietà multipla

14

Java omette l'ereditarietà multipla sulla base del fatto che ovvia l'obiettivo progettuale di mantenere la lingua semplice .

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

Quali sono i tipici pattern di problemi che traggono vantaggio da un codice progettato per fare un uso intensivo dell'ereditarietà multipla

    
posta treecoder 14.08.2011 - 13:56
fonte

7 risposte

10

Pro:

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

Contro:

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

In C ++ un buon esempio di ereditarietà multipla usata per composte caratteristiche ortogonali è quando usi CRTP , per esempio, configurare un sistema componente per un gioco.

Ho iniziato a scrivere un esempio, ma penso che un esempio del mondo reale sia più degno di attenzione. Alcuni codici di Ogre3D utilizzano l'ereditarietà multipla in un modo piacevole e molto intuitivo. Ad esempio, < Una classe href="https://bitbucket.org/sinbad/ogre/src/b91d204db480/OgreMain/include/OgreMesh.h"> Mesh eredita sia da Risorse 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ò inoltre contenere una serie di animazioni. Sembra naturale no?

Puoi consultare altri esempi in questa libreria , come il modo in cui l'allocazione della memoria viene gestita in modo fine, rendendo le classi ereditate dalle varianti di una classe CRTP overloading new ed delete.

Come detto, i problemi principali con l'eredità multipla aumentano dalla combinazione di concetti correlati. Rende il linguaggio necessario impostare implementazioni complesse (vedi il modo in cui C ++ consente di giocare con il problema del diamante ...) e l'utente non è sicuro di cosa sta succedendo in quell'implementazione. Ad esempio, leggi questo articolo spiegando come è implementato in C ++ .

La rimozione dalla lingua aiuta a evitare le persone che non sanno come la lingua viene utilizzata 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 tu possa pensare.

    
risposta data 14.08.2011 - 14:08
fonte
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. Le mixine sono generalmente utilizzate come un modo per una classe di accumulare diversi pezzi di funzionalità. Senza ereditarietà multipla, devi utilizzare l'aggregazione / delega per ottenere un comportamento di tipo mixin con una classe, che è un po 'più sintattica.

    
risposta data 15.08.2011 - 14:10
fonte
2

Penso che la scelta sia principalmente basata su problemi dovuti al problema dei diamanti .

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

Non sono sicuro del significato della tua ultima domanda. Ma se è "in quali casi è utile l'eredità multipla?", Allora in tutti i casi in cui vorresti avere un oggetto A avente funzionalità degli oggetti B e C, in pratica.

    
risposta data 14.08.2011 - 16:55
fonte
2

Non approfondirò molto qui ma puoi sicuramente capire l'ereditarietà multipla in python tramite il seguente link link :

The only rule necessary to explain the semantics is the resolution rule used for class attribute references. This is depth-first, left-to-right. Thus, if an attribute is not found in DerivedClassName, it is searched in Base1, then (recursively) in the base classes of Base1, and only if it is not found there, it is searched in Base2, and so on.

...

It is clear that indiscriminate use of multiple inheritance is a maintenance nightmare, given the reliance in Python on conventions to avoid accidental name conflicts. A well-known problem with multiple inheritance is a class derived from two classes that happen to have a common base class. While it is easy enough to figure out what happens in this case (the instance will have a single copy of ''instance variables'' or data attributes used by the common base class), it is not clear that these semantics are in any way useful.

Questo è solo un piccolo paragrafo ma abbastanza grande per chiarire i dubbi che immagino.

    
risposta data 14.08.2011 - 14:39
fonte
1

Un luogo in cui l'ereditarietà multipla sarebbe utile è una situazione in cui una classe implementa diverse interfacce, ma si vorrebbe avere alcune funzionalità predefinite integrate in ogni interfaccia. Questo è utile se la maggior parte delle classi che implementano un'interfaccia vogliono fare qualcosa allo stesso modo, ma occasionalmente devi fare qualcosa di diverso. Puoi avere ogni classe con la stessa implementazione, ma ha più senso spingerla in un'unica posizione.

    
risposta data 15.08.2011 - 13:49
fonte
1

What are the typical problem patterns that benefit from a code designed to make heavy use of multiple inheritance?

Questo è solo un esempio, ma ne trovo di valore inestimabile per migliorare la sicurezza e attenuare le tentazioni per 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 veramente classi base astratte così come le interfacce che hanno solo un po 'di implementazione per far rispettare gli aspetti universali dei loro contratti, non stanno davvero restringendo la generalità del contratto tanto quanto meglio rafforzandolo.

Semplice esempio (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 f è chiamato da migliaia di posti nella base di codice, mentre f_impl è sovrascritto da un centinaio di sottoclassi.

Sarebbe difficile eseguire questo tipo di controllo di sicurezza in tutti i 1000 luoghi che chiamano f o tutti i 100 posti che sovrascrivono 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 l'astrazione minimamente, poiché sta semplicemente affermando una precondizione richiesta per chiamare questa funzione. In un certo senso, sta probabilmente rafforzando il contratto fornito dall'interfaccia e alleggerendo l'onere di verificare l'input x per assicurarsi che sia conforme alle precondizioni valide in tutti i 100 luoghi che lo sovrascrivono.

È qualcosa che vorrei che ogni linguaggio avesse, e anche desiderato, anche in C ++, che fosse un po 'più di un concetto nativo (es: non ci richiede di definire una funzione separata per l'override).

Questo è estremamente utile se non hai fatto questo assert in anticipo, e ti sei reso conto che ne avevi bisogno in seguito, quando alcuni posti casuali nella base di codice stavano incontrando valori negativi passati a f .

    
risposta data 06.01.2016 - 21:05
fonte
0

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

Secondo: ereditarietà multipla da interfacce astratte

    
risposta data 14.08.2011 - 14:05
fonte

Leggi altre domande sui tag