0

I have a code like this. My derived class man, may not properly have a member function declared pure virtual higher up the hierarchy. eg. WagTail here

class mammal{
public:
    virtual void WagTail() = 0;
    virtual void BreastFeed() { };
    ~mammal(); 
};

class Terrestrials : public mammal{
public:
    virtual void sunbathe() = 0;
};

class cat : public Terrestrials {
    void sunbathe() override final  {
        cout << "My owner made me stand in the sun";
    }
    void WagTail() override final   {
        cout << "Wagging Tail";
    }
};

class Man : public Terrestrials {
    void sunbathe() override final  {
        cout << "Life is good!";
    }
    void WagTail() override final   {
        // Wait, Wha??? ERROR!!!!
    }
}; 

What is the best way to fix a mistake like this without rewriting the entire hierarchy?

  • 1
    slightly off-topic, but not all terrestrials are mammals. – Matthew Apr 2 '15 at 18:32
4

The fundamental problem is that a pure virtual function was added very high in a class hierarchy which not all conceivable subclasses can plausibly support. This is why one should be very careful defining deep class hierarchies. I see a couple approaches.

One approach is to simply provide an empty implementation of WagTail for the Man class. Hopefully this won't break anything. And after all, primates generally have tails, only for people they have withered away to an internal coccyx (tailbone) because they provided no benefit. And some people are in fact born with "tails".

Another is to move tail-ness into a separate class, really just an interface, and have most Terrestrials inherit from that. This way other classes, like Reptiles and Fish, can also have tails and a WagTail method. In general, multiple inheritance of interfaces is preferable to deep class hierarchies.

Historical Note: Back in the 1980s and early 1990s, before there was Java and C++ compilers did not widely support multiple inheritance, deep class hierarchies were quite common. Microsoft's MFC was quite deep, as was Apple's MacApp.

  • Thanks. Can you cite an article that describes how to design classes when you could end up with a deep hierarchy? – The Vivandiere Apr 2 '15 at 18:29
  • Are you looking for cases where it is OK to end up with a deep hierarchy or how to avoid deep hierarchies? – Randall Cook Apr 2 '15 at 18:33
  • Both of the mentioned. When is it ok, When is not ok, and what to do when it is not ok? – The Vivandiere Apr 2 '15 at 18:34
  • 1
    I did a little digging and found a SO question, and a couple pages on OO design, especially deep class hierarchies. With deep class hierarchies, at each level one is introducing methods and attributes, all of which must be relevant to all subclasses. One is also introducing tight coupling to the parent. These simultaneously increase complexity and decrease flexibility, leading to error-prone code. – Randall Cook Apr 2 '15 at 18:48
4

Actually, Randall Cook gave a very good answer here, but I would like to add something. Assumed you are going to implement "WagTail" for "man", the correct way of implementing it depends on the expectations of the code calling that method on mammals. If the caller expects some kind of error behaviour or exception to be thrown, then you could actually implement that. If the caller does not expect it, you may implement it empty.

That's why for any interface or pure virtual base class, there should be a contract somewhere, a formal or informal definition of the expected behaviour. Without a contract, you can only guess whats necessary not to violate the Liskov Substitution Principle.

2

This is a case of a Liskov Substitution Principle violation.

Your mammal class appears to be misnamed, not all mammals have tails as you may have noticed.

You may be able to solve it with multiple-inheritence.

class Waggable
    + WagTail

class Terrestrial
    + SunBathe

class Cat : Terrestrial, Waggable

class Man : Terrestrial

Though I'm not sure what you mean by not rewriting the hierarchy. The hierarchy is the problem so I don't know how you can 'fix' it without changing it.

  • Lets say I am not allowed to rename mammal. I added breastfeed member function to simulate that – The Vivandiere Apr 2 '15 at 18:27
  • 2
    You can still have all classes inherit from mammal, but you need to move the WagTail out of it. – Matthew Apr 2 '15 at 18:28
  • 1
    This situation might create a violation of the LSP, but it is not clear it is already one (see my answer). And the hierarchy itself would not be a problem without that method in stake. – Doc Brown Apr 2 '15 at 18:40
  • Right, it all depends on what the expectations there are around that method. I made some assumptions and labelled it as an LSK violation based on the fact that OP never defined what the expectations were. – Matthew Apr 2 '15 at 18:45
  • You have the tail wagging the dog. A dog's tail is Waggable. The dog is a Wagger. – kevin cline Apr 2 '15 at 19:15
0

In this case, tail-wagging is just the expression of something more fundamental (excitement?). Rename WagTail at the high level to something more "simple" (ExpressExcitement?), update the invoking code, and just invoke the relevant subclass's WagTail methods/code from their ExpressExcitement methods.

That could be a lot of work (it could also be a really quick search/replace); but it's probably worthwhile to make your common denominator more meaningful / less implementation specific.

... I guess this may fail your "without rewriting the entire hierarchy" requirement. But, I'd posit that the requirement is wrong.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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