One of the first sound bites from the beloved book Design Patterns is this:

Program to an interface, not an implementation

It would seem that a corollary is that we can measure the quality of our code on the number of interfaces; the more, the better. However, that's not how it feels in reality when you are trying to figure out whether to use an IFooFactory, IFooPolicy, IFooPolicyFactory or perhaps even an IFooFactoryFactory.

Do you extract interfaces from your classes to enable loose coupling? If so, you probably have a 1:1 relationship between your interfaces and the concrete classes that implement them. That's probably not a good sign, and violates the Reused Abstractions Principle (RAP). I've been guilty of this and didn't like the result.

Having only one implementation of a given interface is a code smell.

Programming to an interface does not guarantee that we are coding against an abstraction. Interfaces are not abstractions. Why not?

An interface is just a language construct. In essence, it's just a shape. It's like a power plug and socket. In Europe we use one kind, and the US uses another, but it's only by convention that we transmit 230V through European sockets and 110V through US sockets. Although plugs only fit in their respective sockets, nothing prevents us from sending 230V through a US plug/socket combination.

Krzysztof Cwalina already pointed this out in 2004: interfaces are not contracts. If they aren't even contracts, then how can they be abstractions?

Interfaces can be used as abstractions, but using an interface is in itself no guarantee that we are dealing with an abstraction. Rather, we have the following relationship between interfaces and abstractions:

Abstractions, interfaces and their intersection

There are basically two sets: a set of abstractions and a set of interfaces. In the following we will discuss the set of interfaces that does not intersect the set of abstractions, saving the intersection for another blog post.

There are many ways an interface can turn out to be a poor abstraction. The following is an incomplete list:

LSP Violations #

Violating the Liskov Substitution Principle is a pretty obvious sign that the interface in use is a poor abstraction. This may be most obvious when the consumer of the interface needs to downcast an instance to properly work with it.

However, as Uncle Bob points out, even an interface as simple as this seemingly innocuous rectangle ‘abstraction' contains potential dangers:

public interface IRectangle
{
    int Width { get; set; }
    int Height { get; set; }
}

The issue becomes apparent when you attempt to let a Square class implement IRectangle. To protect the invariants of Square, you can't allow the Width and Height properties to differ. You have a couple of options, none of which are very good:

  • Update both Width and Height to the same value when one of them are being written.
  • Ignore the write operation when the caller attempts to assign an invalid value.
  • Throw an exception when the caller attempts to assign a Width which is different from the Height (and vice versa).

From the point of view of a consumer of the IRectangle interface, all of these options would at the very least violate the Principle of Least Astonishment, and throwing exceptions would definitely cause the consumer to behave differently when consuming Square instances as opposed to ‘normal' rectangles.

The problem stems from the fact that the operations have side effects. Invoking one operation changes the state of a seemingly unrelated piece of data. The more members we have, the greater the risk is, so the Interface Segregation Principle can, to a certain extent, help.

Header Interfaces #

Since a higher number of members increases the risk of unexpected side effects and temporal coupling it should come as no surprise that interfaces mechanically extracted from all members of a concrete class are poor abstractions.

As always, Visual Studio makes it very easy to do the wrong thing by offering the Extract Interface refactoring feature.

We call such interfaces Header Interfaces because they resemble C++ header files. They tend to simply state the same thing twice without apparent benefit. This is particularly true when you have only a single implementation, which tends to be very likely for interfaces with many members.

Shallow Interfaces #

When you use the Extract Interface refactoring feature in Visual Studio, even if you don't extract every member, the resulting interface is shallow because it doesn't recursively extract interfaces from the concrete types exposed by the extracted members.

An example I've seen more than once involves extracting an interface from a LINQ to SQL or LINQ to Entities context in order to define a Repository interface. As an example, here's an interface extracted from a very simple LINQ to Entities context:

public interface IPostingContext
{
    void AddToPostings(Posting posting);
    ObjectSet<Posting> Postings { get; }
}

At first glance this may look useful, but it isn't. Even though it's an interface, it's still tightly coupled to a specific object context. Not only does ObjectSet<T> reference the Entity Framework, but the Posting class is defined by a very specific, auto-generated Entity context.

