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 f
viene chiamato da mille posti nella base di codice, mentre f_impl
viene sovrascritto da un centinaio di sottoclassi.
Sarebbe difficile fare questo tipo di controllo di sicurezza in tutti i 1000 posti che chiamano f
o 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' x
input 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 assert
in 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
.