Some Thoughts on Interfaces

 One principle that I have seen enunciated is that one should not have an interface with only one implementation.

This is generally sensible. Why separate out an interface if there is no variation? You can get physical insulation without an interface, if you need one, using the PIMPL idiom.

(This approach explicitly rejects the idea of creating an interface/implementation pair just because you expect a need to add another implementation in three months. Just be aware that coding in that context needs to be careful - the first time you create a class which owns an instance of the class with potential variation, and it doesn't do so through a pointer of some sort, you're adding more work than is necessary down the line when you suddenly need to be holding a pointer to an interface.

My own view is that if you really know that a second implementation is barrelling down the tracks towards you, there's no reason not to put the eventually-required separation of responsibilities into place -- with documentation as to why and the expected design for when the second implementation actually arrives.)

I can think of two considerations which qualify this.

The first is that although an application proper may have only one implementation of an interface, you may want another one to provide a mock or stub class for unit testing. This is especially true if the implementation uses external resources such as files, sockets, or databases and does not have those injected as resources allowing those activities to be locked indirectly. Or it could be that the internal use of, say, a database connection is complex enough that mocking the activity would be needlessly complex when not testing the implementation itself.

The second is that having an interface in a low-level package and an implementation in a high-level package may make eminent sense if the implementation has many package dependencies but the interface itself does not.

One codebase I worked on had the concept of "business day". This was used throughout in determining a fair number of sorts of details in transaction processing, and in those contexts it's a simple single-valued concept.

The implementation was heavily dependent on database lookups which themselves used fairly complicated models which had been developed in much higher-level libraries.

Making the Business Day generation part of an interface allows packages to do what they need to without having a physical dependency on the multiple packages used in implementing the concept. An application would need all the levels, but no package up to a very high level would.

Of course, this assumes, implicitly, case 1 in any case: unit testing of the lower-level packages required mocking the BusinessDay calculator.

It may also be the case that a single implementation is hiding a better design that would be done with two or more. Drawing a NullObject pattern out of a class which requires checks with an empty() or isNull() call can simplify a good deal of code.[1] Or different currently de facto associations of data values within the class should be recognized as explicit subtypes. (Wheel size, gas mileage, weight, and riskiness in accidents are different clusters for SUVs and smaller cars. You might be able to capture the variation in a class which simply represents a car, but you may discover that it's better to define the types explicitly.)

[1] Defining empty() for a class of your own is frequently a code smell. Sometimes you need it, but often it can be avoided in a way which leads to cleaner logic, in which case a null object (and very possibly a larger subset of implementations) can avoid using a test at all because only one branch will hold true per type.

This is especially true if the logic is like

if (!foo empty())
.... // Do something else using foo, possibly several times...

which is usually a signal that the block should be moved inside foo.

Comments

Popular posts from this blog

Boundaries

Overview

Considerations on an Optimization