10

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?

1
  • 2
    ...are you reinventing abstract classes or what?
    – ZJR
    Feb 17, 2012 at 8:14

5 Answers 5

10

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.

5
  • 3
    And that is the design pattern know as "template method" (en.wikipedia.org/wiki/Template_method_pattern). Feb 17, 2012 at 8:19
  • Yes, this is Case 3-compliant. This looks promising. Will examine in detail. Also, this is some sort of event system. Is there a name for this 'pattern'?
    – MustafaM
    Feb 17, 2012 at 8:22
  • @MadKeithV are you sure this is the 'template method'?
    – MustafaM
    Feb 17, 2012 at 8:26
  • @illmath - yes, it's a nonvirtual public method which delegates parts of its implementation details to virtual protected/private methods. Feb 17, 2012 at 8:30
  • @illmath I hadn't thought of it as a template method before but I believe it is a basic example of one. I just found this article which you might want to read where the author believes it deserves its own name: Non-Virtual Interface Idiom Feb 17, 2012 at 8:34
1

The problem with an interface is that it can't have a default implementation, which is a pain for mundane properties and defeats DRY.

In my own opinion, interfaces should have only pure methods - without a default implementation. It doesn't break the DRY principle in any way, because interfaces shows how to access some entity. Just for references, I am looking at the DRY explanation here :
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."

On the other hand, the SOLID tells you that every class should have an interface.

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

No, the case 3 is not superior to case 1. You have to make up your mind. If you want to have a default implementation then do so. If you want a pure method then go with it.

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

Then Joe Coder should get what he deserves for ignoring failing unit tests. He did test this class, didn't he? :)

Which Case is the best for a software project which might have 40,000 lines of code?

One size doesn't fit all. It is impossible to tell which one is better. There are some cases where one would fit better then the other.

Maybe you should learn some design patterns instead of trying to invent some of your own.


I just realized that you are looking for non-virtual interface design pattern, because that is what your case 3 class looks like.

5
  • Thanks for the comment. I've updated Case 3 to make my intent more clear.
    – MustafaM
    Feb 17, 2012 at 7:56
  • 1
    I'm gonna have to -1 you here. There's no reason at all to say that all interfaces should be pure, or that all classes should inherit from an interface.
    – DeadMG
    Feb 17, 2012 at 7:59
  • @DeadMG ISP Feb 17, 2012 at 8:02
  • @VJovic: There's a big difference between SOLID and "Everything must inherit from an interface".
    – DeadMG
    Feb 17, 2012 at 8:14
  • "One size doesn't fit all" and "learn some design patterns" are correct - the rest of your answer violates your own suggestion that one size doesn't fit all. Feb 17, 2012 at 8:16
0

Interfaces can have default implementations in C++. There is nothing saying that a default implementation of a function does not solely depend on other virtual members (and arguments), so doesn't increase any kind of coupling.

For case 2, DRY is superseding here. Encapsulation exists to protect your program from change, from different implementations, but in this case, you have no different implementations. So YAGNI encapsulation.

In fact, run-time interfaces are usually considered inferior to their compile-time equivalents. In the compile-time case, you can have both case 1 and case 2 in the same bundle- not to mention it's numerous other advantages. Or even at run-time, you can simply do Employee : public IEmployee for effectively the same advantage. There are numerous ways to deal with such things.

Case 3: (best of both worlds?)

Similar to Case 1. However, imagine that (hypothetically)

I stopped reading. YAGNI. C++ is what C++ is, and the Standards committee is never, ever, going to implement such a change, for excellent reasons.

7
  • You say "you have no different implementations". But I do. I have Nurse implementation of Employee, and I might have other implementations later (a Doctor, a Janitor, etc.). I've updated Case 3 to make it more clear what I meant.
    – MustafaM
    Feb 17, 2012 at 7:55
  • @illmath: But you have no other implementations of get_name. All of your proposed implementations would share the same implementation of get_name. Besides, as I said, there is no reason to choose, you can have both. Also, Case 3 is utterly worthless. You can override non-pure virtuals, so forget about a design where you can't.
    – DeadMG
    Feb 17, 2012 at 7:59
  • Not only can interfaces have default implementations in C++, they can have default implementations and still be abstract! i.e. virtual void IMethod() = 0 { std::cout << "Ni!" << std::endl; } Feb 17, 2012 at 8:01
  • @MadKeithV: I don't believe that you can define them inline, but the point is still the same.
    – DeadMG
    Feb 17, 2012 at 8:09
  • @MadKeith: As if Visual Studio has ever been a particularly accurate representation of Standard C++.
    – DeadMG
    Feb 17, 2012 at 8:15
0

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

From what I see in your implementation, your Case 3 implementation requires an abstract class which can implement pure virtual methods which can then later be changed in the derived class. Case 3 would be better as the derived class can change the implementation of do_work as and when required and all the derived instances would basically belong to the base abstract type.

Which Case is the best for a software project which might have 40,000 lines of code.

I would say it purely depends on your implementation design and the objective that you want to achieve. Abstract class and Interfaces are implemented based on the problem that has to be solved.

Edit on question

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

Unit tests can be performed to check whether each class confirms to the expected behavior. So if proper unit tests are applied, bugs can be prevented when Joe Coder implements the new class.

0

Using interfaces only breaks DRY if each implementation is a duplicate of every other. You can resolve this dilemma by applying both interface and inheritance, yet there are some cases where you may wish to implement the same interface on a number of classes, but vary the behavior in each of the classes, and this will still keep to the principle of DRY. Whether you choose to use any of the 3 approaches you've described comes down to the choices you need to make to apply the best technique to match a given situation. On the other hand, you'll probably find that over time, you use Interfaces more, and apply inheritance only where you wish to remove repetition. That's not to say that this is the only reason for inheritance, but that it's better to minimize the use of inheritance to allow you to keep your options open if you find your design needs to change later on, and if you wish to minimize the impact on descendant classes from the effects that a change would introduce in a parent class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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