Этот подход несовершенен.
Это не мешает объявлению, например
class D extends A<B> {
}
, которая скомпилирует, но выдает исключение во время выполнения, более конкретно:
new D().self().getClass() // => ClassCastException
Чтобы позволить классам обеспечивать функциональность, выходящую за пределы их известного суперкласса, вы можете попробовать адаптер pattern.
Обычно это интерфейс, например
interface Adaptable {
T getAdapter(Class<? extends T> key);
// for those who don't like to type long method names
default T as(Class<? extends T> key) {
return getAdapter(key);
}
}
Реализация может выглядеть как
class A implements Adaptable {
@Override
public T getAdapter(Class<? extends T> key) {
/*
* To be less strict, one might also check for 'key.isInstance(this)',
* but it's an implementation decision.
*/
if(getClass() == key) {
return key.cast(this);
}
return null;
}
}
Однако шаблон адаптера позволяет предоставлять другие объекты, обычно специализированные виды на цель, см. Пример FileSource
ниже.
Основным недостатком этого подхода является то, что клиент всегда должен проверять наличие адаптера. Однако, если клиент знал, что объект является искомого подкласса, который он ищет, он мог бы просто разыграть его, поэтому мы ничего не теряем. Интерфейс также можно расширить с помощью java.util.Optional
, но основная идея остается прежней.
interface Adaptable {
Optional<T> getAdapter(Class<? extends T> key);
}
Для примера использования, допустим, существует класс Source
, который моделирует доступный источник для любого процесса. Поскольку мы знаем, что обработка исходного кода часто сложна и поэтому ее трудно нормализовать в один класс или интерфейс, мы позволяем классу Source
реализовать Adaptable
.
class Source implements Adaptable {
@Override
public Optional<T> getAdapter(Class<? extends T> key) {
if(getClass() == key) {
return Optional.of(key.cast(this));
}
return Optional.empty();
}
}
Теперь есть базовая реализация, FileSource
, которая обычно доступна как java.io.File
.
class FileSource extends Source {
private File pointer;
public File asFile() {
return pointer;
}
}
Клиент теперь может проверить, доступен ли источник в виде файла, и выполнить некоторую операцию, используя базовый java.io.File
.
Source source;
...
source.getAdapter(FileSource.class).ifPresent(fileSource -> {
File file = fileSource.asFile();
// do your magic with 'file'
});
Еще лучше, FileSource
может просто предоставить адаптер для File
. На этом этапе клиенту даже не нужно заботиться о подклассе реализации, а только о том, что он на самом деле хочет.
class FileSource extends Source {
private File pointer;
@Override
public Optional<T> getAdapter(Class<? extends T> key) {
if(File.class == key) {
return Optional.of(key.cast(asFile()));
}
return super.getAdapter(key);
}
public File asFile() {
return pointer;
}
}
и
Source source;
...
source.getAdapter(File.class).ifPresent(file -> {
// do your magic with file
});