The interface may give you the impression of working against loosely coupled code, but you can't easily (if at all) implement a different IPostingContext with a radically different data access technology. You'll be stuck with this particular PostingContext.

If you must extract an interface, you'll need to do it recursively.

Leaky Abstractions #

Another way we can create problems for ourselves is when our interfaces leak implementation details. A good example can be found in the SystemWrapper project that provides extracted interfaces for various BCL types, such as System.IO.FileInfo. Those interfaces may enable mocking, but we shouldn't expect to ever be able to create another implementation of SystemWrapper.IO.IFileInfoWrap. In other words, those interfaces aren't very useful.

Another example is this attempt at defining a Repository interface:

public interface IFooRepository
{
    string ConnectionString { get; set; }
    // ...
}

Exposing a ConnectionString property strongly indicates that the repository is implemented on top of a database; this knowledge leaks through. If we wanted to implement the repository based on a web service, we might be able to repurpose the ConnectionString property to a service URL, but it would be a hack at best - and how would we define security settings in that scenario?

Exposing a FileName property on an interface that represents an abstract resource is another example of a Leaky Abstraction.

Leaky Abstractions like these are often difficult to reuse. As an example, it would be difficult to implement a Composite out of the above IFooRepository - how do you aggregate a ConnectionString?

Conclusion #

In short, using interfaces in no way guarantees that we operate with appropriate abstractions. Thus, the proliferation of interfaces that typically follow from TDD or use of DI may not be the pure goodness we tend to believe.

Creating good abstractions is difficult and requires skill. In a future post, I'll look at some principles that we can use as guides.


Comments

anonymous #
How do you propose mocking/stubbing then?
2010-12-02 16:21 UTC
Good abstractions will still be interfaces (or base classes), so replacement with Test Doubles will still be possible.
2010-12-02 16:24 UTC
*they also say that an abstract class is preferred over an interface, I am guilty of the one interface per class, so can't wait to see your point of view for creating abstractions
2010-12-02 16:28 UTC
The whole discussion about abstract base classes versus interfaces is orthogonal to the point I'm trying to make, so I'm not going to go into that discussion in my next post.

The reason why the Framework Design Guidelines favor abstract classes is related to keeping options open for future extensions to abstractions without breaking backwards compatibility. It makes tons of sense when you just have to respect backwards compatibility when adding new features. This is the case for big, commercial frameworks like the BCL. However, I'm beginning to suspect that this is kind of a pseudo-argument; it's really more an excuse for creating abstractions that don't adhere to the Open/Closed Principle.

On the other hand, it isn't that important if you control the entire code base in question. This is often the case for enterprise applications, where essentially you only have one customer and at most a handful of deployments.

The problem with base classes is that they are a much more heavy-handed approach. Because you can only derive from a single base class, it becomes impossible to implement more than one Role Interface in the same class. Because of that constraint, I tend to prefer interfaces over base classes.
2010-12-02 17:57 UTC
So in order to change implementation of a dependency in c# (ie. Throw in a test double) i have to use an interface (unless i use TypeMock or the like).
The way I understand your post is not that you want me to change this, which is also impossible in a statically typed language.
Do I understand you correctly that you are more talking about how I design these dependencies so they would be - well better designed?
So in a way, if I understand you correctly, the things you are saying her could be applied in fx. Ruby as well, since they are design principles and not language stuff?
2010-12-02 20:53 UTC
Yes, the only thing I say is that an interface doesn't guarantee that you are using a good abstraction, but you can certainly use interfaces to model good abstractions - hence the Venn diagram.
2010-12-02 20:59 UTC
Daniel Gioulakis #
Hi there Mark,
I've found this post to be quite an interesting read. At first, I was not in complete agreement, but as I took a step back and examined the way I write domain-driven code, I realized 99% of my code follows these concepts and rarely would I ever have a 1:1 relationship.

Today I happened upon a good video from one of Udi Dahan's talks: "Making Roles Explicit". It can be found here: http://www.infoq.com/presentations/Making-Roles-Explicit-Udi-Dahan

You can find some examples of his talk in practice here:
http://www.simonsegal.net/blog/2010/03/18/nfetchspec-code-entity-framework-repository-fetching-strategies-specifications-code-only-mapping-poco-and-making-roles-explicit/

