The other day I stumbled across "Is it Possible to do Object-Oriented Programming in Java", a video presentation by Kevlin Henney hosted at InfoQ. I had expected the same tired comparisons to Smalltalk and C++, but I was pleasantly surprised. Despite the talk's title, it had very little to do with Java; the bulk of the discussion centered around abstraction. The one thing that really stuck with me was the purist notion of writing code such that concrete classes appear only to the right of "new" operators, and using interfaces everywhere else (even in concrete implementations of equals and hash code functions).
So in a new project I've been experimenting with this. I started by identifying all of the types and services I would need to deal with and defined them as interfaces. The arguments of each method call are defined in terms of either primitives or interfaces. Whenever I had to deal with concrete types in the underlying framework I defined interfaces to wrap them.
The first thing I noticed was that I was able to model the entire project without a single line of functional code. What is interesting here is that I was able to design it without knowing how to implement it. In this particular case I was very familiar with the problem domain, but I did not have a clue about how to achieve that functionality using the underlying framework. But since I wasn't dealing with concrete types I didn't have to.
The second thing I noticed was how many pieces of the system could be injected using Spring or some other IoC container. This would enable me to stub or mock many pieces of the application and easily swap them out during the container initialization steps as the actual implementations were completed. There are no constructors, so it is easy to imagine any of the types being constructed from some kind of factory or IoC container.
Lastly, but perhaps most importantly, when I took the previous observations into consideration I realized that I didn't have to obsess about the details of the implementation. The implementation classes may be ugly, horrendous masses of spaghetti code, but that doesn't matter. They can be swapped out whenever it is convenient. If the initial implementation was with concrete classes, that would be a much more difficult undertaking. Concrete classes leak all kinds of public, internal (package protected), and protected members, and refactoring them is a difficult task. However, if all of the interactions between classes are constrained to an interface or function prototype, then there are no surprises. Any implementation of the interface is interchangeable.
I've read some articles on test-driven development that present the notion of writing tests before writing code. And while that has always made sense on an academic level, I've never believed it to be practical until now. By modeling all interactions with interfaces, the tests are obvious and can actually compile (but not run) before the first line of actual code is written.
Other than a few extra interface types in the project, I haven't really noticed a downside to this approach--I've even extracted interfaces for aggregate root and entity types in DDD. It really helps put the focus on abstraction and encapsulation, and removes a lot of pressure when coding the actual implementation.