Entity interface anti-pattern
This has been discussed many times before on various mailing lists, and I’m sure there are blog posts about it that are eluding me currently, but I’ll put it out there anyway.
Interfaces on entities are an anti-pattern
(when they’re in the vein of ICustomer and Customer)
This smells of applying a rule a little too broadly. People are (rightly) being urged to program to abstractions, to decouple and explicitly reveal their dependencies. Programming to abstractions is nearly always a good thing, but rules are often accompanied by exceptions, and in this case it’s entities that are the exception.
Lets take a step back and remind ourselves why abstractions are useful, specifically programming to interfaces.
- It lets us program against a contract, rather than a concrete implementation
- We can design multiple implementations of an interface, without altering dependencies
- We can substitute implementations at will
Taking a typical service example, it’s much better for a TextWriter to depend on a IStream than it is to depend on a FileStream, because we could substitute the stream for a MemoryStream or a more high-level XmlStream without changing the design; if we didn’t have this abstraction we wouldn’t have the flexibility. A side-effect of this ability is greatly improved testability.
Back to entities. Here are some tell-tale signs that you’re might be implementing an anti-pattern:
- Your interface signature identical to your class
- There’s only one implementation of your interface
- Your entity has no behavior to abstract, yet you’ve created an abstraction anyway
Signatures
A contract specifies what functionality an instance should provide. An implementation will rarely be of the same size (method count wise) as the contract because it will have additional code dedicated to how it provides the expected behavior. If the only thing your class does is exist purely to provide backing fields for your interface’s properties and methods, then it’s fairly redundant to have both the class and interface. It’s duplication for the sake of an abstraction that provides no benefits.
Implementations
Excluding cases when you have an inheritance hierarchy mapped, you won’t have more than one implementor of an entity interface; you’ll simply have ICustomer and Customer, IProduct and Product. What’s the point in being able to substitute implementations when there’s guaranteed to only ever be one? Again, a redundant design when used with entities.
Behavior
Abstraction is about allowing consumers of your object to use it without knowledge of the actual implementation. The behavior that an entity contains should mostly concern only itself (such as state changes, adding and removing children, etc…); this kind of behavior has no side-effects and can rarely vary in implementation, therefore entities don’t need abstractions.
What about testability? With changes only affecting itself, the entities themselves can be used in tests, so there’s no need to mock them.
What’s a good example of interfaces on an entity?
Like any rule, there are exceptions; in this case it’s when your interface doesn’t fit the 3 rules mentioned above. Interfaces should be logical components that are combined to make a single unit; they can be used to contain sub-sections of behavior that are implemented by multiple entities. This design allows you to create services that depend on the specific piece of behavior, rather than an a whole entity, thus giving you a service that can be used with many entities (and ones that don’t exist yet) without changing the design. For example, given House, Car, and Wall classes, if each of these implemented an IPaintable interface, a service could instruct these entities to be painted without knowing (or caring) what kind of entity it was dealing with (specifically, without creating an overload for each entity type).
Don’t apply rules blindly, understand what you’re doing and why you’re doing it.
File this one under things we all already knew.
Edit: Updated the behavior section to be less controversial (and more correct!) and added the good example