I haven't been putting thought into this post so I thought I could at least post the things I have been thinking about and some resources. This is dealing with the use and idea of the code construct (C# interfaces). Feel free to comment with your own questions, answers, or resources.
what makes a good interface?
can you really refactor out to an interface?
if so how good would that interface really be?
how good did your class before hand have to be?
if not then it's important to consider interfaces up front
how do you make the judgement to put in the effort of making a good interface?
are these really just problems with explicit static typing instead of dynamic typing or inferred static typing?
how do you ensure the least astonishment with interfaces?
links for research:
http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstractions.aspx
http://martinfowler.com/bliki/RoleInterface.html
http://simpleprogrammer.com/2010/11/02/back-to-basics-what-is-an-interface/
http://www.objectmentor.com/resources/articles/isp.pdf
http://www.lostechies.com/blogs/jimmy_bogard/archive/2010/12/02/the-case-against-interfaces-in-tdd.aspx
http://publicvoidlife.blogspot.com/2010/10/to-learn-is-to-abuse.html
http://en.wikipedia.org/wiki/Law_of_Demeter
http://en.wikipedia.org/wiki/Principle_of_least_astonishment
Nice post with some comments about the use of inheritance:
http://www.lostechies.com/blogs/derickbailey/archive/2011/01/18/i-use-inheritance-and-i-m-not-entirely-ashamed-of-it-should-i-be.aspx
Awesome links. Hopefully I'll have time later today to get through the rest of them. I would like to counter one point made in the first link, though. The author says, "Having only one implementation of a given interface is a code smell." I see the point he's getting at, but I don't necessarily agree with that statement at face value.
ReplyDeleteMechanically going through the motions of making "header interfaces" for every class is something of a code smell, yes. Not everything is going to need an interface. Generally, most of what I write within the scope of domain logic generally doesn't have an interface. I usually make more use of inheritance there and less use of interfaces.
However, anything within my problem domain which depends on an external resource (anything outside of the control of the domain code) get an interface in the domain and an implementation in a separate project. 9 times out of 10, it gets one and only one implementation. If I'm tinkering around with another implementation (say, another tool to handle the dependency) then it'll have a second or a third, but only temporarily or experimentally and not used in Production code.
At this point the creation of those interfaces feels like it's just out of habit, but it does have a point and that point isn't dulled by the fact that there's only one implementation at any given time. I may want (or need) to switch out something about that dependency later. I may want to change logic within the dependency, and the interface "contract" is a way to ensure that I (or whoever else works on it) is at least reminded of the contract as a set of requirements. Sure, this almost never happens. But it might, and that's why it's there.
On a more abstract note (about abstractions), I tend to think of interfaces as "what something does." This is probably why I don't use many in my domain objects, but rather in my services behind which I keep the dependencies. (Though there are always exceptions to that trend, such as domain objects at my last job which were IFraudable, meaning that the state of a given object could be "potentially fraudulent" according to business rules, but only for certain objects. There was no dependency, it was entirely business logic and therefore entirely in domain code.) When dealing with abstractions of "what something is" I try to use inheritance more. When dealing with "what something does" I use an interface.
There's often overlap, but that's ok. The language is designed to handle that overlap. What something _is_ can also _do_ multiple things. To use the classic car analogy from OOP 101: A Camry _is_ a Car, it _does_ IDrivable, ISittable, ICargoCarrier, etc.
Granted, even with that analogy there are, as always, potential problems with the logic. If most implementations of ISittable are furniture, and suddenly a Car implements that interface, there's not the inherent requirement that to properly sit in a car you must open the door and climb inside. This is something not present in furniture. There's the possibility of astonishment in that. Dealing with that possibility depends on external factors (the userbase, the product owners, etc.) on a case-by-case basis.
Actually I'm not sure if inheritance is ever truly needed. The big thing you get out of it is code reuse, but you could get that same stuff from composition. As for the polymorphism you get with inheritance, it's nothing you couldn't obtain with interfaces. I think it's pretty well understood that composition and interfaces give you looser coupling and greater abilities then inheritance, so I'm starting to think that inheritance is really just something to make working with classes easier.
ReplyDeleteThat said I tend to use it in domain models and I believe that is because I don't mind the tighter coupling...or at least I haven't paid a price for it. With a domain model, I'm hopefully capturing the real world meaning and interactions in code. If these were things that were to change, then perhaps composition is the way to go. Of course we never really know and good practices and strong design can create a system easily adaptable to change.
Your ideas around interfaces seem to go pretty well with what I have seen and read around .NET and Java code. Where the interface is some kind of ability. I'm not sure if I subscribe to this mentality anymore. I believe in my "To Learn is to Abuse" post I touched on how I'm starting to think of how everything should be considered an interface. Perhaps we are too focused on classes with it's the interfaces that are the important part. Alan Kay (smalltalk and oop) has said that the big idea was messaging not inheritance and classes.
I'll add that I think a good rule of thumb is that if your inheritance hierarchy gets too deep (maybe even just beyond 2 levels) then it's a code smell. I say that because they can become brittle and it is very tightly coupling those classes (which are suppose to be related anyway if you are using inheritance). But if you run into several special cases that cause inheriting another level down to achieve the functionality you want, then composition would provide a better in almost all ways solution I think.
ReplyDeleteWhile I imagine there are outlying cases in problems domains where I've simply never worked, I can't say that I've ever had more than at _most_ 2 levels of inheritance in my domain. Beyond that it starts to feel "too abstract" and I would agree that it could easily become a code smell. Often it ends up being concrete children of an abstract base class.
ReplyDeleteTo continue the analogy, you don't buy a Car but instead buy a Camry. However, given that you can't instantiate the Base Car in any reasonable scenario, I guess it's a good question to ask why that can't be an interface. The only answer I can think of right now would be confined to a particular language. You might need a particular bit of functionality that's provided by an abstract class rather than an interface. Common ancestor methods, for example.
I'm thinking that's about as far as it should go. If you end up customizing behavior then composition and interfaces is probably better. Gang of Four and others generally agree composition is better overall. Makes me think that inheritance is only good for the cases where it's ok for the classes the have the tight coupling. Which to me means extremely related small clusters of classes.
ReplyDeleteYa, depending on how much behavior is being customized, it could become very easy to break Liskov Substitution with inheritance, at least at some level. Interfaces seem to provide a more intuitive means of reminding the developer of that particular principle.
ReplyDeleteHonestly, the most relevant examples that currently come to mind where the tight coupling is appropriate (and even expected) are for non-real objects in code, where classes and OO are used for the purpose of inheritance as a convenience. Things such as a Base Page or Base Handler in a website/service, or any abstract strategy class whose children are meant to be applied to some process.
Sounds like you are talking about framework extension points and the template method pattern. I'm sure both of those can be solved with strategy or behavior interfaces which allow less polution. Hell in skme cases just a Func<> or Action<> is all you need. But I think we are coming to a conclusion, inheritance is really for convienence with tight coupling being the trade off. At least that is what I'm getting out of this.
ReplyDeleteInterfaces give you the advantage of defining the roles/abilities in little pieces as needed and give you polymorphism. Composition gives you the behavior reuse with looser coupling since you an define those with interfaces, or abstract them out another way, and inject them in.