Domanda

Diciamo che abbiamo una classe Apple concreta. (Gli oggetti Apple possono essere istanziati.) Ora, qualcuno viene e deriva un Apple dalla classe Peach astratta. È astratto perché introduce una nuova funzione virtuale pura. L'utente di Peach è ora costretto a derivarne e definire questa nuova funzione. È uno schema comune? È corretto farlo?

Esempio:


class Apple
{
public:
    virtual void MakePie();
    // more stuff here
};

class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };

Ora diciamo che abbiamo una bacca di classe concreta . Qualcuno deriva un astratto class Tomato da Berry. È astratto perché sovrascrive una delle funzioni virtuali di Berry e la rende pura virtuale. L'utente di Tomato deve implementare nuovamente la funzione precedentemente definita in Berry. È uno schema comune? È corretto farlo?

Esempio:


class Berry
{
public:
    virtual void EatYummyPie();
    // more stuff here
};

class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };

Nota: i nomi sono inventati e non riflettono alcun codice reale (si spera). Nessun frutto è stato danneggiato nello scrivere questa domanda.

È stato utile?

Soluzione

Re Peach di Apple:

  • Non farlo se Apple è una classe di valore (ovvero ha copia ctor, non identico le istanze possono essere uguali, ecc.). Vedi Meyers Articolo C ++ 33 più efficace per il motivo.
  • Non farlo se Apple ha un pubblico distruttore non virtuale, altrimenti tu invitare comportamenti indefiniti quando il tuo gli utenti eliminano una Apple tramite a puntatore a Peach.
  • Altrimenti, probabilmente sei al sicuro, perché non hai violato sostituibilità di Liskov . A Peach IS-A Apple.
  • Se possiedi il codice Apple, preferisci fattorizzare una classe base astratta comune (Fruit forse) e derivarne Apple e Peach.

Re Tomato di Berry:

  • Come sopra, più:
  • Evita, perché è insolito
  • Se necessario, documenta cosa devono fare le classi derivate di Tomato per non violare la sostituibilità di Liskov. La funzione che stai sostituendo in Berry - chiamiamola Juice () - impone determinati requisiti e fa alcune promesse. Le implementazioni delle classi derivate di Juice () non devono richiedere altro e promettere niente di meno. Quindi un DerivedTomato IS-A Berry e un codice che conosce solo Berry è sicuro.

Probabilmente, soddisfi l'ultimo requisito documentando che DerivedTomatoes deve chiamare Berry :: Juice () . In tal caso, considera invece l'utilizzo del Metodo modello:

class Tomato : public Berry
{
public:
    void Juice() 
    {
        PrepareJuice();
        Berry::Juice();
    }
    virtual void PrepareJuice() = 0;
};

Ora c'è un'eccellente possibilità che un pomodoro sia una bacca IS-A, contrariamente alle aspettative botaniche. (L'eccezione è se le implementazioni delle classi derivate di PrepareJuice impongono condizioni supplementari oltre a quelle imposte da Berry :: Juice ).

Altri suggerimenti

Mi sembrerebbe un'indicazione di un cattivo design. Potrebbe essere forzato se volessi prendere una definizione concreta da una libreria chiusa ed estenderla e ramificarne un sacco di cose, ma a quel punto prenderei seriamente in considerazione le linee guida relative all'incapsulamento sull'ereditarietà. Se è possibile incapsulare , probabilmente dovresti.

Sì, più ci penso, questa è una pessima idea.

Non necessariamente sbagliato , ma decisamente puzzolente. Soprattutto se si lascia il frutto fuori al sole per troppo tempo. (E non credo che al mio dentista piacerebbe che mangiassi mele concrete.)

Tuttavia, la cosa principale che vedo qui che è maleodorante non è tanto la classe astratta derivata da una classe concreta, ma la gerarchia dell'eredità DAVVERO PROFONDA.

EDIT: rilettura Vedo che si tratta di due gerarchie. Tutta la roba della frutta mi ha fatto confondere.

Se si utilizza la pratica consigliata di disporre del modello di ereditarietà "is-a" allora questo schema non verrebbe quasi mai fuori.

Una volta che hai una classe concreta, stai dicendo che è qualcosa di cui puoi effettivamente creare un'istanza. Se poi ne deriva una classe astratta, allora qualcosa che è un attributo della classe base non è vero per la classe derivata, che dovrebbe impostare dei klaxon che qualcosa non va.

Guardando il tuo esempio, una pesca non è una mela, quindi non dovrebbe derivarne. Lo stesso vale per il pomodoro derivante dalla bacca.

Qui è dove consiglierei di solito il contenimento, ma non sembra nemmeno essere un buon modello, dal momento che una mela non contiene una pesca.

In questo caso, vorrei scomporre l'interfaccia comune - PieFilling o DessertItem.

un po 'insolito, ma se tu avessi qualche altra sottoclasse della classe base e le sottoclassi della classe astratta avessero abbastanza cose comuni da giustificare l'esistenza della classe astratta come:

class Concrete
{
public:
    virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
    virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
    virtual void eat()=0;
    // and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
    void eat() {}
};
class Sub2:public Abstract {
    void eat() {}
};
int main() {
    Concrete *sub1=new Sub1(),*sub2=new Sub2();
    sub1->eat();
    sub2->eat();
    return 0;
}

Hmmm ... pensando "che ....." per un paio di secondi, arrivo a una conclusione che non è comune ... Inoltre, non deriverei Peach da Apple e Tomato da Berry ... hai qualche esempio migliore? :)

È un sacco di strana merda che puoi fare in C ++ ... Non riesco nemmeno a pensare all'1% di esso ...

A proposito di sovrascrivere un virtuale con un puro virtuale, probabilmente puoi semplicemente nasconderlo e sarà davvero strano ...

Se riesci a trovare uno stupido compilatore C ++ che collegherebbe questa funzione come virtuale, otterrai una chiamata di funzione virtuale pura in fase di runtime ...

Penso che questo possa essere fatto solo per un hack e non ho idea di che tipo di hack davvero ...

Per rispondere alla tua prima domanda, puoi farlo poiché gli utenti di Apple, se viene data un'istanza concreta derivata da Peach, non sapranno nulla di diverso. E l'istanza non saprà che non è una Apple (a meno che non ci siano alcune funzioni virtuali di Apple che sono state ignorate di cui non ci hai parlato).

Non riesco ancora a immaginare quanto sia utile sostituire una funzione virtuale con una pura funzione virtuale - è legale?

In generale, vuoi conformarti a Scott Meyers "Rendi astratte tutte le classi non foglia". oggetto dai suoi libri.

Comunque, a parte quello che descrivi sembra legale - è solo che non riesco a vederti averne bisogno così spesso.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top