Given that interfaces are meant to be fixed contracts that guarantee some behaviour, and thus should not change, how do you handle cases where they need to change?
Let's say I have an interface, IDataSource, with a Prepare method.
interface IDataSource:
method public void Prepare(<args>).
end interface.
I now realise that the behaviour represented by Prepare needs to change; I need to add a return value for a ReuqestContext type.
interface IDataSource:
method public RequestContext Prepare(<args>).
end interface.
Assuming there are consumers of this interface that are out of my control, this breaks things for someone. I see myself having a couple of options:
1. Create a new interface IDataSource2 and add a Prepare method that returns the RequestContext. The implementing class now needs to implement both interfaces.
interface IDataSource2:method public RequestContext Prepare(<args>).
end interface.
2. Add a new "PrepareRequest" method that does what I want, and leave the existing "Prepare" method alone.
interface IDataSource:
method public void Prepare(<args>).
method public RequestContext PrepareRequest(<args>).
end interface.
3. Add a new "Prepare" method that returns the RequestContext object as an output parameter instead of a return value.
interface IDataSource:
method public void Prepare(<args>).
method public void Prepare(<args>, output RequestContext).
end interface.
4. Modify the existing Prepare method to return the RequestContext and release note the change
Each of these has their own downside.
There's some discussion at http://stackoverflow.com/questions/45123/interfaces-and-versioning about this, and the highest-ranked answer basically says, "it's hard, and don't change your interfaces". There's also a comment along the lines of "classes are better than interfaces for abstraction".
Any advice, thoughts, comments?
thanks,
-- peter
how about
5) use interface inheritance
/** interface1 */
using Progress.Lang.*.
interface temp.interface1:
method void foo(p as char).
method void bar().
end interface.
/** interface2 */
using Progress.Lang.*.
interface temp.interface2 inherits temp.interface1:
method char foo(p as char).
end interface.
Could certainly do that.
I may have a naming problem though, and certainly anyone who's implementing the interface or who - more likely - is referring to a (in this case) DataSource via a variable defined as IDataSource cannot use the new functionality without changes.
Unless i make the old inherit from the new interface, but that needs more thought.
-- peter
The part I wonder about is the "consumers out of control" part. If this is part of some published framework and the interface is something applications using that framework will have utilized, then clearly you need to be cautious about making any change. Indeed, the only change you can make without being disruptive is to define a new, modified interface and point people to it as the one they should be using in the future, but leave the other one in place for some reasonable time (possibly forever) so that people can transition. *Any* change to the interface itself is going to break people's code.
But, if it is not something like this, but is internal code, then change it the *right* way, whatever that is in the circumstance, and change the impacted code to correspond. You have to do that to have the change have impact anyway.
It is also possible that someone using the interface does not /want/ the new functionality.
Or maybe they are busy working on somethign else now and do wish to take advantage of the new functionality. Not today but when they are ready.
Before an API is published and put to use, you better have it right. But we all make errors so change is inevitable. Minimizing the impact is especially important for APIs.
If this is going into a framework, then it would be a good idea to consult a book on framework patterns.
"Implementation Patterns" has a good section discussing the pros and cons of different approaches to building and evolving a framework:
unless something changed in latest versions the method return type doesn't take part in method signature so you will most likely have that naming conflict... can't help to wonder what do you want to return from a method that considered void in the first place?
alternatively you can add a method to return the 'prepared' context of the old void method, but this will be odd for the new usage since there will be two method calls
that naming conflict... can't help to wonder what do you want to
return from a method that considered void in the first place?
I found myself needing more and more stuff related to the request, so I refactored it into a RequestContext type. That by itself isn't the issue though: I had previously stored the context as "CurrentTableRequest" and "CurrentDataAccessObject" (say) properties, which were set by Prepare, and cleared after the request's completion. Basically, incomplete/poor design up front, and now I'm paying for it. In this case, I could simply replace the Current* properties with one CurrentRequestContext property, but I don't like how that smells.
I hadn't meant for the specifics of the change to be important: my question was more related to the fact that designs aren't perfect first (or 2nd or 3rd ) time around, and was looking for strategies and techniques to mitigate downstream damage.
-- peter
The part I wonder about is the "consumers out of control" part. If this is part of some published framework and the interface is something applications using that framework will have utilized, then clearly you need to be cautious about making any change. Indeed, the only change you can make without being disruptive is to define a new, modified interface and point people to it as the one they should be using in the future, but leave the other one in place for some reasonable time (possibly forever) so that people can transition. *Any* change to the interface itself is going to break people's code.
This problem certainly applies to frameworks, but I think it's a far more generalised issue than that. Any 3rd-party code your application references may have this issue. Any published code which has extension or customisation points too.
-- peter
OK, so I am using "framework" to include any code which one publishes for use by third parties. That, one has to change cautiously because one is going to upset people. Then, I think there is no choice but to think hard and decide whether it is better to add something entirely new which won't disturb existing code or whether it is the right thing to upset people anyway.
For internal code I think it is less of an issue.
pjudge wrote:
I found myself needing more and more stuff related to the request, so I refactored it into a RequestContext type. That by itself isn't the issue though: I had previously stored the context as "CurrentTableRequest" and "CurrentDataAccessObject" (say) properties, which were set by Prepare, and cleared after the request's completion. Basically, incomplete/poor design up front, and now I'm paying for it. In this case, I could simply replace the Current* properties with one CurrentRequestContext property, but I don't like how that smells.
well, incomplete design is better than no design... we can never do something perfect anyway and the design phase should end at some point so the implementation can start
I would say there are two different cases here... one when the interface is meant as a contract you sign, aka external world can trust you'll stick to it, and when it's a contract you require from others - the external world must implement it if they want to talk to a particular part of your system.
I was chatting to a friend on Friday about this topic, and he brought up a similarly-interesting distinction between interfaces and extension points. The former being more internal to the application, and the latter being important for 3rd parties/consumers of the appplication. He also reinforced what most of you have said about being careful about managing changes in public APIs.
What can be useful here is an equivalent of 'deprecated' annotation... this could give hints to developer not to use methods that were replaced by something else while the old code will still work.
I trypically add an annotation to this effect - @deprecated - which has no effect on the compiler (as in Java, C# etc). But at least there's some indication for the developer.
-- peter
It may be a big issue for internal code too, depending on your definition of internal. For example, we used various components in our prodoucts. Some of these were built by other groups at PSC and some by external companies. All too often, it turns out to be a big job to update them with newer versions even for things made by PSC. Preserving backward compatibility is hard work.