Classe base astratta con solo membri protetti

3

Spesso, estraggo la logica comune da una classe creando una classe base astratta con solo membri protetti. Ad esempio:

class Base {

protected:

    void foo() { ... }
    std::map<KeyType, ValueType> d_map;

};

class Derived : public Foo {

public:

    Derived() : Base() { ... }

    // ...

};

La mia logica qui è che foo() e d_map possono essere usati privatamente su più classi derivate, quindi questa astrazione li colloca in una rappresentazione base comune.

Domanda : questo schema ha senso (ed è considerato un buon stile) da una prospettiva di progettazione rigorosa? Sarebbe preferibile creare una classe separata con la funzionalità protetta come membri accessibili pubblicamente e fare riferimento a quelli delle sottoclassi derivate?

    
posta Ryan 12.04.2017 - 21:17
fonte

4 risposte

6

Se stai usando la classe base come metodo per condividere l'implementazione tra le due (o più) classi derivate e la classe base non rappresenta un'interfaccia, l'ereditarietà pubblica è quasi certamente un errore.

Una possibilità sarebbe di usare l'aggregazione (mettere i dati condivisi in una classe helper e includere un'istanza di quella classe in ogni classe "derivata" che ne ha bisogno).

Un'altra possibilità sarebbe quella di utilizzare l'ereditarietà privata. L'ereditarietà privata viene solitamente descritta come "è implementata in termini di". Laddove l'eredità pubblica deve essere conforme al Principio di sostituzione di Liskov (almeno in un disegno decente), l'ereditarietà privata non comporta tale requisito. Con l'ereditarietà privata, la classe derivata è "consapevole" della sua relazione con la classe base e può utilizzare l'implementazione dalla classe base. Quella relazione è "nascosta" dal mondo esterno, quindi, c'è non una conversione implicita dalla classe derivata alla classe base come avviene con l'ereditarietà pubblica.

class base { };

class D1 : public base { };

class D2 : base { };

int main() { 
    base *b = new D1; // no problem
    base *b2 = new D2; // won't compile
}

Se insisti davvero, puoi convertire un puntatore in derivato in un puntatore in base - ma devi usare un cast (e, cosa interessante, deve essere un cast in stile C, nessuno dei "nuovi" "I cast di C ++ possono farlo correttamente 1 ).

  1. Per coloro che sono tentati di contestare questo: sì, un reinterpret_cast sembrerà fare correttamente il lavoro per alcuni casi - ma (per dare un solo controesempio) se usi l'ereditarietà multipla, verrà convertito solo da derivato uno dei tipi di base correttamente. Tentare di convertire in un altro tipo di base produrrà risultati cattivi.
risposta data 13.04.2017 - 01:44
fonte
0

Tutto dipende dalla logica di business delle entità che ereditano la tua classe base.

Se tutti hanno in comune funzioni e campi e non è necessario effettuare alcuna sostituzione speciale che rende la loro funzione diversa nelle diverse classi ereditate, allora questo stile è preferibile in quanto è coerente con la L in SOLID, vale a dire Principio di Liskov .

MA, se la logica delle entità ereditate è diversa o se desideri raggruppare entità con una classe base comune, solo perché hanno queste due cose in comune e assolutamente nient'altro, allora una classe "helper" è una scelta preferibile . C'è una citazione comune quando rottura del principio di Liskov .

If it looks like a duck, quacks like a duck, but needs batteries, then you probably have the wrong abstraction.

    
risposta data 12.04.2017 - 21:56
fonte
0

Bene, avere dati accessibili pubblicamente in una classe è una cattiva pratica e sicuramente non considerato un buon stile.

In ogni caso dovresti derivare privatamente da quella classe. La derivazione protetta potrebbe avere senso in alcuni casi.

Tuttavia, di solito sarebbe meglio usare invece la composizione. Rende tale classe testabile da sola (dato che il metodo è pubblico) e quella classe può essere utilizzata da qualche altra parte.

Potrebbe anche aiutare a evitare ereditarietà multiple se hai bisogno di quella funzionalità in una classe che ha già una classe base.

È sempre una buona idea avere meno accoppiamenti tra le classi. La derivazione pubblica è un collegamento più strong e dovrebbe essere utilizzata solo l'oggetto derivato È un oggetto di base. L'ereditarietà protetta o privata sono meno accoppiati, ma la composizione è ancora migliore.

    
risposta data 14.04.2017 - 03:32
fonte
0

Non posso dire se sia una buona pratica o meno finché non conosco la semantica del metodo in relazione a quella della classe.

Se la tua classe base era Animal e il metodo condiviso rappresenta il comportamento animale come Eat, Talk o Poop, sei bravo in una prospettiva OO. Se il metodo è comunque solo una funzione di handy-dandy per scopi generici come inviare una email o elaborare una stringa o stampare una rappresentazione testuale dell'oggetto, NON si farebbe bene nel reparto di progettazione OO.

    
risposta data 14.04.2017 - 22:25
fonte

Leggi altre domande sui tag