Рубрики
Без рубрики

Вступающий в силу Java Вторник! Разрабатывайте и документируйте классы для наследования или запрещайте это.

Погружение в девятнадцатую главу книги “Эффективная Java”. Помеченный как java, эффективный, наследование, архитектура.

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

Первое, что нужно знать, это то, что вы должны документировать последствия переопределения каждого переопределяемого метода (т.Е. методы, которые не являются окончательными и общедоступными или защищенными). Это означает, что вы должны документировать, когда класс использует свои собственные методы внутри и, следовательно, когда переопределение метода может привести к неожиданным результатам. Мы видели пример этого в обзоре предыдущей главы. Другие вещи, которые должны быть задокументированы, – это то, может ли этот метод вызываться из статического контекста, из фонового потока и т.д. Все, что может повлиять на то, как разработчик реализует метод.

Начиная с Java 8, в JavaDoc была добавлена новая возможность для облегчения этого. Эта возможность добавляет новый раздел в документацию, специально предназначенный для облегчения этой документации, @implspec . Давайте посмотрим пример использования этого в java.util. AbstractCollection ‘s удалить javadoc метода.

{@inheritDoc}

This implementation iterates over the collection looking for the specified
element.  If it finds the element, it removes the element from the collection
using the iterator's remove method.

Note that this implementation throws an UnsupportedOperationException if the
iterator returned by this collection's iterator method does not implement the
remove method and this collection contains the specified object.

Это дает понять, что изменение того, как работает итератор , повлияет на поведение этой remove функции. Представьте, если бы у нас была документация такого типа, когда мы переопределили add и добавьте все функции в HashSet в нашем предыдущем обзоре мы могли бы знать, что функция addAll вызывала функцию add .

“Но, Кайл, – скажете вы, – разве в нашей документации не должно быть сказано о том, что делает функция, а не о том, как она это делает. ” Вы правы, это то, что предполагает хорошая практика документации API. К сожалению, поскольку наследование реализации нарушает инкапсуляцию, для облегчения реализации подклассов нам необходимо создать эту документацию “как”, чтобы разработчики подклассов знали, как это сделать, и не были ошеломлены неожиданным поведением.

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

Итак, что вы делаете частным и что вы делаете защищенный ? Это просто потребует некоторого размышления и опробования. К сожалению, формулы не существует. Лучший способ убедиться, что вы делаете доступным то, что вам нужно, но не делаете доступным то, что вам не нужно, – это просто написать несколько подклассов и посмотреть, как это выглядит. Подача, которая Эффективный Java makes заключается в создании трех подклассов для каждого из классов, которые вы пишете для расширения, и в том, чтобы один из этих подклассов был написан кем-то, кроме автора суперкласса, прежде чем вы его выпустите. После выпуска вы в значительной степени вынуждены сохранить реализацию, с которой начали.

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

public class Super {
  public Super() {
    overrideMe();
  }

  public void overrideMe() {}
}

public class Sub extends Super {
  private final Instant instant;

  public Sub() {
    this.instant = Instant.now();
  }

  @Override
  public void overrideMe() {
    System.out.println(instant);
  }

  public static void main() {
    Sub sub = new Sub();
    System.out.println(sub.overrideMe());
  }
}

Запустив этот код, вы увидите два выхода к вашему стандартному выходу. Один из них null и один из них – это значение в instant . Это происходит потому, что переопределение вызывается из конструктора класса Super . Самая сумасшедшая часть этого заключается в том, что мы получаем два разных значения, выводимых для одной и той же переменной final .

Некоторые другие вещи, которые следует иметь в виду, заключаются в том, что реализация Cloneable и Сериализуемый может быть опасным при реализации в одном из этих классов, созданных для наследования. Есть некоторые методы, которые можно найти в главе, посвященной Поддающийся клонированию для снижения этого риска. В основном это сводится к тому, чтобы не вызывать переопределяемые методы для выполнения работы cloneable.

Так что, как вы можете видеть, это большая работа. Действительно, так оно и есть. Вот почему наше действие по умолчанию должно заключаться в том, чтобы не создавать наши классы для наследования, если нам это действительно не нужно. Так как же нам предотвратить это? Есть два основных способа сделать это.

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

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

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

Оригинал: “https://dev.to/kylec32/effective-java-tuesday-design-and-document-classes-for-inheritance-or-else-prohibit-it-1751”