Перегрузка конструктора - лучшая практика в Java

Конструкторы могут быть перегружены, как и любой другой метод, и я знаю об этом факте. Из-за задачи я решил использовать абстрактный суперкласс с несколькими конструкторами:

Абстрактный суперкласс:

protected ListSortierer()
{
  this( null, null );
}

protected ListSortierer( List<E> li )
{
  this( li, null );
}

protected ListSortierer( Comparator<E> comp )
{
  this( null, comp );     
}

protected ListSortierer( List<E> li, Comparator<E> com )
{
  this.original = Optional.ofNullable( li );
  this.comp = Optional.ofNullable( com );
}

Для доступа к каждому из этих конструкторов мне понадобилось также несколько конструкторов в подклассе.

BubbleSort.java:

public ListBubbleSort()
{
  super();
}

public ListBubbleSort( List<E> li )
{
  super( li );
}

public ListBubbleSort( Comparator<E> com )
{
  super( com );
}

public ListBubbleSort( List<E> li, Comparator<E> com )
{
  super( li, com );
}

В этом случае каждый конструктор подкласса сразу вызывает конструктор суперкласса. Мне пришло в голову, что я могу снова обратиться к собственному конструктору и передать null значения:

public ListBubbleSort()
{
  this( null, null );
}

public ListBubbleSort( List<E> li )
{
   this( li, null );
}

public ListBubbleSort( Comparator<E> com )
{
   this( null, com );
}

public ListBubbleSort( List<E> li, Comparator<E> com )
{
   super( li, com );
}

Это позволит мне опустить 3 перегруженных конструктора в абстрактном суперклассе, но будет обеспечивать, чтобы каждый подкласс следовал одному и тому же шаблону.

Мой вопрос: каков наилучший подход в случае согласованности? Обрабатывайте отсутствующие значения в абстрактном суперклассе или в подклассе? Имеет ли это значение для создания экземпляра или это просто вопрос мнения?

Ответ 1

Каков наилучший подход в случае согласованности?

  1. Сделать все дочерние конструкторы частными.
  2. Внедрите статические заводские методы.

    ListBubbleSort.withList(List<E> list)
    ListBubbleSort.withComparator(Comparator<E> comparator)
    
  3. Сделайте вызов для соответствующего super конструктора. Не пропускайте null.

    public static <E> ListBubbleSort withList(List<E> list) {
        return new ListBubbleSort(list);
    }
    
    private ListBubbleSort(List<E>) {
        super(list);
    }
    
    protected ListSortierer(List<E>) {
        // initialise only the list field
        this.origin = list;
    }
    
  4. Не используйте Optional в качестве поля.

    this.original = Optional.ofNullable(li);

  5. Если у вас есть параметры 3+, рассмотрите шаблон Builder.

Обрабатывать отсутствующие значения в абстрактном суперклассе или в подклассе?

Предполагается, что конструктор должен предоставить начальные значения. Вы не передаете начальные значения, вы просто указываете на их отсутствие.

По умолчанию значение null является начальным значением для ссылочных типов. Таким образом, нет необходимости переназначать поле, если значение для него не было задано.

Имеет ли это значение для создания экземпляра или это просто вопрос мнения?

Читаемость, обслуживание.


Я бы рекомендовал прочитать " Эффективную Java" Джошуа Блоха:

Создание и уничтожение объектов

  • Пункт 1: Рассмотрите статические заводские методы вместо конструкторов
  • Пункт 2: рассмотрите построитель, столкнувшись со многими параметрами конструктора

Ответ 2

Что касается самого вопроса: я думаю, что оба варианта не идеальны.

Вы стараетесь писать как можно меньше кода. Вы осторожны с добавлением перегрузок только потому, что они кажутся удобными. На самом деле, вы должны сделать обратное: очень сильно подумайте, каковы ваши реальные варианты использования, и только поддерживайте их.

И в вашем случае, весь смысл упражнения, похоже, позволяет сортировать с различными реализациями. И в этом смысле вам следует скорее взглянуть на шаблон стратегии, например.

Другими словами: ваша первая мысль всегда предпочитает композицию над наследованием. И когда ваш дизайн приводит вас к таким вопросам, лучшим ответом может быть отход от вашего текущего проекта и поиск способа включить разную сортировку как своего рода "сервис", вместо того, чтобы поддерживать сам список вместе в сортировку.

Ответ 3

Это позволит мне опустить 3 перегруженных конструктора в абстрактном суперклассе, но будет обеспечивать, чтобы каждый подкласс следовал одному и тому же шаблону.

Выполнение контракта достигается посредством абстрактных методов или интерфейсов. Вы не можете быть уверены, что каждый подкласс будет иметь эти конструкторы, или, по крайней мере, вы не можете быть уверены, что каждый подкласс правильно добавит эти конструкторы.

Итак, учитывая инкапсуляцию, эти конструкторы лучше в суперклассе.

Ответ 4

Это позволит мне опустить 3 перегруженных конструктора в абстрактном суперклассе, но будет обеспечивать, чтобы каждый подкласс следовал одному и тому же шаблону.

Подкласс не имеет ограничений для вызова конкретного конструктора родительского класса. Он может вызывать любой из них, пока он вызывает один из них.
Таким образом, вы не смогли бы достичь такого требования.

О решении, использующем статический заводский метод, в некоторых случаях это приемлемо и даже прекрасно, но это не чудо, а также некоторые ограничения.
Например: он не позволяет переключиться на другие реализации.
Для классов JDK я согласен, что это почти никогда не проблема, но для пользовательского класса мы должны использовать ее с осторожностью.
Кроме того, ничто не мешает подклассу расширять родительский класс и не проходить по фабрике для создания экземпляров этого подкласса.
Таким образом, указанное требование не гарантируется.

Мой вопрос: каков наилучший подход в случае согласованности? Обрабатывайте отсутствующие значения в абстрактном суперклассе или в подклассе? Имеет ли это значение для создания экземпляра или это просто вопрос мнения?

Это не вопрос мнения. Это важно.
Если подкласс делегирует родительскому классу и что проверки всегда одни и те же, представляется интересным позволить родительскому классу выполнить проверки.
Но если у вас нет гарантии, что родительский класс никогда не будет модифицирован в отношении проверок, подклассы могут создавать некоторые экземпляры несогласованности, если родительский класс меняет свои проверки.

Ответ 5

Вы можете добавить окончательный addAll, который будет вызываться в дочерних конструкторах как дополнительный. Для компаратора семантика отличается, она (почти) должна быть перегружена, если это необходимо.

private final Optional<Comparator<E>> comparatorOption;

public final void addAll(List<E> li) {
    ...;
}

protected ListSortierer() {
    comparatorOption = Optional.empty();     
}

protected ListSortierer(Comparator<E> comp) {
    comparatorOption = Optional.of(comp);     
}

Лучше избегать нулевых параметров.

Предписание всех подклассов иметь одинаковые конструкторы, имея много конструкторов, неплохо, если вы хотите. Преимущество состоит в том, что API для всех классов ведет себя одинаково. Тем не менее, это необязательный код котловой плиты, который может предотвратить шаблон строителя. В исходной старой java многие конструкторы - это путь, но в настоящее время многие разработчики используют API.

Возможно, для сортировщика списков может существовать свободный API, использующий какой-то строитель.

Итак, окончательный ответ: более контекстное решение.