Почему использование 'final' в классе действительно так плохо?

34

Я занимаюсь рефакторингом старого сайта PHP OOP.

Мне так хочется начать использовать 'final' на классах для " make it explicit that the class is currently not extended by anything". Это может сэкономить много времени, если я приду в класс, и мне интересно, могу ли я переименовать / удалить / изменить protectedсвойство или метод. Если я действительно хочу расширить класс, я могу просто удалить ключевое слово final, чтобы разблокировать его для расширения.

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

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

Может быть, это портит создание объекта Mock или имеет другие побочные эффекты, о которых я не думаю.

Чего мне не хватает?

JW01
источник
3
Если у вас есть полный контроль над вашим кодом, то, я полагаю, это не страшно, но немного на стороне OCD. Что если вы пропустите класс (например, у него нет детей, но он не окончательный)? Лучше пусть инструмент отсканирует код и скажет, есть ли у какого-нибудь класса дети. Как только вы упаковываете это как библиотеку и отдаете кому-то еще, работа с 'final' становится болью.
Работа
Например, MbUnit - это открытая структура для модульного тестирования .Net, а MsTest - нет (неожиданный сюрприз). В результате вы ДОЛЖНЫ выложить 5 тыс. К MSFT только потому, что вы хотите запустить некоторые тесты MSFT. Другие организаторы тестов не могут запустить тестовую DLL, созданную с помощью MsTest, - все из-за последних классов, которые использовал MSFT.
Работа
3
Некоторая запись в блоге на эту тему: Финальные классы: открыт для расширения, закрыт для наследования (май 2014; Матиас Веррэс)
хакре
Хорошая статья. Это говорит мои мысли об этом. Я не знал об этих аннотациях, так что это было удобно. Приветствия.
JW01
«В PHP 5 вводится ключевое слово final, которое не позволяет дочерним классам переопределять метод путем добавления префикса к final. Если сам класс определяется как final, он не может быть расширен». php.net/manual/en/language.oop5.final.php Это совсем не плохо.
Черный

Ответы:

54

Я часто читал, что уроки должны быть окончательными только в редких / особых случаях.

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

Таким образом, использование finalпо умолчанию ни в коем случае не плохо. На самом деле, многие люди предполагают, что это должно быть по умолчанию, например, Джон Скит .

Может быть, это испортило создание Mock объекта ...

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

Конрад Рудольф
источник
1
1+: Так как это портит насмешку; рассмотрите возможность создания интерфейсов или абстрактных классов для каждого «конечного» класса.
Спойл
Хорошо, что ставит гаечный ключ в работах. Это опоздание противоречит всем другим ответам. Теперь я не знаю во что верить: o. (Снимите флажок с моего принятого ответа и отложите принятие решения во время повторного судебного разбирательства)
JW01
1
Вы можете рассмотреть сам Java API и то, как часто вы будете использовать ключевое слово final. На самом деле, он используется не очень часто. Он используется в тех случаях, когда производительность может ухудшиться из-за динамического связывания, возникающего при вызове «виртуальных» методов (в Java все методы являются виртуальными, если они не объявлены как «final» или «private»), или при представлении пользовательских подклассы класса Java API могут вызывать проблемы согласованности, такие как подкласс 'java.lang.String'. Java String - это объект-значение для каждого определения, и он не должен впоследствии изменять свое значение. Подкласс может нарушить это правило.
Джонни Ди,
5
@Jonny Большая часть API Java плохо спроектирована. Помните, что большинству из них уже более десяти лет, и за это время было разработано много лучших практик. Но не верьте мне на слово: так говорят сами инженеры Java, прежде всего Джош Блох, который подробно писал об этом, например, в Effective Java . Будьте уверены, что если бы Java API был разработан сегодня, он выглядел бы совсем по- другому и finalиграл бы гораздо большую роль.
Конрад Рудольф
1
Я до сих пор не убежден, что использование «финала» чаще стало лучшей практикой. По моему мнению, «окончательный вариант» действительно должен использоваться только в том случае, если этого требуют требования к производительности или если вы должны гарантировать, что согласованность не может быть нарушена подклассами. Во всех других случаях необходимая документация должна входить в документацию (которая должна быть написана в любом случае), а не в исходный код как ключевые слова, которые обеспечивают определенные шаблоны использования. Для меня это своего рода игра бога. Использование аннотаций будет лучшим решением. Можно добавить аннотацию @final, и компилятор может выдать предупреждение.
Джонни Ди
8

