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?

MustafaM
  • 1,958
  • 3
  • 15
  • 13

5 Answers5

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.

Gyan aka Gary Buyn
  • 2,775
  • 19
  • 17
  • 3
    And that is the design pattern know as "template method" (http://en.wikipedia.org/wiki/Template_method_pattern). – Joris Timmermans Feb 17 '12 at 08: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 '12 at 08:22
  • @MadKeithV are you sure this is the 'template method'? – MustafaM Feb 17 '12 at 08:26
  • @illmath - yes, it's a nonvirtual public method which delegates parts of its implementation details to virtual protected/private methods. – Joris Timmermans Feb 17 '12 at 08: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](http://www.gotw.ca/publications/mill18.htm) which you might want to read where the author believes it deserves its own name: Non-Virtual Interface Idiom – Gyan aka Gary Buyn Feb 17 '12 at 08: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.

BЈовић
  • 13,981
  • 8
  • 61
  • 81
  • Thanks for the comment. I've updated Case 3 to make my intent more clear. – MustafaM Feb 17 '12 at 07: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 '12 at 07:59
  • @DeadMG [ISP](http://en.wikipedia.org/wiki/Interface_segregation_principle) – BЈовић Feb 17 '12 at 08:02
  • @VJovic: There's a *big* difference between SOLID and "Everything must inherit from an interface". – DeadMG Feb 17 '12 at 08: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. – Joris Timmermans Feb 17 '12 at 08:16
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.

Karthik Sreenivasan
  • 1,025
  • 2
  • 10
  • 22
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.

DeadMG
  • 36,794
  • 8
  • 70
  • 139
  • 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 '12 at 07: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 '12 at 07: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; } – Joris Timmermans Feb 17 '12 at 08:01
  • @MadKeithV: I don't believe that you can define them inline, but the point is still the same. – DeadMG Feb 17 '12 at 08:09
  • @MadKeith: As if Visual Studio has ever been a particularly accurate representation of Standard C++. – DeadMG Feb 17 '12 at 08:15
  • Whoops - I need to retract my statement after looking at the draft standard: 10.4: [ Note: A function declaration cannot provide both a pure-specifier and a definition —end note ]. That doesn't seem to preclude a definition outside the declaration and nor does the rest of the language of that part of the standard - but I'll leave that up to the language lawyers! – Joris Timmermans Feb 17 '12 at 08:34
  • @MadKeith: It's well known that you can define pure virtual functions. – DeadMG Feb 17 '12 at 08:40
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.

S.Robins
  • 11,385
  • 2
  • 36
  • 52