
Question:
I have several Java interfaces/ABCs/classes:
public abstract class Target {
public abstract void fire(Load load);
}
public class HttpTarget extends Target {
@Override
public void fire(Load load) {
// ...
}
}
public interface Load {
// ...
}
public class HttpLoad implements Load {
// ...
}
// Inside a driver
Target target = testSuite.getTarget();
Load load = testSuite.getLoad();
target.fire(load);
So essentially a Target
can fire()
a Load
. My main app Driver
doesn't care about what kind of Target
is returned by getTarget()
, or what kind of Load
is returned by getLoad()
. It's job is to make sure that a load is fired.
I'd like to change the fire()
method definition inside HttpTarget
to:
@Override
public void fire(HttpLoad httpLoad) {
// ...
}
However when I do that, Java complains that the method override doesn't match the definition provided by its parent Target
class (as Load
and HttpLoad
are two different things).
What's the solution here? Generics? Abstract factories? <strong>Ultimately, I want to be able to enforce that HttpTarget
's fire()
method can only accept HttpLoad
s, but still be compatible with the Driver
code.</strong> Can someone provide a code example? Thanks in advance!
Yes, you would need generics:
public abstract class Target<L extends Load> {
public abstract void fire(L load);
}
public class HttpTarget extends Target<HttpLoad> {
@Override
public void fire(HttpLoad load) {
...
}
}
public interface TestSuite<L extends Load> { // or class
L getLoad();
Target<L> getTarget();
}
public class HttpTestSuite implements TestSuite<HttpLoad> {
@Override
public HttpLoad getLoad() {
...
}
@Override
public Target<HttpLoad> getTarget() {
return new HttpTarget();
}
}
The reason Java refuses to compile your HttpTarget class is because it doesn't override the Target's fire(Load)
method. Indeed, a Target, by contract is supposed to accept any kind of Load as argument. And the HttpTarget
's fire()
method only accepts instances of HttpLoad, and thus breaks the Liskov principle. Generics are the solution to this problem.
You will have to use generics and even then it is not exactly what you want.
public interface Load<T extends Load> {
public void someMethod();
}
public class HttpLoad implements Load<HttpLoad> {
@Override
public void someMethod() {
System.out.println("Http Load");
...
}
}
public abstract class Target<T extends Load> {
public abstract void fire(Load<T> load);
}
public class HttpTarget extends Target<HttpLoad> {
@Override
public void fire(Load<HttpLoad> load) {
load.someMethod();
}
}
Now if you write
Target<HttpLoad> httpTarget = new HttpTarget();
Load<HttpLoad> httpLoad = new HttpLoad();
Load<OtherLoad> otherLoad = new OtherLoad();
Load otherLoad2 = new OtherLoad();
httpTarget.fire(httpLoad);
httpTarget.fire(otherLoad); // this doesn't compile
httpTarget.fire(otherLoad2) // this how ever compiles