Sunday, March 25, 2012

Updating an Interface Implementation

A little while back, we saw how we could implement interfaces explicitly in Visual Studio (Explicit Interface Implementation).  We saw how Visual Studio helps us implement an interface.  Consider the following interface:

After creating a class (called "Implementation") that declares that it implements this interface, put the cursor in "ISaveable".  Then we see that we get the clue (with the small blue box under the "I") that Visual Studio wants to help us.  If we press Control and the dot (Ctrl+.), then we get a popup to help us.

By selecting "Implement interface 'ISaveable'", we get the interface stubbed out.  Note that we can get similar behavior by right-clicking on "ISaveable" and selecting "Implement Interface" and then "Implement Interface" again.  Also, if you run a refactoring tool (such as ReSharper), you will be given similar options through that tool.

Now that the interface is implemented (i.e., the contract is fulfilled), Visual Studio behaves a little differently. If we right-click now, and select "Implement Interface", we will only see the option to explicitly implement the interface.  Because the non-explicit interface is already implemented, Visual Studio does not even give us that option.

But What if the Interface Changes?
But what if there are changes to the interface?  Let's say that another method was added to the interface:

Now if we go back to our "Implementation" class, we will see that the option to "Implement Interface" is back (for both the right-click and "Ctrl+." options).  If we select this option, then it will add any interface members that aren't already implemented.  In this case, it will add the Save method that takes the object parameter.

Don't Update an Interface in the Wild
An interface is a contract.  If you were to update an interface (by adding members or changing existing members), then existing classes that implement that interface will break.  Those classes will no longer build.  Because of this, we should only consider changing an interface if we have not yet released it.  After we have released it, we should assume that there are classes that implement the interface and that those classes will break.

If we're still in the development cycle, then we will most likely go through many different iterations of the interface.  That's to be expected.  But we need to be confident that the interface is complete when we release it for use by other developers.

Another Option - Combining Interfaces
But we have another option.  Rather than changing the existing interface, we can create a new interface that inherits the original members.  In our sample interface, it would look something like this:

Now we have a new interface "IObjSaveable" that includes all of the members of "ISaveable", plus it's own members.  This means that any existing classes that implement ISaveable will continue to work as expected. For classes that need the extended functionality, they can implement IObjSaveable.  If we choose to "Implement Interface" on IObjSaveable, we get both save methods:

Contracts are Contracts
Interfaces are contracts.  To keep up our side of the contract (as the interface designer), we need to make sure that we are not changing the interface once we've released it for use.  But as we've seen, we can create new interfaces that include the members of other interfaces.  If you look at the Visual Studio help files, you will see that IList, ICollection, and IEnumerable do just that (ICollection includes IEnumerable and IList includes ICollection).  We can follow this same pattern to make sure that our code is well abstracted and extensible.

Happy Coding!

No comments:

Post a Comment