В моих глазах существует несколько возможностей, каждый имеющий недостатки и профессионалов:
, Как Вы видите, "хорошие" решения, все зависят от пользовательского выбора, который снова показывает пользователя как самый слабый элемент цепочки.
Какие-либо другие предложения?
Согласно Википедии , «запечатанные классы в основном используются для предотвращения наследования. Они добавляют еще один уровень строгости во время компиляции, улучшают использование памяти и запускают определенные оптимизации, которые повысить эффективность времени выполнения ».
Также из блога Патрика Смаккиа :
Управление версиями: когда класс изначально запечатан, он может измениться на незапечатанный в будущем без нарушения совместимости. (…)
Производительность: (…) если JIT-компилятор видит вызов виртуального метода с использованием запечатанных типов, JIT-компилятор может создать более эффективный код, вызвав метод невиртуально. (…)
Безопасность и Предсказуемость: класс должен защищать свое собственное состояние и не позволять себе когда-либо испортиться. Когда класс распечатан, производный класс может получать доступ и управлять состоянием базового класса, если какие-либо поля данных или методы, которые внутренне управляют полями, доступны и не являются закрытыми. (…)
Это все довольно веские причины - я фактически не знал о преимуществах производительности последствия, пока я не нашел его только сейчас :)
Точки управления версиями и безопасности кажутся огромным преимуществом с точки зрения надежности кода, что очень хорошо оправдано для любого большого проекта. Конечно, это не добавка для модульного тестирования, но она может помочь.
)Управление версиями и точки безопасности кажутся огромным преимуществом с точки зрения надежности кода, что очень хорошо оправдано для любого большого проекта. Конечно, это не добавка для модульного тестирования, но она может помочь.
)Управление версиями и точки безопасности кажутся огромным преимуществом с точки зрения надежности кода, что очень хорошо оправдано для любого большого проекта. Конечно, это не добавка для модульного тестирования, но она может помочь.
Потому что создание типа для наследования намного сложнее, чем думает большинство людей. Лучше всего отмечать все типы таким образом по умолчанию, поскольку это предотвратит наследование других типов от типа, который никогда не предназначался для расширения.
Следует ли расширять тип - это решение каждого разработчик, который его создал, а не разработчик, который приходит позже и хочет расширить его.
Джошуа Блох в своей книге «Эффективная Java» говорит об этом. Он говорит «документ на наследство или запретить его». Дело в том, что класс - это своего рода контракт между автором и клиентом. Разрешение клиенту наследовать от базового класса делает этот контракт намного более строгим. Если вы собираетесь наследовать от него, вы, скорее всего, переопределите некоторые методы, в противном случае вы можете заменить наследование на композицию. Какие методы разрешено переопределять и что вы должны делать для их реализации - следует задокументировать, иначе ваш код может привести к непредсказуемым результатам. Насколько я помню, он показывает такой пример - вот класс коллекции с методами
public interface Collection<E> extends Iterable<E> {
...
boolean add(E e);
boolean addAll(Collection<? extends E> c);
...
}
Есть некая реализация, то есть ArrayList. Теперь вы хотите унаследовать от него и переопределить некоторые методы, чтобы он выводил на консоль сообщение при добавлении элемента. Теперь вам нужно переопределить как add , так и addAll , или только добавить ? Это зависит от того, как реализован addAll - работает ли он напрямую с внутренним состоянием (как ArrayList) или вызывает add (как AbstractCollection). Или может быть addInternal , который вызывается как add , так и addAll . Таких вопросов не было, пока вы не решили унаследовать от этого класса. Если просто использовать - не беспокоит. Итак, автор класса должен задокументировать его, если он хочет, чтобы вы унаследовали от его класса.
А что, если он захочет изменить реализацию в будущем? Если его класс только используется, а не наследуется от него, ничто не мешает ему изменить реализацию на более эффективную. Теперь, если вы унаследовали от этого класса, просмотрели исходный код и обнаружили, что addAll вызывает add , вы переопределяете только добавить . Позже автор изменил реализацию, так что addAll больше не вызывает add - ваша программа не работает, сообщение не выводится при вызове addAll . Или вы посмотрели на источник и обнаружили, что addAll не вызывает add, поэтому вы переопределяете add и addAll . Теперь автор изменяет реализацию, поэтому addAll вызывает add - ваша программа снова не работает, когда вызывается addAll , сообщение печатается дважды для каждого элемента.
Итак. - если вы хотите, чтобы ваш класс был унаследован, вам необходимо задокументировать, как это сделать. Если вы думаете, что вам может потребоваться что-то изменить в будущем, что может сломать некоторые подклассы - вам нужно подумать, как этого избежать. Позволяя своим клиентам наследовать от вашего класса, вы раскрываете гораздо больше деталей внутренней реализации, чем вы делаете, когда просто позволяете им использовать ваш класс - вы открываете внутренний рабочий процесс, который часто может быть изменен в будущих версиях.
Если вы раскрываете какие-то детали, и клиенты полагаются на них - вы больше не можете их изменить. Если вас это устраивает или вы задокументировали, что можно, а что нельзя переопределить - это нормально. Иногда вы этого просто не хотите. Иногда вы просто хотите сказать - «просто используйте этот класс, никогда не наследовать от него, потому что я хочу свободу изменять детали внутренней реализации».
Итак, в основном комментарий: «Потому что класс не хочет иметь потомков, и мы следует уважать его пожелания ».
Итак, кто-то хочет пометить класс как окончательный / запечатанный, когда он думает, что возможные изменения деталей реализации более ценны, чем наследование. Есть и другие способы достижения результатов, подобных наследованию.