8/29/07

Interfaces and abstract classes in .NET are pretty similar in functionality - you can't instantiate them, but still objects of various runtime types can hide both behind an interface or behind an abstract class. So when to use which?

The "Framework Design Guidelines" book by Brad Abrams and Krzysztof Cwalina is a very good book for all those who like asking such questions. Particularly, there is a whole section 4.3 "Choosing between class and interface" discussing this issue. I'll spoil the plot ending and post the main advice right away:

Do favor defining classes over interfaces.

The main argument of the authors is flexibility of classes. This applies mostly to layered architectures, where a client layer uses the framework layer. Classes can provide default implementation, so even if you already have derived classes in your client layer and you're adding a new virtual method to your base class in the framework, the clients won't even have to recompile their derived classes - the new virtual method magically appears in the derived types automatically and you don't have to change anything in the client. On the contrary, if you add a new method to an interface, which is already being implemented by some clients, you can't provide the default implementation - it would introduce a breaking change (all clients would have to manually add methods to implement the new method of the interface).

This is a very good point - once you ship an interface you basically can't change it anymore - it's carved in stone starting from the very moment anyone on the client side implements it. Unless you want to break that client and make them recompile or even worse - re-deploy.

When I was developing an editor for C# it was based on a framework for editors. During design of the framework and the editor, I learned a lot about how to use interfaces (more precisely, how not to use them). Now I want to share my experiences with you to add a couple of measly cents to the dollars of wisdom of .NET architects.

My whole editor framework is built around a concept of a 'block' - a rectangular thing which can be displayed on the screen and contain text and other blocks. The framework is basically a hierarchy of classes that inherit from the Block class. If you're familiar with the Composite design pattern and the text editor example from the GoF book, you'll immediately recognize that blocks are simply Glyphs - basic elements from which documents are composed. Now, I had an interface IBlockand an abstract class Block, which implemented IBlock. As it turned out many lines of code later, this was the most stupid thing to do:

Anytime I wanted to add new members to the class hierarchy, I had to update both the base class and the interface

Once I'd ship the IBlock interface and a client would implement it, I wouldn't be able to add anything to it anymore

The only situation where IBlock would be useful is when some client would want to implement it without inheriting from Block - and who on earth would ever want that?

I removed the IBlock interface and changed all its usages to use the abstract class Block instead. This simplified my life so much.

What I've learned from this: interfaces are good when you want to work with some distinguished feature or ability of an entity, and not the whole entity. For example, if you want to enumerate something, it is sufficient for you to receive an IEnumerable - you don't care about the rest of the entity. It can be anything, of any complexity - for you is only important that it allows to enumerate itself. A good occasion to use an interface is having many base classes - you don't have to inherit from Collection, you have various base classes and the only thing you have to do is to implement IEnumerable. As I said, interfaces describe part of the functionality, and you can mix-in this functionality to existing class hierarchies without adding a new base class.

Classes, on the opposite, are good for modeling whole entities, entire complete entities as a whole. That was my mistake - I used an interface for the whole entity "Block", where I should have used a class instead. With classes, you can't mix in a new base class to an existing one - that would mean merging two clearly separate stand-alone entities into some weird-looking hybrid. That is one of the reason multiple inheritance is not allowed in .NET - you shouldn't mix entities together, it's not a good practice. On the opposite, you should do your best to separate your entities from one another. It's like normalization of relational databases - you split your tables until each table describes a clearly defined entity with exact boundaries and strictly defined connections and relationships to other entities.

A common design mistake is called "burning the base class" - it's when you make your whole hierarchy inherit from some base class just to add some functionality to the entire class hierarchy in one simple step. As a rule, this functionality doesn't represent a separate, stand-alone entity, but still you waste your base class to add partial functionality. As I said before, interfaces are best when you want to add part of a functionality - you can mix it in to an existing hierarchy without burning the base class. An example of burning the base class is a System.MarshalByRefObject from the .NET base class library. Clearly, we waste a base class of all Windows Forms controls just to add some ability to all the classes at once. Adding an ability is done best by interfaces. However, this way requires more typing because an interface can't provide a default implementation.

To reiterate: use classes to model whole entities, and use interfaces to add abilities to existing entities. The only exception to this rule I can currently think of - is when you don't want your entities to be extended in the future. That is, you'd like to specify a precise contract for an entity, and you can guarantee that this entity won't have additional abilities in the future - at least not in your scope. Then, it's OK to use an interface to capture the entity and carve it in stone. Clients are free to add functionality to this entity, but you don't want to see it in your framework. You'll only work with this strictly defined entity and it's not going to change.

Finally, I've discovered a rule of thumb for myself which generally helps me to use interfaces correctly. An interface should define at most one member. Yes, that easy. One could even have an FxCop rule for it. If you want to define an ability on some entity, one member is mostly enough: GetEnumerator() for an enumerable, Clear() for a clearable, Count for countable and so on and so forth. If you want a more complex ability or a composed entity, use interface inheritance:

declare no members and inherit from no interfaces ("a marker interface"), or

declare a single member, or

inherit from two other interfaces.

This would be some minimal "interface normal form" à la Chomsky, to which any interface hierachy could be reduced. Unfortunately, this mathematically beautiful "normal form" wouldn't be practical for everyday development.

