Changing an interface

  • Thread starter Thread starter wdudek
  • Start date Start date
W

wdudek

I've seen 2 differnt approaches people take to interfaces and am just curious
on some other opinions. For purposes of this post I am talking about adding a
new method to an existing interface. The first is that an interface can
never change, once it's "published" it is fixed in stone and if a change
needs to be made you should create a new interface that possibly inherits
from the old one. The second is that it is ok as long as the change does not
break the existing contracts, which wouldn't happen if a new method was
added, since they were not using it before they wouldn't need to be aware of
it now.

thoughts?
 
I've seen 2 differnt approaches people take to interfaces and am just curious
on some other opinions. For purposes of this post I am talking about adding a
new method to an existing interface.  The first is that an interface can
never change, once it's "published" it is fixed in stone and if a change
needs to be made you should create a new interface that possibly inherits
from the old one. The second is that it is ok as long as the change does not
break the existing contracts, which wouldn't happen if a new method was
added, since they were not using it before they wouldn't need to be awareof
it now.

The second approach is incorrect since the interface can be
implemented as well as used; in fact, it is the whole point of having
an interface! If you change the interface, all existing
implementations would immediately break. Just imagine what would
happen if Microsoft added, say, a new method to IEnumerable tomorrow.

If you do not want to allow users to implement your interfaces, then
interfaces are the wrong tool for the job. Use abstract classes with
internal constructors. Those can only be used but not derived from by
code outside of your assembly (and friend assemblies, as needed), so
there you can safely add new methods. Well, almost - keeping in mind
that adding a new overload can break existing code; if you had a
method Foo(double), and in your new version added an overload
Foo(decimal), any code that called it passing an integer - e.g.
Foo(123) - would now stop compiling because of ambiguity.
 
I understand Pavel's comments about if Microsoft added a method to
IEnumerable, and definately see how that would cause problems. From Peter's
perspective I guess I need to provide more background information on what we
are changing and why. Probably the most important piece to this is that we
are writing this strictly in house, not that this should allow us to bend the
rules and make changes we shouldn't. Secondly what I started off doing that
caused me to post this was add functionality to an existing wcf service. The
service currently has a method that returns a list of products a user can
see. The new method did the same thing but also limited the list by an
application name provided by the calling client. There are numerous
applications using the old code so they need to see the interface with the
current method. Meanwhile new applications need to see both methods. I didn't
want to create a second service because the functionality is very integrated
and people calling this wouldn't expect to look elsewhere for it. After doing
some testing I found that I could add the new method and the existing code
would work fine while the new code would get the additional method. I didn't
seem to have much choice here. My dilema arose when I got to the library the
service calls and how to handle this there. I originally added a new
interface but then decided that since there wasn't a new interface in the wcf
service that this was confusing. This led me to go back and add the method to
the existing interface. I guess the one caveat about this all being in house
code is that I know that the only code implementing the interface that was
added to is the class inside the library containing the interface. All other
references to the interface are to the return valeu of my factory class. Does
this change anyone's opinion?

Peter Duniho said:
The second approach is incorrect since the interface can be
implemented as well as used; in fact, it is the whole point of having
an interface! [...]

While I agree with pretty much everything else you wrote, I'll point out
that allowing the client to implement the interface is not "the WHOLE
point of having an interface".

There are examples of interfaces that are read-only as far as the client
is concerned. In those situations, it shouldn't be a breaking change to
add a member to the interface. For example, using an interface to control
accessibility (C# doesn't provide as fine-grained accessibility control as
some other languages, and so an interface might be used to keep an entire
class private except to some specific other class while allowing _some_ of
the members of the class to be public to the rest of the world).

Which is not the same as saying that doing so should be considered
lightly. Just that there are in fact scenarios in which changing the
interface after the fact wouldn't be disastrous.

Pete
 
While I agree with pretty much everything else you wrote, I'll point out  
that allowing the client to implement the interface is not "the WHOLE  
point of having an interface".

I disagree, but do read on.
There are examples of interfaces that are read-only as far as the client  
is concerned.  In those situations, it shouldn't be a breaking change to  
add a member to the interface.  For example, using an interface to control  
accessibility (C# doesn't provide as fine-grained accessibility control as  
some other languages, and so an interface might be used to keep an entire 
class private except to some specific other class while allowing _some_ of  
the members of the class to be public to the rest of the world).

That was precisely why I went on to explain how abstract classes can
be used for the same purpose. The reason being, you can't force the
client to avoid implementing an interface and passing it to your
methods which take arguments of corresponding types; and with abstract
classes, you have that option, and for the task you described (hiding
implementation classes, exposing only a limited surface), they work
just as well. Therefore, it would seem that using abstract classes for
that purpose is more appropriate.
 
They really need a preview pane in here to prevent those run on paragraphs :)

Thanks though, in this case I did modify the existing interface, because
adding the second did seem a bit clunky to me but your argument for not doing
so does make sense and it's likely to be the direction I take going forward.

Bill

Peter Duniho said:
[...] Does this change anyone's opinion?

Not mine. And I think you could use an occasional paragraph break. :)

Basically, I think that there are times when you can get away with simply
modifying an existing interface. But I agree with Pavel in the sense that
I think one should consider those situations carefully.

The usual way to deal with versioning issues like this is actually to
create a second interface, either as a new version of the old interface
(so includes both methods) or as a whole new interface (including only the
new method). You can use casting or "is" and "as" (in C#) to convert a
reference to the old interface to one of the new interface.

I'm not aware of a better general-purpose versioning mechanism in .NET. I
wish there were one, but I'm not sure what it would look like :). If you
have a really good, compelling reason to avoid using the standard approach
and just modify the existing interface, there are (as I said) situations
in which that could be justified. But otherwise, I think it's better to
stick with the "tried and true", clunky though it might be. :)

Pete
 
I don't really understand your comment.  You recommended an abstract class  
with an internal constructor, but that assumes (to start with) you are  
satisfied with "internal" as an appropriate level of accessibility for the  
purpose of hiding the members from other classes.

Yes. I am treating this from a reusable library design perspective
here. Of course, it would be preferrable to have even more fine-
grained control than just "internal", but we have what we have
with .NET.

When considering interfaces that are only used within an assembly and
not outside it (or within a bunch of coupled assemblies which expose
public members just so that they can interoperate, and not intended to
be called from outside that group), the things are obviously different
- if only because you can immediately fix all the classes broken by a
refactored interface.
 
Back
Top