Указанные образцы классов:
class S {}
class A extends S {
public int getAInfo() {
return 1;
}
}
class B extends S {
public int getBInfo() {
return 2;
}
}
Мы можем создать интерфейс K
, который использует преимущества параметра ограниченного типа следующим образом:
@FunctionalInterface
interface K<T extends S> {
void handle(T s);
@SafeVarargs
static <FT extends S> K<FT> compose(K<FT> finalK, K<? super FT>... ks) {
return s -> {
Stream.of(ks).forEach(k -> k.handle(s));
finalK.handle(s);
};
}
}
Затем мы можем использовать K.construct
для создания нового экземпляра K
, который будет пересылать s
по всему конвейеру K
s. Этот конвейер K
s структурирован таким образом, что все, кроме последнего k
, являются универсальными обработчиками c, принимающими любой экземпляр S
. Только последний примет экземпляр подкласса специфицированного c класса S
, а именно FT
(сокращение от «окончательного типа»).
С этим определением K
все следующие утверждения compile:
K<S> k1 = s -> System.out.println("K1 handled.");
K<S> k2 = s -> System.out.println("K2 handled.");
K<A> k3A = a -> System.out.println(a.getAInfo());
K<B> k3B = b -> System.out.println(b.getBInfo());
K.compose(k3A, k1, k2).handle(new A()); // Propagates instance of A through pipeline
K.compose(k3B, k1, k2).handle(new B()); // Propagates instance of B through pipeline
, но ad-ho c полиморфизм всех K
s в конвейере, которые не последний k
, все еще сохраняется, поскольку оба эти операторы также компилируются:
k1.handle(new S());
k2.handle(new S());
Один недостаток, который я вижу в этом решении, заключается в том, что, в отличие от другого ответа, обработчики K
должны выполняться строго последовательно и вызывать следующий обработчик k
не может находиться в середине предыдущего. Я все еще думаю, что вы можете расширить этот тип дизайна, чтобы обеспечить его поддержку.