Let's say that we have the following method in class TaxCalculator:
public double calculateTax(double income) {
return income * 0.3;
}
And we use this method in the Main class like this:
var calculator = new TaxCalculator();
double tax = calculator.calculateTax(100_000);
System.out.println(tax);
If I change the implementation of the calculateTax
method to:
public double calculateTax(double income) {
return income * 0.4;
}
Do I need to recompile both the TaxCalculator class and the Main class?
I know this question sounds very silly, but I heard in a lecture that if we don't use interfaces, every change we make in tightly-coupled code (like the one I showed above) will force us to recompile all the classes that depends on the class we made the change.
And this sounds weird to me, because the Main class doesn't know the implementation of the method we've made the change.
Thanks in advance for any help!
CodePudding user response:
Yeah, that lecturer was just dead wrong. More generally, this is a very outdated notion that used to be a somewhat common refrain, and various lecturers still espouse it:
The idea that, if you have a class, you make a tandem interface that contains every (public) method in that class; given that the class already occupies the name of the concept, the interface can't be given a good name, and thus, an I is prefixed. You have a class Student
and a matching interface IStudent
.
Don't do that. It's a ton of typing, even if you use tools to auto-generate it, everytime you change one you are then forced to change the other, and there is no point to it.
There are exotic and mostly irrelevant ways in which you get a 'more tight coupling' between a user of the Student
class and the Student
class code itself vs. having that user use IStudent
instead. It sounds like either you or more likely perhaps the lecturer is confused and presumed that this tight coupling implies that any change in Student.java
would thus require a recompile.
Furthermore, if those examples are from the lecture, oh boy. double
is absolutely not at all acceptable for financial anything. That should most likely be an int
or long
, representing cents (or whatever passes for 'atomic monetary unit' for the currency in question; pennies for pounds, satoshis for bitcoin, yen for yen, and so on). In rare cases, BigDecimal
. In any case, not, ever, double
or float
.
You need to recompile B, where B uses something from A, if:
You change the value of a constant in A that is used directly used by B, and that constant was 'CTC' (Compile Time Constant). Only the primitives and strings can be CTC, and they are CTC if the field is
static final
, and is immediately initialized (vs. initialized in a separatestatic {}
block), and whose expression is itself CTC, which means its comprised of literals and possibly simple operations between CTCs, e.g. instatic final int a = 5; static final int b = a 10;
,b
is also CTC. In contrast, e.g.static final long c = System.currentTimeMillis();
is not a compile time constant becauseSystem.currentTimeMillis()
isn't, for obvious reasons.You change a signature of any element in A that B uses. Even if the caller (B.java here) can be recompiled with zero changes. For example, you have in
A.java
:void foo(String param)
and you change thatvoid foo(Object param)
. Even thoughfoo("hello")
is a valid call to either method, you still need to recompile here. Relevant elements are the name of the method, the types of the parameters (not the names), and the return type. Changing the exceptions you throw is fine. Deleting something in A that B used is, naturally, also something that'd require a recompile.
And that's essentially it. The interjection of an interface doesn't meaningfully change this list - if that constant is in the interface, the same principle applies (if you change it, you have to recompile users of this constant), and if you change signatures, you'd have to change them in the interface as well, and we're back where we started.
Adding an interface does have some generally irrelevant bonuses.
As a caveat, any such attempt must always answer the rather poignant question of: But how do callers make an instance? If the lecturer uses
IStudent student = new Student();
they messed that up, and the few mostly irrelevant benefits of using an interface are gone.If there are meaningfully different implementations available (quick rule of thumb: If you can come up with good news for all relevant types, this is the case), using an interface is 'correct' and none of this applies. For example,
java.util.List
is the interface,java.util.LinkedList
andjava.util.ArrayList
are meaningfully different implementations of the same idea.It's slightly easier to make an implementation of the interface specifically for test purposes. However, mocks and extending the class are generalized solutions to this problem too and usually work just as well, and more generally making a test-specific impl requires more care than just a rote application of the 'make a mirroring interface' principle.
You get an extra level of access - you can have
public
things in the class that nevertheless aren't mirrored in the interface, and thus, are not 'accessible' via the interface. There is a single good reason to make thingspublic
when they aren't really meant for external consumption: When you have a multi-package system. Java's module system acknowledges this too, and (via the 'exported package' concept) also introduces, effectively, another access level (apublic
thing in a non-exported package is not accessible from other modules, it's not as public as apublic
thing in an exported package). This is outdated, and there are ways around it even in a multi-package library, and it doesn't actually stop much - you cannot enforce callers to 'code to the interface'1
- Well, you can, but those are a bit clunky, and those would also stop another package in the same project, which was the whole point. You can use hacks to get around this, but if you're willing to use these hacks, you can just make those
public
, but not actually meant for external consumption non-public and use the same hackery.