2

Is it bad design to have an abstract class inherit from another abstract class? I have a single base node (class BaseNode) and 3 possible child node types (Sink, Process, and Source).

Is the inheritance between BaseNode and SinkNode good practise? If not what is another solution?

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) 
2

Yes, it is generally a bad design; however, I am sure they may be a case or two where it is appropriate, but I haven't seen one.

In fact, use of abstract base classes, even one, is generally bad. Inheritance is for polymorphism, not code resuse; and composition should be favored over inheritance to begin with.

With that said, we work in the real world, and tools and are here to make working software, not to be used in a exercise of spiritual purity. In some cases, it just makes practical sense to use an abstract base class for code reuse; however, it should be done with great reservation. Here are my personal rules for abstract base class use:

  1. Abstract base class can save a great deal of coding in at least 3 decedent classes
  2. The abstract base class's methods do not call any of its other methods
  3. The abstract base class aggregates multiple interfaces
  4. The abstract base class' behavior is easily overridable
  5. The abstract base class should NEVER be referenced by anyone except its inheritors, only reference the aggregated interfaces externally.

I do not think your particular use case warrants even one abstract class, never the less two.

If I were you, I would just declare several interfaces, one for each shared method, then implement everything individually in the sink and datawrite nodes. This will give you much more flexibility in the future.

Let me give you an example as to why you should not be using abstract base classes and inheritance haphazardly:

Let's say your boss tells you to make a game, the player class needs to be able to shoot any of three types of enemies: goblins, trolls, and elves. Since they act largely the same, you define a abstract Enemy class which they all inherit from. Now you make your player class which has a shoot method taking the abstract Enemy class as an argument.

This looks great, and you've saved a lot of time and code... until your boss adds a requirement that if you shoot a golden box, your health refills... obviously your golden box cannot inherit from your your Enemy class, it shares nothing in common with them except the ability to be shot. Now you have to implement two versions of the Shoot method... there goes code reuse.

-1

I don't think it's bad design. In the following scenario, I think it is appropriate.

class IControl
{
public:
    virtual void draw() = 0;
};

class IButton: public IControl
{
pulic:
    virtual void click() = 0;
};

class WinButton: public IButton
{
public:
    void draw() { cout<<"Windows button drawn"<<endl; }
    void click() { cout<<"Windows button clicked"<<endl; }
};

Please point me out if I am wrong. Thanks!

  • 1
    Please describe why you think it's appropriate; don't just dump code – Jan Doggen Sep 25 at 8:07
-1

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

I don't think so. I have been using it in a large C++ application at work for more than twenty years.

I'll present a simplified view of the kinds of entities a CAD application would deal with.

There's usually a common base class that deals with names of objects, to attach user specific data to an object, etc.. However, there are pure virtual functions to make a copy of the object, to write an object to a file or stream and read from a file or stream, etc.

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;
};

Some of the leaf level types are Part, Assembly, Face, Edge, Vertex.

Part and Assembly usually have a base class. An Assembly is conceptually a composite Part. Hence, they have a lot of common functionality that can be captured in an intermediate base class, such as Model.

class Model : public BaseEntity
{
  public:

    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, and Vertex are topological entities of the BREP model. The common functoinality of these classes can be captured in an intermediate clas, such as 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.
    // ...
    //

};

Neither Model nor TopologyEntity are concrete classes. They define abstractions that make sense. They may implement common functionality. They may add more virtual functions that the concrete classes must implement.

Here's a diagram of such a class hierarchy.

enter image description here

  • 3
    You may be correct, but this answer could use some more support. Unless it is true that large, 20 year old applications never contain bad design. – user82096 Apr 19 '17 at 15:21
  • @dan1111, of course. – R Sahu Apr 19 '17 at 16:12

protected by gnat Sep 25 at 10:54

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.