L'estensione di una classe genitore astratta con una cattiva progettazione di classe figlio astratta?

0

È una cattiva progettazione avere una classe astratta ereditata da un'altra classe astratta? Ho un singolo nodo base (classe BaseNode) e 3 possibili tipi di nodi figlio (Sink, Process e Source).

L'ereditarietà tra BaseNode e SinkNode è una buona pratica? Se no, qual è un'altra soluzione?

class BaseNode (metaclass=ABCMeta):
   @abstractmethod
   def _Execute(self) -> bool:
       pass

   @abstractmethod
   def _Configuration(self) -> bool:
       pass
   def __init__(self, nodeID) 
      print(nodeID)

class SinkNode(BaseNode, metaclass=ABCMeta):
    @staticmethod
    def Type():
        return NodeType.SINK

    def __init__(self, nodeID):
        super().__init__(nodeID)

class DataWriteNode(SinkNode): 
    def _Execute(self): 
       print('Execute') 
    def _Configuration(self):
       print('Configure') 
    def __init__(self, nodeID): 
       super().__init__(nodeID) 
    
posta eman_44 19.04.2017 - 17:06
fonte

2 risposte

1

Sì, è generalmente un cattivo design; tuttavia, sono sicuro che potrebbero essere un caso o due in cui è appropriato, ma non ne ho visto uno.

In effetti, l'uso di classi base astratte, anche una, è generalmente cattivo. L'ereditarietà è per il polimorfismo, non per la rielaborazione del codice; e la composizione dovrebbe essere favorita rispetto all'eredità per cominciare.

Detto questo, lavoriamo nel mondo reale, e strumenti e siamo qui per creare software di lavoro, non per essere utilizzati in un esercizio di purezza spirituale. In alcuni casi, ha senso usare una classe di base astratta an per il riutilizzo del codice; tuttavia, dovrebbe essere fatto con grande riserva. Ecco le mie regole personali per l'uso astratto della classe base:

  1. La classe base astratta può salvare una grande quantità di codice in almeno 3 classi decedenti
  2. I metodi della classe base astratta non chiamano nessuno dei suoi altri metodi
  3. La classe base astratta aggrega più interfacce
  4. Il comportamento della classe base astratta è facilmente sovrascrivibile
  5. La classe base astratta non dovrebbe MAI essere referenziata da nessuno tranne che dai suoi eredi, solo fare riferimento esternamente alle interfacce aggregate.

Non penso che il tuo caso d'uso particolare meriti nemmeno una classe astratta, mai meno due.

Se fossi in te, dichiarerei solo diverse interfacce, una per ciascun metodo condiviso, quindi implementerò tutto individualmente nei nodi sink e datawrite. Questo ti darà molta più flessibilità in futuro.

Lasciatemi fare un esempio sul perché non dovresti usare le basi astratte e l'ereditarietà a casaccio:

Diciamo che il tuo capo ti dice di fare un gioco, la classe del giocatore deve essere in grado di sparare a uno dei tre tipi di nemici: goblin, troll ed elfi. Dato che agiscono in gran parte allo stesso modo, tu definisci una classe nemica astratta da cui ereditano tutti. Ora fai la tua classe di giocatore che ha un metodo di tiro che prende come argomento la classe Enemy astratta.

Questo sembra fantastico, e hai risparmiato un sacco di tempo e codice ... fino a quando il tuo capo aggiunge un requisito che se spari a una scatola d'oro, la tua salute si ricarica ... ovviamente la tua scatola d'oro non può ereditare dalla tua tua Classe nemica, non condivide nulla in comune con loro tranne la possibilità di essere colpito. Ora devi implementare due versioni del metodo Shoot ... ecco come riutilizzare il codice.

    
risposta data 19.04.2017 - 18:18
fonte
-1

Is it bad design to have an abstract class inherit from another abstract class?

Io non la penso così Lo sto usando in una grande applicazione C ++ al lavoro da più di vent'anni.

Presenterò una vista semplificata dei tipi di entità con cui si occuperà un'applicazione CAD.

Di solito c'è una classe base comune che si occupa di nomi di oggetti, di allegare dati specifici dell'utente a un oggetto, ecc. Tuttavia, ci sono pure funzioni virtuali per fare una copia dell'oggetto, per scrivere un oggetto in un file o trasmettere e leggere da un file o stream, ecc.

class BaseEntity
{
   public:    

     void setName(std::string const& name);
     std::string const& getName() const;

     void setAttribute(std::string const& attName, int data);
     void setAttribute(std::string const& attName, double data);
     void setAttribute(std::string const& attName, std::string const& data);

     // Return false if attribute with the given data type does not exist.
     bool getAttribute(std::string const& attName, int& data);
     bool getAttribute(std::string const& attName, double& data);
     bool getAttribute(std::string const& attName, std::string& data);

     BaseEntity* makeCopy() const = 0;
     virtual std::ostream& write(std::ostream& out) const = 0;
     virtual std::istream& read(std::istream& in) const = 0;
};

Alcuni tipi di livelli foglia sono Part , Assembly , Face , Edge , Vertex .

Part e Assembly di solito hanno una classe base. Un Assembly è concettualmente un compoiste Part . Quindi, hanno molte funzionalità comuni che possono essere acquisite in una classe base intermedia, come Model .     modello di classe: BaseEntity pubblica     {       pubblico:

    void setParent(Model* model);
    Model* getParent() const;

    // Assume there is a type called Transform
    Transform getTransformToParent() const;

    // More convenience functions to position a Part/Assembly
    // in the parent Model.
    //
    // ...
    //

    virtual double getVolume() const = 0;
    virtual double getSurfaceArea() const = 0;

    // More query functions common to Part and Assembly
    // ...
    //
};    

Face , Edge e Vertex sono entità topologiche del modello BREP. La funzionalità comune di queste classi può essere catturata in un clas intermedio, come TopologyEntity .

class TopologyEntity : public BaseEntity
{
  public:

    void setID(int id);
    int getID() const;

    void setPart(Part* part);
    Part* getPart() const;

    // More convenience functions for all derived classes.
    // ...
    //

};

ModelTopologyEntity sono classi concrete. Definiscono le astrazioni che hanno senso. Possono implementare funzionalità comuni. Possono aggiungere più funzioni virtuali che le classi concrete devono implementare.

Ecco un diagramma di tale gerarchia di classi.

    
risposta data 19.04.2017 - 17:17
fonte