C++ – Interface and Inheritance: Best of both worlds

cclass-designdesigninheritanceinterfaces

I 'discovered' interfaces and I started to love them. The beauty of an interface is that it is a contract, and any object that fulfills that contract can be used wherever that interface is required.

The problem with an interface is that it can't have a default implementation, which is a pain for mundane properties and defeats DRY. This is also good, because it keeps the implementation and the the system decoupled. Inheritance, on the hand, maintains a tighter coupling, and has the potential of breaking encapsulation.

Case 1 ( Inheritance with private members, good encapsulation, tightly coupled)

class Employee
{
int money_earned;
string name;

public:
 void do_work(){money_earned++;};
 string get_name(return name;);
};


class Nurse : public Employee: 
{
   public:
   void do_work(/*do work. Oops, can't update money_earned. Unaware I have to call superclass' do_work()*/);

};

void HireNurse(Nurse *n)
{
   nurse->do_work();
)

Case 2 (just an interface)

class IEmployee
{
     virtual void do_work()=0;
     virtual string get_name()=0;
};

//class Nurse implements IEmployee.
//But now, for each employee, must repeat the get_name() implementation,
//and add a name member string, which breaks DRY.

Case 3: (best of both worlds?)

Similar to Case 1. However, imagine that (hypothetically) C++ did not allow overriding methods except those methods that are pure virtual.

So, in Case 1, overriding do_work() would cause a compile-time error. To fix this, we set do_work() as pure virtual, and add a separate method increment_money_earned(). As an example:

class Employee
{
int money_earned;
string name;

public:
 virtual void do_work()=0;
 void increment_money_earned(money_earned++;);
 string get_name(return name;);
};


class Nurse : public Employee: 
{
   public:
   void do_work(/*do work*/ increment_money_earned(); ); .
};

But even this has problems. What if 3 months from now, Joe Coder creates a Doctor Employee, but he forgets to call increment_money_earned() in do_work()?


The question:

  • Is Case 3 superior to Case 1? Is it because it of 'better encapsulation' or 'more loosely coupled', or some other reason?

  • Is Case 3 superior to Case 2 because it conforms with DRY?

Best Answer

One way to solve the forgetting-to-call-the-superclass problem is to give the control back to the superclass! I've re-jiggered your first example to show how (and made it compile ;)). Oh, I also assume that do_work() in Employee was supposed to be virtual in your first example.

#include <string>

using namespace std;

class Employee
{
    int money_earned;
    string name;
    virtual void on_do_work() {}

    public:
        void do_work() { money_earned++; on_do_work(); }
        string get_name() { return name; }
};

class Nurse : public Employee
{
    void on_do_work() { /* do more work. Oh, and I don't have to call do_work()! */ }
};

void HireNurse(Nurse* nurse)
{
    nurse->do_work();
}

Now do_work() cannot be overridden. If you want to extend it you have to do it via on_do_work() which do_work() has control over.

This, of course, can be used with the interface from your second example as well if Employee extends it. So, if I understand you correctly I think that makes this Case 3 but without having to use hypothetical C++! It's DRY and it has strong encapsulation.