Is “program to interfaces” a common design principle in C++ projects?
I read a lot about "program to interfaces" and "inversion of control" during the last days. Mostly in the context with the Java language. My question is if it is also a common practice in C++ development. What are the advantages? What are the disadvantages?
Is it worth to apply to small projects (like 15-20 classes)?
The principles you speak of are generally applicable to any OO language. The basic tenet here is "loose coupling". A class that depends upon another class (contains an instance of it and calls methods on it as part of its own work) really only depends on a set of functionality the dependency provides. If the class defines a reference to a concrete class that it depends on, and you then want to replace the class with another, you not only have to develop the new class, but change the dependent class to depend on the new type. This is generally bad, because if your class is depended on by many other classes, you have to change code in multiple places, requiring you to test all the use cases involving those objects to ensure you haven't broken previously-working functionality.
Interfaces were designed to eliminate this, allowing multiple classes that are unrelated by ancestry to be used interchangeably based on a common, enforced set of methods that you know a class will implement. If, instead of depending on a class, you depended on an interface, any class implementing the interface would fulfill the dependency. That allows you to write a new class to replace an old one, without the class that uses it knowing the difference. All you have to modify is the code that creates the concrete implementation of the class filling the dependency.
This presents a quandary; sure, your class Depender can say it needs an IDoSomething instead of a DoerClass, but if Depender knows how to create a DoerClass to use as the IDoSomething, you haven't gained anything; if you want to replace DoerClass with BetterDoer, you must still change Depender's code. The solution is to give the responsibility for giving the class an instance of a dependency to a third party, a Creator. The class chosen for this depends on the context. If a class naturally has both Depender and DoerClass, it's the obvious choice to use to put them together. This is often the case when you have one class that has two helper dependencies, and one dependency needs the other as well. Other times you may create a Factory, which exists to provide the caller with an instance of a specific object, preferably with all dependencies hooked up.
If you have several interdependent classes, or dependencies many levels deep, you may consider an IoC framework. IoC containers are to Factories as Repositories are to DAOs; they know how to get you a fully-hydrated instance of ANY class requiring one or more dependencies, like a Repository can produce any fully-hydrated domain object from data in the DB. It does this by being told what concrete class should be used to fill a dependency in a certain situation, and when asked to provide a class, it will instantiate that class, providing instances of the required dependencies (and dependencies of dependencies). This can allow patterns where Class A depends on B, which depends on C, but A cannot know about C. The IoC framework knows about all three, and will instantiate a B, give it a new C, then give the B to a new A.
Yes, it is fairly common, but not in the form you might expect.
In Java, the interface is formalized and explicit, and programming to an interface means implementing that specific interface.
In C++, the same is done too, sometimes (although using abstract base classes rather than interfaces), but another common way it is done in C++ is with templates, where the interface is implicit.
For example, the standard library algorithms all work with the iterator "interface", except that no such interface is ever defined in code. It is a convention, and nothing more.
A valid iterator is required to expose certain functionality, and so, any type which exposes this functionality is an iterator. But it doesn't have to implement some kind of hypothetical IIterator interface like you would in Java.
The same is common in user code. You often write your code to accept a template parameter which might be anything that works. You implicitly define an interface through your use of the type: anything you require from it becomes part of this implicit interface that a type must satisfy in order to be usable.
The interface is never formalized in code, but you are still using it and programming against it.