Если вы хотите оставить себе примечание о том, что у класса нет подклассов, то обязательно сделайте это и используйте комментарий, вот для чего они. Ключевое слово «final» - это не комментарий, а использование языковых ключевых слов просто для того, чтобы что-то сигнализировать вам (и только вы когда-либо знаете, что это значит) - плохая идея.

Джереми
источник
17
Это совершенно неверно! «Использование языковых ключевых слов просто для того, чтобы что-то сигнализировать вам […], является плохой идеей». Наоборот, именно для этого существуют языковые ключевые слова. Ваше предварительное предостережение, «и только вы когда-либо будете знать, что это значит», конечно, верно, но здесь не применимо; OP использует, finalкак ожидалось. В этом нет ничего плохого. А использование языковой функции для принудительного применения ограничения всегда лучше, чем использование комментария.
Конрад Рудольф
8
@Konrad: За исключением того, что finalдолжно означать «Никакой подкласс этого класса не должен создаваться» (по юридическим причинам или что-то в этом роде), а не «у этого класса в настоящее время нет детей, поэтому я все еще в безопасности связываться с его защищенными членами». Цель final- полная противоположность «свободно редактируемому», и у finalкласса даже не должно быть protectedчленов!
SF.
3
@ Конрад Рудольф Это не крошечный вообще. Если другие люди позже захотят расширить его классы, существует большая разница между комментарием, который говорит «не расширен», и ключевым словом, которое говорит «может не расширяться».
Джереми
2
@Konrad Rudolph Это звучит как хорошее мнение, чтобы поставить вопрос о дизайне языка ООП. Но мы знаем, как работает PHP, и он использует другой подход, разрешающий расширение, если оно не запечатано. Притворяться, что это нормально, сравнивать ключевые слова, потому что «так и должно быть» - это просто защита.
Джереми
3
@ Джереми, я не смешиваю ключевые слова. Ключевое слово finalозначает «этот класс не должен быть расширен [пока]». Ни больше, ни меньше. Было ли PHP разработано с учетом этой философии, не имеет значения: в конце концов, в нем естьfinal ключевое слово. Во-вторых, спор о дизайне PHP обречен на провал, учитывая то, как PHP-дизайн сложен и в целом плох.
Конрад Рудольф
5

Есть хорошая статья о том, «Когда объявлять классы окончательными» . Несколько цитат из этого:

TL; DR: Делайте ваши классы всегда final, если они реализуют интерфейс, и никакие другие открытые методы не определены

Почему я должен использовать final?

  1. Предотвращение массивной цепочки наследства судьбы
  2. Обнадеживающая композиция
  3. Заставить разработчика задуматься о публичном API пользователя
  4. Заставить разработчика сократить публичный API объекта
  5. finalКласс всегда может быть расширяемым
  6. extends нарушает инкапсуляцию
  7. Вам не нужна эта гибкость
  8. Вы можете изменить код

Когда следует избегать final:

Финальные занятия эффективно работают только при следующих допущениях:

  1. Существует абстракция (интерфейс), которую реализует последний класс
  2. Все общедоступные API финального класса являются частью этого интерфейса.

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

PS Спасибо @ocramius за отличное чтение!

Никита У.
источник
5

«окончательный» для класса означает: вы хотите подкласс? Давай, удаляй «финал», подкласс сколько хочешь, но не жалуйся мне, если он не работает. Ты сам по себе.

Когда класс может быть разделен на подклассы, это поведение, на которое полагаются другие, должно быть описано в абстрактных терминах, которым подчиняются подклассы. Вызывающие должны быть написаны, чтобы ожидать некоторую изменчивость. Документация должна быть написана тщательно; Вы не можете сказать людям «посмотрите на исходный код», потому что исходный код еще не там. Это все усилия. Если я не ожидаю, что класс находится в подклассе, это ненужное усилие. «final» ясно говорит, что эти усилия не были предприняты, и дает справедливое предупреждение.

