1

Let's say I have an interface such as

Public Interface CarManager {
    Car getCar(String model);
}

And I have a lot of legacy classes that implement this interface.

But now I have a new requirement where I still need to implement this interface, yet the getCar method would now require a new signature such as

Public Class fancyNewImpl implements CarManager {
    Car getCar(Map<String, Int> modelAndYear) {
    // Do something, return something
    }
}

What's the best way to go about this problem, without having to change the method signature of the original interface and all of its implementation classes?

I've read a bit on Interface Adapters but I still am not convinced that this would satisfy this particular use case.

2

Option 1 - just add new method overload (meaning: a method with same name, but with different signature and technically never considered the same method from the language perspective) to the class that implements the interface

This is the answer suggested by Lars.

Pros: Least effort.

Cons: Only callers which knew the name of your new concrete class fancyNewImpl could perform the downcasting. Furthermore, if the new method signature happens to be also implemented another concrete class, such as fancyNewImplTwo, callers which attempt to downcast to the first concrete class would fail; a caller would have to try both (or, all relevant) concrete classes in order to find the method overload that can use.

Option 2 - Define an interface that inherits from the original one, and has the new method overload.

https://docs.oracle.com/javase/tutorial/java/IandI/nogrow.html

public interface FancyCarManager extends CarManager
{
    Car getCar(Map<String, Int> modelAndYear);
}

Then, implement the two methods in the concrete class that implements the FancyCarManager interface.

Callers will still have to do an upcast (technically an interface check) to FancyCarManager and call the new method. But, this option allows multiple concrete classes, such as FancyNewImpl and FancyNewImplTwo, to be callable through the same new method signature, through the new interface.

Option 3 - Call the method anyway, without interface, via Java Reflection

Basically this answer deserves to be downvoted just because I mentioned this. But in actual programming workplace, you should see a lot of this happening.

(Opinions are mine and are not related to my employer and not inspired from my work. This opinion can be seen in programming blogs elsewhere and articles on the internet.)

Option 4 - Abuse the string parameter by encoding things into it

For example, you can concatenate the year into the model string as

Car car = carManagerInstance.getCar(model + ";" + year);

You can add any number of parentheses (as string characters), other punctuations, Unicode characters, or even non-printable characters. You can see where this is going: shovel an XML fragment or a JSON into a string.

This will likely choke any existing code that expects the string to be formatted in the old way.

However, in legacy code bases where the old meets the new (in a bloody conflicting way), this will happen a lot. This requires a very high testing (quality assurance) effort, and will always have a higher defect rate no matter how much effort is put in.

Reminder

Just because something is mentioned in this answer does not mean it is proper to use it. Ask your more experienced coworkers for opinions. This is especially valuable if your coworker knows the code base you're working on and your company's coding style.

  • Thanks for the thoughtful answer. Me and some coworkers thought about the problem and came up with similar solutions. I think the last one is possible but not something that should be done because callers of the concrete class would have to know to pass in a json, but many times implementations of interfaces are returned via factories so it wouldn't be that straightforward to pass in a Json vs a normal string. But I think the second suggestion is probably the best way to go about this. – SnG Feb 17 '17 at 5:58
  • @SnG: Indeed. Java allows you to check an object's interfaces via instanceof (which, removes the abstraction, since it allows you to eventually find out which concrete class it is), so that you can determine whether it is a new concrete class that will accept a JSON. Without checking first, passing a JSON to the old implementation will blow up, and option #4 does not provide a way to check whether the object supports it. – rwong Feb 17 '17 at 6:05
0

A nice way to deal with this is to make use of polymorphism

Using this you simply add a 2nd method with the same name but different arguments, so you change the signature. This allows the existing consumers to keep working with the old version and allows new consumers to use the newer version of your method.

This is one of the foundations of OO design.

  • 1
    I think you mean overloading, not polymorphism. – Andy Feb 17 '17 at 1:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.