16 comments:

Anonymous
said...

Marker interfaces in .NET aren't very useful, actually, due to attributes, which is a much nicer way of marking something since they are class hierarchy independent and can have functionality attached to them.

I'd actually argue that this article takes the wrong approach at deciding when to choose an interface versus an abstract class because it assumes that a default implementation can be assumed for any new functionality. For me, the question to determine which to use is simple. Do I have a default implementation that I would like to apply to any part of the contract? If the answer to that question is "yes", I have little choice to use anything other than an abstract class. Otherwise, I will always choose an interface. The reason is that classes can extend any number of interfaces, whereas a class can extend only a single abstract class.

The only situation in which new functionality can be added to an abstract class without breaking client code is when a default implementation can be designed. The idea of interfaces and abstract classes is to ensure that clients implement their classes to the specifications of the contract. That said, the only way to keep from breaking client code by adding new functionality to an abstract class would be to provide some level of implementation to the new functionality. If no default implementation can be assumed, a stub would have to be created, which could lead to undesirable and confusing affects. For example, during maintenance, a developer might mistakingly assume that the functionality has already developed in the base class, when in reality, it was not. In this situation, I would argue that it's better to break the client code to make it clear that further implementation is required.

1 - Sometimes u don't have a choice. For example, if u programmed anything in WCF..its pretty much interface based (setting up contract etc.). Web Part framework (IWebPart) or its communication methodology is based on interfaces. To me it would make sense to extend the interfaces fruther when providing custom implementation.

2- This gets muddied further with the new functional programming enhancements in c# 3.5 (i.e extension methods).

If ur an OO purist, I wonder what the options are..not use C# functional programming features?

Thats a great thing about an extension method...that u don't need it defined in a Interface/Abstract class. Or object initializers..now u don't need to follow proper constructor design to allow for multiple default initial states.

Interfaces are there to define a contract for a specific piece of functionality. c# only allows us to inherit from one class but implement multiple interfaces e.g. we might inherit from Block but implement IComparable and IEnumerable. Perhaps you are trying to do too much within the IBlock and it should be broken down further.

senfo: your points about default implementation are very valid. Each developer has own approaches to software design. I shared mine. Yours are interesting just as well.

anon: I'm not an OO purist :) And I love extension methods and I bravely embrace new stuff like WCF. But when I think about interface-only design, I somehow think about COM and then I remember how COM failed... I mean, it's OK, but you have to be very careful with it and not mess things up.

ibloke: I removed IBlock because this was an unnecessary, redundant entity. I could use Block wherever I used IBlock, and it is clearly the only default implementation (like senfo said). So I'm currently happy with my solution.

secretGeek: Leon, you're as always relevant and share amazingly interesting information. This was an eye-opener for me indeed. I like the idea of mixins - e.g. have you seen this: Traits

The first 80% of your post are certainly a good point and also good advice on how to choose between interface and abstract class; especially the "is it a feature or the entity as a whole" question. I never felt comfortable with interface plus default implementation (such as IComponent and Component). In my opinion the intention never worked out, rather it spoiled the picture. It's more like avoiding the decision than a concious one. There are other use cases that call for interfaces or abstract base classes, so be aware that the feature or entity question is not the only relevent criterion, though.

With the "An interface should define at most one member" and the following statement however I think you are on the wrong road. An interface does not only anounce a feature (to stay with your use case) in the sense of a marker interface. It provides the contract to work with that feature. If that contract takes 3 methods, it takes 3 methods. It does not extend 3 other "contracts" that in fact do not form a (mutual) contract because each method for itself does not make much sense. If you reshaped that statement to "An interface should define at most one contract" I would applaude. In fact I think that is what the exmaples you mentioned (enumerable, clear, count) actually did. Whether something can be enumerated, cleared, or counted are three orthogonal aspects (a collection or cursor might or might not support any of them). The fact that they all contain just one method however is pure conincidence. IEnumerator (3 members) is minimalistic but would not fit in your scheme.

In my opinion the usage of interfaces are all about polymorphism. To just add new functionality I would not use interfaces, just start coding it inside the class. Is it a shared functionality? Then I have two common choices: use inheritance or much more better than that use another instance of a class. I mean "uses, composition or aggregation" relationship in UML.

I think your post lacks this usage. If the new functionality can be grouped in a meaningful way why not create a class for it than use it in your subject class. Either just use from a method or make it a member (whole - part relationship) Just remember gof saying "favor composition over inheritance" which mostly works (beware! not all times)

Well I guess it does. But if you're writing a chess application, I don't see much value in the IGame interface. You're artificially limiting yourself. A class here would do just fine (unless you're writing a distributed client-server game-provider app, where games are separated from game providers). If you're writing one game, and everything is about the game, why hide it from yourself beneath an interface?

If a class is a type of another class, I use abstract class because abstract class support inheritance. Example: PartTimeStaff and FullTimeStaff is a type of Staff.

If a class is not a type of another class but they have common function, I use inheritance. Example: FullTimeStaff and Customer should not come from the same base class but they may have common function, e.g. discountRate; in this case, interface is more appropriate.

Disclaimer

The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion. All code samples are provided "AS IS" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.