to Overview
Okt. 12, 2016

Naming of Interfaces and Implementations

Many of you are probably familiar with code containing an interface FormDataValue and implementation classes FormDataValueImpl. However I received consent on my complacent criticism, accompanied by an excuse like: “Our team decided not to follow a naming condition like e.g. IFormDataValue.”

I feel my criticism was misunderstood? There are almost always more than two options in naming classes. Why do some teams only find these two? I will try to analyze the problem more deeply …


Which Problem is solved with Single Implementation Interfaces?

The answer is not trivial. In direct Conversations I often felt, that there are two objectives:

  • decoupling an abstract interface from the concrete implementation
  • even in the long run there will only be a single implementation

The most popular purpose of decoupling is that code gets more flexible to changes in future. But constraining the implementations to exactly one is contradictory (we explicitly constrained ourselves not to be flexible). Actually the idea of having exactly one implementation often leads to a programming style, where this constraint is also exploited in code, e.g. by casting to the implementation class, e.g..

FormDataValue value = ...;

// other code

FormDataValueImpl impl = (FormDataValueImpl) value;
impl.doNonInterfaceAction();

From my point of view one should critically consider, whether it is more important to be decoupled or to constrain yourself to exaclty one implementation: These objectives are contradictory:

  • whoever uses decoupling as explicit desgign principle (e.g. by definining new implementation classes) will eventually have multiple implementations
  • whoever uses the constraint of a single implementation (e.g. by casting from interface to implementation class) will eventually violate decoupling

Why …Impl and I… are not good naming

In general we expect names to be precise and descriptive for the described object. With ...Impl and I... we do not describe the object, but the role in our programming language, to be specific, whether the object is an interface or an implementation, both information

  • that is redundant at definition time (we can clearly see what it is)
  • and that should not change anything at use time (we should not care about what it is)

For the name of an implementation we should be careful that the way of this implementation it is described in some way. We will have to face the following problem of .NET collections, if we disrespect this principle:

For an interface IList we find a sub type List, but from the name nobody will know what to expect from this list. Java and .NET developers will probably expect to find an array based list, functional developers (using Haskell or ML) tend to think that linked lists should be the default. I can only speculate about the thoughts of F# developers (that are functional and .NET). Many third party implementations will have to decide whether to comply to this convention (and consequently call their own array lists List, which would lead to ambiguities) or to break with this convention (which also confuses the user). The collection library C5 breaks the convention and calls its array-based list ArrayList.

In Java we find and interface List with implementations ArrayList and LinkedList. This is more robust and if we have a view on third party collection libraries we will find many collection names, that are descriptive and consistent. The collection library fastutil provides different implementations for object and primitive types so the names are even more specific then in the java api (e.g. ObjectArrayList and ByteArrayList)

However there are good reasons not to describe the implementation in the type name, especially if later changes (of the current implementation) are expected. In this case we can assign a prefix like Default.

Maybe some of the readers might argue that DefaultFormDataValue is not better than FormDataValueImpl. After all there are following arguments:

  • Default expresses the intended role. The type is the default implementation, which should be used in case of doubt. Impl does not express the same, we only assume the same because of conventions.
  • Default is linguistically more correct. What DefaultFormDataValue is a FormDataValue (that should be used as default). FormDataValueImpl is an Implementation (for FormDataValueImpl). Whoever reads the implementation inheritances as is a will read DefaultFormDataValue is FormDataValue (which is sound) and FormDataValueImpl is a FormDataValue (which cannot be derived linguistically).
  • Default is correct because pure logic implies that there is only one default. On the other hand Impl implies that there is an implementation (but there could be more …).
    Default is more robust for future implementations. If we extend our Impl code by a compact implementation, we would have FormDataValueImpl next to FormDataValueCompactImpl (where linguistically the second is a subconcept of the first). With Default we would have DefaultFormDataValue next to CompactFormDataValue (both are linguistically distinguishable)
  • Default reminds the user that there may be non default implementations. Everyone directly casting the interface into the implementation type cannot argue that he skipped an explicit type check because there cannot be more implementations.
  • Maybe an old implemention should be replaced (e.g. CompactDataValue get the new default). Renaming CompactDataValue to DefaultDataValue is comprehensible, CompactDataValue (or DataValueCompactImpl) to DataValueImpl lets the user riddling about the name changed.

Furthermore I... und ...Impl introduce a fragile naming convention:

  • I... only works if the whole community enforces this convention (this is probably only the case for .NET)
  • ...Impl breaks immediately, if somebody plans multiple implementations for an interface. In this case the developer must decide, whether to use one Impl (with other implementations deviating from the convention) or to abandon the convention completely. There are no community best practices to solve this conflict in a consistent way.

Some more detailed and other aspects can be found here (1,2,3).