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. WhatDefaultFormDataValue
is aFormDataValue
(that should be used as default).FormDataValueImpl
is anImplementation
(forFormDataValueImpl
). Whoever reads the implementation inheritances asis a
will readDefaultFormDataValue is FormDataValue
(which is sound) andFormDataValueImpl is a FormDataValue
(which cannot be derived linguistically).Default
is correct because pure logic implies that there is only one default. On the other handImpl
implies that there is an implementation (but there could be more …).
Default
is more robust for future implementations. If we extend ourImpl
code by a compact implementation, we would haveFormDataValueImpl
next toFormDataValueCompactImpl
(where linguistically the second is a subconcept of the first). WithDefault
we would haveDefaultFormDataValue
next toCompactFormDataValue
(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). RenamingCompactDataValue
toDefaultDataValue
is comprehensible,CompactDataValue
(orDataValueCompactImpl
) toDataValueImpl
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 oneImpl
(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).