Если вы не хотите, чтобы пользовательские реализации RankerFactory
наследовали от DefaultRankerFactory
, вы можете создать абстрактный базовый класс (ABC), от которого наследуются CustomRankerFactory
и DefaultRankerFactory
, причем DefaultRankerFactory
не переопределяет любое поведение по умолчанию. Например:
interface RankerFactory {
Ranker<A> getRankerForA(paramsForA);
Ranker<B> getRankerForB(paramsForB);
Ranker<C> getRankerForC(paramsForC);
// ...
}
public abstract class AbstractRankerFactory {
public Ranker<A> getRankerForA(paramsForA) {
// ...default behavior...
}
public Ranker<B> getRankerForB(paramsForB) {
// ...default behavior...
}
public Ranker<C> getRankerForC(paramsForC) {
// ...default behavior...
}
}
public class DefaultRankerFactory extends AbstractRankerFactory {}
public class CustomRankerFactory extends AbstractRankerFactory {
@Override
public Ranker<C> getRankerForC(paramsForC) {
// ...custom behavior...
}
}
Это на самом деле не меняет поведение, которое вы сейчас демонстрируете, но удаляет DefaultRankerFactory
из иерархии CustomRankerFactory
. Таким образом, иерархия теперь читает CustomRankerFactory
- это AbstractRankerFactory
, а не CustomRankerFactory
- это DefaultRankerFactory
.
Предостережение для следующего примера: это не обязательно «лучший» подход к проектированию, но он действительный и может удовлетворить ваши потребности в конкретной ситуации. Тем не менее, в целом, вместо этого стоит создать азбуку. По сути, экономно используйте следующую технику.
В Java 8 были дополнения к существующим интерфейсам, которые нарушили бы существующую функциональность как в библиотеках Java, так и в пользовательских классах, которые реализовали эти интерфейсы. Например, интерфейс Iterable<T>
добавил следующий метод:
public void forEach(Consumer<? super T> consumer);
Это потребовало бы обновления всех интерфейса Iterable<T>
, включая пользовательские классы (не только в стандартной библиотеке Java). Команда Java решила, что это слишком большая нагрузка для разработчиков (это, по сути, нарушило бы большую часть существующего кода Java), но они все же хотели добавить важные функции, такие как функциональный метод forEach
, поэтому они скомпрометировали и добавил ключевое слово default
в Java.
Это ключевое слово позволяет интерфейсу предоставлять реализацию по умолчанию, которая используется всеми реализующими классами, если реализующий класс вручную не переопределяет реализацию по умолчанию. Например, реализация JDK 10 forEach
в Iterable<T>
:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
На практике это означало, что во всех реализациях Iterable<T>
был добавлен метод forEach
без необходимости переписывать какой-либо код. В случае вашей ранкерной системы вы можете использовать это ключевое слово default
, чтобы удалить ABC и уменьшить сложность фабричной иерархии. Например:
interface RankerFactory {
default Ranker<A> getRankerForA(paramsForA) {
// ...default behavior...
}
default Ranker<B> getRankerForB(paramsForB) {
// ...default behavior...
}
default Ranker<C> getRankerForC(paramsForC) {
// ...default behavior...
}
// ...
}
public class DefaultRankerFactory implements RankerFactory {}
public class CustomRankerFactory implements RankerFactory {
@Override
public Ranker<C> getRankerForC(paramsForC) {
// ...custom behavior...
}
}
Это снижает сложность иерархии, но имейте в виду, что оно использует ключевое слово default
для целей, для которых оно изначально не предназначалось, и это может вызвать путаницу в клиентском коде. Если используется этот подход, следует документально подтвердить, что все поведение по умолчанию содержится в интерфейсе, и что пользовательские реализации RankerFactory
должны реализовывать (т.е. переопределять) только те методы, для которых они хотят настраивать поведение, а не реализовывать все методы, как это делается. обычная практика с реализацией интерфейса.