gnasher729
источник
-1

Одна вещь, о которой вы, возможно, и не задумывались, это тот факт, что ЛЮБОЕ изменение класса означает, что он должен пройти новое QA-тестирование.

Не отмечайте вещи как окончательные, если вы действительно, действительно не имеете это в виду.


источник
Спасибо. Не могли бы вы объяснить это дальше. Ты имеешь в виду, что если я отмечу класс как final(изменение), тогда я просто должен повторно протестировать его?
JW01
4
Я бы сказал это более решительно. Не помечайте вещи как окончательные, если расширение не может быть выполнено из-за дизайна класса.
S.Lott
Спасибо S.Lott - я пытаюсь понять, как плохое использование final влияет на код / ​​проект. Были ли у вас какие-либо ужасные истории ужасов, которые заставили вас быть настолько догматичными в отношении щадящего использования final. Это опыт из первых рук?
JW01
1
@ JW01: у finalкласса есть один основной вариант использования. У вас есть полиморфные классы, которые вы не хотите расширять, потому что подкласс может нарушить полиморфизм. Не используйте, finalесли вы не должны предотвращать создание подклассов. Кроме этого, это бесполезно.
S.Lott
@ JW01, в производственных условиях вам может быть НЕ разрешено удалять финал, потому что это не тот код, который тестировал и утверждал заказчик. Однако вам может быть разрешено добавить дополнительный код (для тестирования), но вы не сможете делать то, что хотите, потому что вы не можете расширить свой класс.
-1

Использование 'final' лишает свободы других, которые хотят использовать ваш код.

Если код, который вы пишете, предназначен только для вас и никогда не будет выпущен для широкой публики или для клиента, то вы, конечно, можете делать со своим кодом то, что вы хотите. В противном случае вы не позволите другим использовать ваш код. Слишком часто мне приходилось работать с API, который было бы легко расширять для моих нужд, но потом мне мешал «финал».

Кроме того, часто есть код, который лучше не делать private, но protected. Конечно, privateозначает «инкапсуляция» и скрывает вещи, которые считаются деталями реализации. Но, как программист API, я мог бы также задокументировать тот факт, что метод xyzсчитается деталью реализации и, таким образом, может быть изменен / удален в будущей версии. Поэтому каждый, кто будет полагаться на такой код, несмотря на предупреждение, делает это на свой страх и риск. Но он действительно может сделать это и повторно использовать (надеюсь, уже протестированный) код и быстрее найти решение.

Конечно, если реализация API с открытым исходным кодом, можно просто удалить «финал» или сделать методы «защищенными», но затем вы изменили код и должны отслеживать изменения в форме патчей.

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

Обратите внимание, что я не считаю, что 'final' или 'private' - это зло, но я думаю, что они используются слишком часто, потому что программист не думал о своем коде с точки зрения повторного использования и расширения кода.

Джонни Ди
источник
2
«Наследование - это не повторное использование кода».
Quant_dev
2
Хорошо, если вы настаиваете на идеальном определении, вы правы. Наследование выражает отношение «есть», и именно этот факт учитывает полиморфизм и предоставляет способ расширения API. Но на практике наследование очень и очень часто используется для повторного использования кода. Особенно это касается множественного наследования. И как только вы вызываете код суперкласса, вы фактически повторно используете код.
Джонни Ди
@quant_dev: Повторное использование в ООП означает, прежде всего, полиморфность. И наследование является критической точкой для полиморфного поведения (совместное использование интерфейса).
Сокол
Интерфейс @Falcon Sharing! = Обмен кодом. По крайней мере, не в обычном смысле.
Quant_dev
3
@quant_dev: Вы неправильно используете возможность повторного использования в смысле ООП. Это означает, что один метод может работать со многими типами данных благодаря совместному использованию интерфейса. Это основная часть повторного использования кода ООП. А наследование автоматически позволяет нам совместно использовать интерфейсы, что значительно расширяет возможности повторного использования кода. Вот почему мы говорим о возможности повторного использования в ООП. Простое создание библиотек с подпрограммами почти так же стара, как само программирование, и может быть выполнено практически на любом языке, это часто встречается. Повторное использование кода в ООП - это нечто большее.
Сокол