Suppose I am designing something like the following interface:
public interface MyInterface{
public MyInterface method1();
public void method2(MyInterface mi);
}
However, there is the caveat that the return type for method1
and the parameter for method2
match the concrete implementation and not just MyInterface
. That is, if I have MyInterfaceImpl
that implements MyInterface
, it needs to have the following:
public class MyInterfaceImpl implements MyInterface{
@Override
public MyInterfaceImpl method1(){...}
@Override
public void method2(MyInterfaceImpl mi){...}
}
As written above, method1
won't cause any compile errors, but there is nothing guaranteeing that the return type matches in all implementations. Of course method2
won't even compile because the signature does not match the interface.
One candidate solution is to use self-referential or recursive bounds in generics:
public interface MyInterface<T extends MyInterface<T>>{
public T method1();
public void method2(T mi);
}
public class MyInterfaceImpl implements MyInterface<MyInterfaceImpl>{
@Override
public MyInterfaceImpl method1();
@Override
public void method2(MyInterfaceImpl mi);
}
This would get me what I want with one exception: other implementations might pass the wrong generic type (nothing forces T
to match the concrete type). So potentially someone else could implement the following:
public class NotMyInterfaceImpl implements MyInterface<MyInterfaceImpl>{
@Override
public MyInterfaceImpl method1();
@Override
public void method2(MyInterfaceImpl mi);
}
That would compile just fine even though NotMyInterfaceImpl
should implement MyInterface<NotMyInterfaceImpl>
.* That makes me think I need something else.
*Note that I don't think I'm trying to violate LSP; I'm OK with the return type/parameter being subclasses of NotMyInterfaceImpl
.
So I don't know of a clean way to do this. That leads me to believe that I might be focusing too much on implementation details in the interface, but it doesn't seem that way to me. Is there any way to do the type of thing I described, or is this some kind of smell that I'm putting something in an interface that doesn't belong there?