Practical PHP Patterns: Separated Interface
Join the DZone community and get the full member experience.Join For Free
Before analyzing this pattern, we have to clarify some concepts and introduce some kind of nomenclature.
A component, or package, is a set of cohesive classes. In PHP applications, it is usually homogeneous to a folder, or a one of the highest level namespaces. Examples of components are Zend_Auth, sfYaml, Doctrine\DBAL or Doctrine\ORM.
There are also different levels of abstraction for components: Doctrine\ORM\Metadata is a subcomponent of Doctrine\ORM.
If you are reading this article, of course there is no reason to explain the term class.
A dependency (which can be established between classes or interfaces or abstract classes or components) is defined as follows. If A depends on B, in case we delete B from the codebase, A ceases to work, even in its own unit tests. In fact, if we can break the dependency in unit test, than there is not a dependency at all.
The cease to work errors reside at the level of parse errors or unit tests error, where the class is really tested as much as possible in isolation.
For example, if Car depends on AbstractEngine, since it takes it as a collaborator in its constructor, it can't be instantiated in unit tests without AbstractEngine, but it can without a concrete implementation (with a mock - generated or handrolled - for example). If Car extends AbstractCar instead, its source file can't even be included without its dependency.
If even only one class (or interface) A from a component P depends on a class (or interface) B from a component Q, the component P depends on the component Q. We can't delete Q without the class A ceasing to work, so that the component P becomes incomplete and not functional (probably not only by name but also by fact, since the other classes may have dependencies on the broken A).
The Separated Interface pattern prescribes to define an interface into a package P but implement it in another one Q. The goal of the pattern is one of the most noble in software engineering: breaking dependencies.
In our example, Q depends now on P, but P does not depend on Q. The dependency is now backwards with respect to the classic solution of keeping interface and implementation together.
In fact, this pattern is commonly used to implement Dependency Inversion: if the interface was kept with its implementation, P would have dependended on Q. There are obviously various techniques to realize then instantiation and lifecycle management (such as Dependency Injection or a Service Locator).
When using this pattern, higher-level components do not depend anymore on lower-level ones, but only on their own abstractions, contained in them. Different implementations for the lower-level components can be used.
This approach can be taken further by defining an intermediate packages for the interfaces: P depends on A (Abstraction) by composition, while Q depends on A by implementation or subclassing. Also according to Fowler, this variation comes handy when there is more than one client package (homegeneous to P).
At runtime, the software cannot work without implementation for all the used interfaces. However, components can be tested in almost total isolation.
In our example with a stub or a mock for the interface for P. At the same time, with the only dependency of an interface (no code but only method signatures) for Q. Here you have more flexibility because you can swap implementations even in production (not with a mock but with other real implementations) to create new composite behaviors.
Note that the actual the instantation is not a responsibility of each of the interacting components: an higher-level layer with a Factory (used at startup or as a runtime-wired Factory object hanging around ready to instantiate every needed class) or another creational pattern will perform this job.
The principle that emerges with the creation of Separated Interfaces is that the developers of the client code are responsible for defining the interface they use. The client, and not the implementation, should drive the interface form.
This happens all the time with TDD in the mockist style, where you extract mocks in test code and implement them with concrete classes later: the unit tests define not only the class contract on the user side (which methods it has) but also on the collaborator one (which collaborators and which methods it uses).
While introducing a third intermediate component containing interfaces, this responsibility is moved away from both the client and implementation components.
This pattern can be an overkill if only one implementation of the lower-level component is needed, or if the division in components is more nominal than real. In the case of three packages, if one of the two sides have no multiple implementations there is really no need for further separation.
We'll show some UML class diagrams for the situations described in this article.
Here is one for the simplest Separated Interface implementation.
While this is the case with three separate components.
Opinions expressed by DZone contributors are their own.