Зачем избегать заключительного ключевого слова?

StackOverflow https://stackoverflow.com/questions/519914

Вопрос

В java есть ли когда-нибудь основания для расширения неабстрактного класса?

Кажется, это всегда указывает на плохой код, когда существуют иерархии классов.Согласны ли вы, и почему / why not?

Это было полезно?

Решение

Я согласен с Джоном и Кентом, но, как и Скотт Майерс (в эффективном C ++), я иду гораздо дальше.Я верю, что каждый класс должен быть либо abstract, или final.То есть, только конечные классы в любой иерархии действительно пригодны для прямого создания экземпляра.Все остальные классы (т.е.внутренние узлы в наследовании) являются “незавершенными” и, следовательно, должны быть abstract.

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

Другие советы

Конечно, бывают случаи, когда имеет смысл иметь не окончательные конкретные классы.Однако я согласен с Кентом - я считаю, что классы должны быть окончательными (запечатанными в C #) по умолчанию, и что методы Java должны быть окончательными по умолчанию (как и в C #).

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

Видишь "Как вы создаете класс для наследования" для более подробного обсуждения этого вопроса.

Этот вопрос в равной степени применим к другим платформам, таким как C # .NET.Есть те (включая меня), которые считают, что типы должны быть окончательными / запечатанными по умолчанию и должны быть явно распечатаны, чтобы разрешить наследование.

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

есть веские причины сохранить ваш код не окончательным.многие фреймворки, такие как hibernate, spring, guice, иногда зависят от не окончательных классов, которые они динамически расширяют во время выполнения.

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

Ваша лучшая ссылка здесь - пункт 15 превосходной книги Джошуа Блоха "Эффективная Java" под названием "Дизайн и документирование для наследования или же запретить это".Однако ключом к тому, следует ли разрешать расширение класса, является не "является ли он абстрактным", а "был ли он разработан с учетом наследования".Иногда между ними существует корреляция, но важно второе.Возьмем простой пример: большинство классов AWT предназначены для расширения, даже те, которые не являются абстрактными.

Краткое содержание главы Блоха заключается в том, что взаимодействие унаследованных классов с их родителями может быть удивительным и непредсказуемым, если предок не был разработан для наследования от.Поэтому классы должны быть двух видов: а) классы, предназначенные для расширения, и с достаточным количеством документации, описывающей, как это должно быть сделано; б) классы, помеченные как окончательные.Классы в (a) часто будут абстрактными, но не всегда.Для

В некоторых случаях вы хотите убедиться, что нет подклассов, в других случаях вы хотите обеспечить подклассы (абстрактные).Но всегда есть большое подмножество классов, где вас, как автора оригинала, это не волнует и не должно волновать.Это часть того, чтобы быть открытым / закрытым.Решение о том, что что-то должно быть закрыто, - это также это должно быть сделано по какой-то причине.

Я не согласен.Если бы иерархии были плохими, не было бы причин для существования объектно-ориентированных языков.Если вы посмотрите на библиотеки виджетов пользовательского интерфейса от Microsoft и Sun, вы наверняка обнаружите наследование.Это все "плохой код" по определению?Нет, конечно, нет.

Наследованием можно злоупотреблять, но то же самое можно сказать и о любой языковой функции.Хитрость заключается в том, чтобы научиться делать все надлежащим образом.

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

protected SomeType doSomething() {
return null;
}

Гарантируется, что это doSomething должно быть либо null, либо экземпляром SomeType .Допустим, у вас есть возможность обрабатывать экземпляр SomeType, но у вас нет варианта использования экземпляра SomeType в текущем классе, но вы знаете, что эту функциональность было бы действительно полезно иметь в подклассах, и почти все в ней конкретное.Нет смысла делать текущий класс абстрактным классом, если его можно использовать напрямую, по умолчанию ничего не делая с его нулевым значением.Если бы вы сделали это абстрактным классом, то у вас были бы его дочерние элементы в этом типе иерархии:

  • Абстрактный базовый класс
    • Класс по умолчанию (класс, который мог бы быть неабстрактным, реализует только защищенный метод и ничего больше)
    • Другие подклассы.

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

  • Класс по умолчанию
    • Другие подклассы.

Теперь, конечно, иерархии можно использовать и злоупотреблять ими, и если что-то не задокументировано четко или классы недостаточно хорошо спроектированы, подклассы могут столкнуться с проблемами.Но те же проблемы существуют и с абстрактными классами, вы не избавитесь от проблемы только потому, что добавите "abstract" в свой класс.Например, если контракт метода "doSomething()", описанный выше, требовал, чтобы SomeType заполнял поля x, y и z при обращении к ним с помощью методов получения и установки, ваш подкласс взорвался бы независимо от того, использовали ли вы конкретный класс, возвращающий null, в качестве базового класса или абстрактного класса.

Общее эмпирическое правило для проектирования иерархии классов - это в значительной степени простой вопросник:

  1. Нужно ли мне поведение предлагаемого мной суперкласса в моем подклассе?(Y/N) Это первый вопрос, который вам нужно задать себе.Если вам не нужно такое поведение, то нет никаких аргументов для создания подкласса.

  2. Нужно ли мне состояние предлагаемого мной суперкласса в моем подклассе?(Y/N) Это второй вопрос.Если состояние соответствует модели того, что вам нужно, это может быть canidate для подкласса.

  3. Если подкласс был создан из предлагаемого суперкласса, действительно ли это было бы отношением IS-A, или это просто короткий путь к наследованию поведения и состояния?Это последний вопрос.Если это просто ярлык, и вы не можете квалифицировать предлагаемый вами подкласс "как суперкласс", то следует избегать наследования.Состояние и логика могут быть скопированы и вставлены в новый класс с другим корнем, или может быть использовано делегирование.

Только если классу требуется поведение, состояние и можно считать, что подкласс ЯВЛЯЕТСЯ экземпляром суперкласса-A(n), следует считать, что он наследуется от суперкласса.В противном случае существуют другие варианты, которые лучше соответствовали бы поставленной цели, хотя это может потребовать немного больше предварительной работы, в долгосрочной перспективе это чище.

Есть несколько случаев, когда мы не хотим позволять изменять поведение.Например, класс String, Math.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top