Почему стоит использовать финальные классы
Максимальное уменьшение области видимости
Когда вы видите у класса префикс final, вы понимаете, что этот класс не может быть расширен любым другим, что не только делает его более читаемым, но и позволяет вам быть уверенным в том, что область действия логики ограничена этим конкретным классом.
Поощрение подхода «композиция вместо наследования»
Принцип открытости/закрытости гласит: класс должен быть открыт для расширения, но закрыт для изменений.
Если по какой-либо причине (хорошо понимая саму причину) вы решите использовать наследование, тогда просто удалите ключевое слово final, и всё готово.
Если вы «по умолчанию» не можете расширять класс (потому что он финальный), вы помогаете себе, думая прежде всего об использовании композиции вместо наследования.
Почему этот класс не финальный?
Если мы стремимся к композиции, а не к наследованию, то мы должны стараться избегать наследования, насколько это возможно, и использовать его только тогда, когда это действительно необходимо. Наследование в ООП часто используется неправильно.
Заблуждения
Когда мы в первый раз изучали ООП, мы приводили классический пример наследования. Тем не менее, когда Алан Кей создал Smalltalk, наследование не было его главной концепцией. Основной концепцией был обмен сообщениями, то есть вы можете отправлять сообщения объектам, а они инкапсулируют в себе данные и логику. Вы можете изменять их поведение, используя различные объекты, что на самом деле является композицией. Но в итоге концепция наследования настолько популярна, что в конечном счёте затмевает композицию.
Выгоды от использования финальных классов
- Понятные контракты. Использование интерфейсов заставит вас мыслить в терминах связей между объектами.
- Изолированные блоки кода без побочных эффектов. Внедрение интерфейсов в качестве зависимостей устранит все неприятные побочные эффекты кода, над которым вы работаете.
- Тестируемость. Подмена на фиктивные зависимости чрезвычайно проста, если они являются интерфейсами.
- Низкая и управляемая сложность. Поскольку всё изолировано, вам не нужно беспокоиться о волнообразных изменениях. Это значительно снижает сложность вашего кода.
- Низкая когнитивная нагрузка. С уменьшением сложности ваш мозг сможет сосредоточиться на самом важном.
- Гибкость кода. Удаляя любую ненужную связь, вы сможете рефакторить намного легче, чем раньше.
- Уверенность в себе. Возможность тестировать свой код изолированно не оставляет сомнений в правильности его изменения.
Композиция вместо наследования
- Если вы чувствуете необходимость перенастроить объект, изменить части алгоритма или переписать часть реализации, подумайте о создании нового класса вместо переопределения существующего.
- Допустим, вам нужно создать иерархию классов, где подклассы являются подходящими заменами своего родительского класса. Это была бы классическая ситуация, когда вы всё ещё можете подумать о наследовании. Однако результат может быть даже лучше, если вы наследуетесь не от конкретных родительских классов, а от абстрактных интерфейсов.
Что вам следует начать делать [вместо того что вы делаете сейчас]
- Используйте интерфейсы для определения контрактов между вашими классами.
- Используйте финальные классы для реализации поведения этих интерфейсов.
- Используйте композицию (через внедрение зависимостей посредством конструктора класса), чтобы собрать всё вместе и минимизировать сложность.
Итого: Интерфейсы > Финальные классы > Композиция
JediPhilosopher
Люто ненавижу тех, кто использует финальные классы в публичных библиотеках-фреймворках.
Потому что чистота кода и прочее абстрактное бла-бла-бла это хорошо, но в жизни все бывает несколько сложнее. И часто бывает так, что представление автора какой-нибудь библиотеки о том, как ее должны использовать, не совпадает с тем, как ее нужно использовать тебе. Да, можно стотыщ раз произнести мантры про "нельзя давать доступ к внутренней реализации, ведь она может измениться без предупреждений и все сломать". Но на практике, когда у тебя стотыщ разгневанных клиентов, а нужный для их удовлетворения кусок функционала завязан на протектед метод/поле, то единственный выход тут — не трындеть о чистоте кода, а отнаследоваться от нужного класса и сделать публичный геттер для этого метода (антипаттерн "Паблик Морозов"). И тут — нна, получи по лицу, гад! — класс оказывается final.
В итоге с чистотой кода все становится в сто раз хуже — приходится пытаться вырезать нужный функционал путем копипаста, что бывает иногда совсем не просто, если он сильно завязан на внутреннее состояние.
И нет, вариант с "давайте обратимся к разработчику и он вынесет это в публичный метод" не работает. Разработчика может уже вообще не быть (проект заброшен), он может быть не согласен с открытием этой функциональности, или просто может очень долго реагировать на запросы. А у меня дедлайн вот прям завтра, и мне прям смерть как нужен именно этот кусок функционала в паблике.
В общем я лично не вижу никаких плюсов в финальных классах в реальном мире (а не в мире розовых пони и идеального кода), одни проблемы от них.
oxidmod
1. Всегда можно сделать форк нужной либы и в нем подфиксить самому, если автора нет\долго реагирует
2. Всегда можно заюзать рефлексию
3. не завязывайте свой код на чужие библиотеки, используйте адаптеры и будет вам счастье
JediPhilosopher
С форками не хочется возиться, так как теряешь возможность обновлений и приходится этот форк поддерживать.
Ну вот рефлексию юзать и приходится. Там где она есть и доступна.
E_STRICT
anonymous
1. Не все либы распространяются в виде исходников. Не все либы openSource. При этом ошибка внутри библиотеки которая могла бы решиться overrideом одного метода, и передачей внутрь своей версии класса которая например использует точно время с тайм-сервера вместо системного, превращается в обмазывание костылями, которые нужно будет еще оторвать когда либу починят.
2. Особенно можно в языках, где её нет или она в зачаточном уровне (кажется тут нужно передавать пример swift)
3. А лучше вообще пишите весь код сами. Особенно когда это код взаимодействия с amazon s3 например. Ну или в случае, если вы не можете решить проблему из-за того, что кто-то решил что её не может существовать, сразу меняйте инфраструктуру.
Полностью согласен JediPhilosopher — final в теории должен решать проблему неправильного написания кода неопытным разработчиком — но его строгая принудительность может создать куда больше проблем в итоге.
Куда разумнее было-бы — если бы final давал предупреждение — псс, ты что-то делаешь не так, подумай еще разок! А уж если ты ставишь supress(«я полностью осознаяю последствия») не создавал более проблем.
И тут скорее кажется нужно использовать договоренности об именовании а не совершенно негибкие языковые конструкции.
oreym
прям с языка снял
sumanai
Или приватном, тогда вообще туши свет.
greatkir
А посмотрите-ка на эту ситуацию глазами автора библиотеки:
Вы решаете слегка отрефакторить код, не ломая обратную совместимость, и тут вам пишет разработчик, у которого ваши изменения в коде библиотеки ломают иерархию чего-то там — рефакторинг невозможен.
Вот и стелят соломку, чтобы по-максимому оградить себя от таких ситуаций, а не только из любви к идеальному коду.
maxim_0_o
Я думаю тут все просто, залез внутрь реализации — сам себе злобный буратино. Каждый кто это делает должен это понимать. И даже если он этого не понимает — это не проблема автора библиотеки.
E_STRICT
Как без
final
отличить где реализация, а где публичный API?E_STRICT
Класс без final это точка расширения, которую мантейнер обязан поддерживать как минимум до следующей мажорной версии проекта (если он следует семвер). По сути поддержка сводится к полному отказу от рефакторинга таких классов. Нельзя добавлять/удалять методы и нельзя изменять сигнатуры существующих протектед методов и свойств. Иначе прилетят гневные багрепорты и жалобы на не соблюдение BC политики.
merhalak
Добавлять может в миноре. Бинарная совместимость сохранится (по крайней мере, в Java):
docs.oracle.com/javase/specs/jls/se7/html/jls-13.html
rjhdby
Ну вот, собственно, эталонный пример, почему в публичных библиотеках/фрейморках предпочитают финалить. Человек пользуется, на халяву, результатами чужих трудов и люто ненавидит авторов за то, что они сделали не так, как удобно лично ему. Мне страшно представить, насколько сильнее будет эта ненависть, если в следующем релизе случайно поломается обратная совместимость к случайно не зафиналенному классу… Оно им надо, потоки вот такого выслушивать?
denisyukphp
Использовать наследование от библиотек плохая идея, всё действительно может поменяться в приватных методах или свойствах, лучше полагаться на апи библиотеки и если очень хочется чего-то допилить, то лучший вариант — декорировать или проксировать нужный класс. В зависимости от вашей задачи, можно закрыть интерфейсом это класса, которая нормальная либа должна поставлять.