So I made a mistake.
When originally writing a signature for an API, I created something like this:
public JellyBeanResult getJellyBeanReport();
Now, it turns out that I would like to re-use the more specific JellyBeanResult object because of its functionality, but it would be confusing to have other functions return a type named for a different process. There are a couple of ways to fix this that I can think of. I could re-name the return type to something more generic:
public GenericResult getJellyBeanReport();
public GenericResult getChocolateBarReport();
but that would break any code that is using the API. I could create a new, more accuratly named class that extends SpecificResult that more closely fits the new func开发者_如何转开发tion:
public class ChocolateBarResult extends JellyBeanResult{};
public JellyBeanResult getJellyBeanReport();
public ChocolateBarResult getChocolateBarReport();
But this is really, really ugly and the problem still sticks around if I want to to use the return type again down the road. How can I clean up these signatures to make them less confusing without breaking any code that is using them?
Move the core functionality from JellyBeanResult to GenericResult and have JellyBeanResult extend GenericResult:
public class JellyBeanResult extends GenericResult {}
public JellyBeanResult getJellyBeanReport();
public GenericResult getChocolateBarReport();
or if you want to be completely consistent:
public class JellyBeanResult extends GenericResult {}
public class ChocolateBarResult extends GenericResult {}
public JellyBeanResult getJellyBeanReport();
public ChocolateBarResult getChocolateBarReport();
The specific of any 'true' API is that it cannot ever be changed. You cannot simply change/remove existing methods. You only can add new functionality.
The only way I see is you should create correct set of methods (like in your example with GenericReport
) and mark old methods with @Deprecated
annotation.
You're right, having ChocolateBarResult
extend JellyBeanResult
would be bad, because JellyBeanResult
likely has methods and fields (such as "color" for a jelly bean) that don't make sense for a chocolate bar. So, don't do this. :-)
What about creating new methods to return the correct result type (GenericResult
) and then marking the narrow getJellyBeanReport()
method as @Deprecated
to discourage anyone new from using it?
I'm not sure exactly what you're going for, but maybe something like this?
public JellyBeanResult getJellyBeanReport() {
return getJellyBeanReport(JellyBeanResult.class);
}
public <T extends JellBeanResult> getJellyBeanReport(Class<T> resultType) {
// get the correct report type
}
Possibilities:
- If you just want to reuse the functionality you can have all of the other classes like ChocolateBarResult use JellyBeanResult instead of extend it. Remember composition is often better than inheritance.
- Keep a release of the current version of the API so that existing users are ok. Create a new version that will have your changes, so that if users want new features they need to upgrade their codebase. If you do this create some kind of change guide and possibly deprecate the method for a release cycle or two to allow for the change over.
- Plan APIs better. Dogfood your code. Try not to release it as an API until you have a good(varied) set of users.
Assuming that you haven't released the current version of the API, could you refactor to use a generic interface and covariant return types?
public interface ConfectionaryResult {...}
public class ChocolateBarResult implements ConfectionaryResult {...}
public class JellyBeanResult implements ConfectionaryResult {...}
public interface ConfectionaryInventory {
ConfectionaryResult getReport();
}
public class JellyBeanInventory implements ConfectionaryInventory {
JellyBeanResult getReport() {...}
@deprecated "Use JellyBeanInventory.getReport() instead"
JellyBeanResult getJellyBeanReport() {
return getReport();
}
}
public class ChocolateBarInventory implements ConfectionaryInventory {
ChocolateBarResult getReport() {...}
}
I'm assuming that the original methods lived inside an inventory class - which may not be the case.
If you're going to refactor anyway, it makes sense to think about making JellyBeanResult an interface that extends your desired GenericResult interface. (It's essentially a marker interface to preserve your previous return type.) Your existing JellyBeanResult class becomes your first iteration GenericResultImpl class.
Once you do this then you can make a decision on whether to deprecate the existing method and how to replace it with a lot less risk.
精彩评论