If you happen to have some time to watch the speech, I was curious to hear your opinion on the subject as it seems like it would violate the concepts of this blog post. I still haven't wrapped my head around it all, but it would seem that Udi recommends creating interfaces for each "role" the domain object plays and using a Service Locator to find the concrete implementation ... or in his case the concrete FetchingStrategy used to pull data back from his ORM. This sounds like his application would have many 1:1 abstractions. I am familiar with your stance on the Service Locator and look forward to your book coming out. :)

Thanks,
Danny
2010-12-17 20:02 UTC
Hi Danny

Thanks for your comments. If you manage to read your way through my follow-up post you'll notice that I already discuss Role Interfaces there :)

I actually prefer Role Interfaces over Header Interfaces, but I can understand why you ask the questions you do. In fact, I went ahead and wrote a new blog post to answer them. HTH :)
2010-12-18 14:27 UTC
Emanuel Pasat #
This blog post is asking for a quick real-time example (simple WCF or Azure example, let's say).
I assume you'll use same constructor injection with abstract factory but how will you avoid 1:1 mapping in these cases? Using the same trick with NullService and WcfService?
What is a good abstraction for a Wcf service or for a CloudQueueClient?

Thanks in advance
2010-12-24 11:23 UTC
I recently wrote an Azure application where I used these abstractions on top of Azure Queues:

public interface IChannel { void Send(object message); }

for sending messages, and this one to handle them:

public interface IMessageConsumer<T> { void Consume(T message); }

Since both are Commands (they return void) they compose excellently.
2010-12-28 08:44 UTC
Emanuel Pasat #
Quote from RAP (http://parlezuml.com/blog/?postid=934) :

"If the only class that ever implements the Customer interface is CustomerImpl, you don't really have polymorphism and substitutability because there is nothing in practice to substitute at runtime. It's fake generality. All you have is indirection and code clutter, which just makes the code harder to understand."

From there I thought that 1:1 mapping means one implementation to one interface.
From your example it seems that 1:1 mapping referes to not having all members of an implementation matching exactly all members of the interface.

Which one is true?
Thanks again
2010-12-29 08:14 UTC
No, I meant exactly the same as the RAP post. Not implementing all members would indicate that an interface has more than one members. That would smell of a Header Interface, and I prefer Role Interface.

What gave you that other impression?
2010-12-29 12:12 UTC
Emanuel Pasat #
The fact that IChannel should have more implementations.

But that's usually accomplished with decorators (we always need cross cutting concerns) and null objects, right?
2010-12-29 12:43 UTC
Yes, that's right, but a Decorator is also an implementation of an interface. As soon as you define your first Decorator, by implication you already have two implementations of the same type.

With an interface like IChannel, a Composite also becomes possible, in the case that you would like to broadcast a message on multiple channels.
2010-12-29 13:13 UTC
Simple #
Can you show please some examples where you use some kind of Test Doubles for Unit Tests without 1:1 Interface implementation? ))

Thanks )




2012-04-21 12:57 UTC
For a framework example, see AutoFixture, which contains some 3000 unit tests - many of them with Test Doubles. Most (if not all) of the interfaces have several implementations, e.g. ISpecimenBuilder.

For a more complete application, see the Booking sample CQRS application. It uses Moq for Test Doubles, and I very consciously wrote that code base with the RAP in mind.
2012-04-22 06:09 UTC
Simple #
Maybe Ill better show some of my code later and ask - where the problem is =)


Do you have plans to publish some books about software development? Some kind of patterns explanation.. or best practices for .NET developers - with actual technologies? (not only DI =))

I think you can not only develop something but also explain how =)

2012-04-23 17:42 UTC
Currently, I don't have any concrete plans for new books, but it's not unlikely that I'll write another book in the future.
2012-04-23 18:12 UTC
Junlong #
You mentioned at the beginning that "Having only one implementation of a given interface is a code smell.", but then you discussed why interfaces are not abstractions in some aspects. Would you mind answering why 1:1 relation between an interface and a implementation class, or posting the link if you have had another post discussing it? Thank you.
2022-03-02 09:20 UTC

Junlong, thank you for writing. This blog post already contains some links, of which I think that Reused Abstractions Principle (RAP) does the best job of describing the problem.

For what it's worth, I also discuss what makes a good abstraction in my book Code That Fits in Your Head.

2022-03-03 6:25 UTC
Alex Murari #

Mark, I think abstraction is (and always has been) an overloaded term. And it hurts our profession, because important concpets become less clear.

Abstraction, in the words of Uncle Bob, is the [...] amplification of the essential and the elimination of the irrelevant."

Divindig a big method in smaller ones is the perfect example of that phrase: we amplify the essential (a call to a new method with a good name indicating what it does) while eliminating the irrelevant (hiding the code [how it does] behind a new method [abstraction]). If we want to know the hows, we navigate to that method implementation. What = essential / How = irrelevant.

This is completelly related to the concept of fractal architecture from your book, we just zoom in when we want. (Great book by the way, going to read it for the 3rd time).

I think we confuse the abstract (concept) with abstract (keyword, opposite of concrete) and interface (concept, aka API) with interface (keyword).

Interfaces (keyword) are always abstractions (concept), because they provide a list of methods/functions (API) for executing some logic that is abstracted (concept) behind it. The real questions are "are they good abstractions?", "Why are you using interfaces (keyword) for a single (1:1) implementation?"

Bottom line: interfaces (keyword) are always abstractions (concept), but not always good ones.

If you have the time, please write an article expanding on that line of reasoning.

2023-08-16 16:35 UTC

Alex, thank you for writing. There are several overloaded og vague terms in programming: Client, service, unit test, mock, stub, encapsulation, API... People can't even agree on how to define object-oriented or functional programming.

I don't feel inclined to add to the confusion by adding my own definition to the mix.

As you've probably observed, I use Robert C. Martin's definition of abstraction in my book. According to that definition, you can easily define an interface that's not an abstraction: Just break the Dependency Inversion Principle. There are plenty of examples of those around: Interface methods that return or take as parameters ORM objects, or returning a database ID from a Create method. Such interfaces don't eliminate the irrelevant.

2023-08-17 7:41 UTC
Alex Murari #

Mark, thanks for the response. There are two overloaded terms in programming that I consider the most important and the most misunderstood: abstraction and encapsulation.

Abstraction is important because it's about managing complexity, encapsulation is important because it's about preserving data integrity.

These are basic, fundamental (I can't stress this enough) concepts that enable the sustainable growth of a software project. I'm on the front-line here, and I know what a codebase that lacks these two concepts looks like. I think you do too. It ain't pretty.

Oddly enough, these aren't taught correctly at all! In my (short) formal training at a university, abstraction was taught in terms of abstract classes/inheritance (OOP). Encapsulation as nothing more than using getters and setters (use auto-properties and you're encapsulated. Yay!). There were no mentions of reducing complexity or preserving integrity whatsoever. I dropped out after two years. The only thing I learned from my time at the university is that there is an immense gap between the industry and educational institutions.

I'm mostly (if not completely) a self-taught programmer. I study a lot, and I'm always looking for new articles, books, etc. It took me ~7 years to find a coherent explanation of abstraction and encapsulation.

Initially, I asked myself "am I the only one who didn't have such basic knowledge, even though being a programmer for almost 10 years?" Then I started asking around work colleagues for definitions of those terms: turns out I wasn't the only one. I can't say I was surprised.

Maybe I am a fool for giving importance to such things as definitions and terminology of programming terms, but I can't see how we can move our profession towards a more engineering-focused one if we don't agree on (and teach correctly) basic concepts. Maybe I need Adam Barr's time machine.

2023-08-17 15:12 UTC

Alex, thank you for writing. I agree with you, and I don't think that you're a fool for considering these concepts fundamental. Over a decade of consulting, I ran into the same fundamental mistakes over and over again, which then became the main driver for writing Code That Fits in Your Head.

It may be that university fails to teach these concepts adequately, but to be fair, if you consider that the main goal is to educate young people who may never have programmed before, maintainability may not be the first thing that you teach. After all, students should be able to walk before they can run.

Are there better ways to teach? Possibly. I'm still pursuing that ideal, but I don't know if I'll find those better ways.

2023-08-20 9:26 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Thursday, 02 December 2010 13:03:04 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Thursday, 02 December 2010 13:03:04 UTC