Почему необходимо препятствовать тому, чтобы класс был разделен на подклассы?

Объединение 2 репозиториев

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master
8
задан Chris 23 August 2008 в 21:50
поделиться

13 ответов

Поскольку запись классов, которые будут substitutably расширены, чертовски трудна и требует, чтобы Вы сделали точные прогнозы того, как будущие пользователи захотят расширить то, что Вы записали.

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

21
ответ дан 5 December 2019 в 04:42
поделиться

Я хочу дать Вам это сообщение из "Завершенного Кода":

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

8
ответ дан 5 December 2019 в 04:42
поделиться

Что было бы, если Вы еще не уверены в интерфейсе и не хотите никакой другой код в зависимости от существующего интерфейса? [Это первое, что пришло на ум, но я интересовался бы другими причинами также!]

Править: Немного поиска с помощью Google дало следующее: http://codebetter.com/blogs/patricksmacchia/archive/2008/01/05/rambling-on-the-sealed-keyword.aspx

Заключение в кавычки:

Существует три причины, почему запечатанный класс лучше, чем негерметизированный класс:

  • Управление версиями: Когда класс первоначально изолируется, он может измениться на негерметизированный в будущем, не повреждая совместимость. (…)
  • Производительность: (…), если JIT-компилятор видит вызов к виртуальному методу с помощью изолированные типы, JIT-компилятор может произвести более эффективный код путем вызова метода нефактически. (…)
  • Безопасность и Предсказуемость: класс должен защитить свое собственное состояние и не позволить себе когда-либо становиться поврежденным. Когда класс распечатывается, производный класс может получить доступ и управлять состоянием базового класса, если какие-либо поля данных или методы, которые внутренне управляют полями, являются доступными и не частными. (…)
9
ответ дан 5 December 2019 в 04:42
поделиться

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

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

1
ответ дан 5 December 2019 в 04:42
поделиться

Единственное законное использование наследования должно определить особый случай базового класса как, например, когда наследовались Форме для получения Круга. Проверять этот взгляд на отношение в противоположное направление: действительно ли Форма является обобщением Круга? Если ответ - да затем, нормально использовать наследование.

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

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

Более конкретным примером был бы Шаблон "одиночка". Необходимо изолировать одиночный элемент, чтобы гарантировать, что нельзя повредить "singletonness".

2
ответ дан 5 December 2019 в 04:42
поделиться

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

Это подходит к Шаблонному шаблону разработки Метода. У меня есть интерфейс, который говорит, что "Я выполняю этот сервис". У меня затем есть класс, который реализует тот интерфейс. Но, что при выполнении того сервиса полагается на контекст, который базовый класс не знает о (и не должен знать о)? То, что происходит, - то, что базовый класс предоставляет виртуальные методы, которые или защищены или частные, и эти виртуальные методы являются рычагами для подклассов для обеспечения информации или действия, которое базовый класс не знает и не может знать. Между тем базовый класс может содержать код, который характерен для всех дочерних классов. Эти подклассы были бы изолированы, потому что они предназначены для выполнения той одной и только одной конкретной реализации сервиса.

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

Изоляция этих подклассов также препятствует глубоким уровням наследования, которое работает хорошо на платформы GUI, но работает плохо на слои бизнес-логики.

1
ответ дан 5 December 2019 в 04:42
поделиться

@jjnguy

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

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

Состав, кажется, часто пропускается; слишком часто люди хотят примкнуть к победившей стороне наследования. Они не должны! Замещаемость является трудной. Значение по умолчанию к составу; Вы будете благодарить меня в конечном счете.

1
ответ дан 5 December 2019 в 04:42
поделиться

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

Пример, о котором я могу думать прямо сейчас, настраивается содержавшие классы с глубокими клонами в .NET. Если Вы наследовались от них, Вы теряете глубокую способность к клону. [Я довольно нечеток на этом примере, это было некоторое время, так как я работал с IClonable], Если у Вас есть истинный одноэлементный класс, Вы, вероятно, не хотите наследованные формы его вокруг, и слой персистентности данных обычно не является местом, Вы хотите большое наследование.

0
ответ дан 5 December 2019 в 04:42
поделиться

Поскольку Вы всегда хотите быть врученными ссылку на класс а не на полученный по различным причинам:
i. инварианты, которые Вы имеете в некоторой другой части Вашего кода
ii. безопасность
и т.д.

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

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

Или возможно нет никакого смысла (что Вы видите теперь) в наличии подкласса.

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

0
ответ дан 5 December 2019 в 04:42
поделиться

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

Как идеальный пример, я недавно создавал небольшой пакет (Java, не C#, но те же принципы) для обертывания функциональности вокруг memcached инструмента. Я хотел интерфейс так в тестах, я мог дразнить далеко memcached клиент API, который я использовал, и также таким образом, мы могли переключить клиенты, если бы потребность возникла (существует 2 клиента, перечисленные на memcached домашней странице). Кроме того, я хотел иметь возможность заменить функциональность в целом, если бы потребность или требование возникли (такой, как будто memcached серверы снижаются по некоторым причинам, то мы могли потенциально заменить в горячем режиме с локальной реализацией кэша вместо этого).

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

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

Это - одна причина, мне действительно нравится язык программирования Ruby... даже базовые классы открыты, не только для расширения, но и на ADD И функциональность ИЗМЕНЕНИЯ динамично, НА САМ КЛАСС! Это назвало monkeypatching и может быть кошмаром, если злоупотреблено, но это - проклятая забава играть с!

1
ответ дан 5 December 2019 в 04:42
поделиться

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

Ну, существует причина: Ваш код теперь несколько изолируется от изменений до интерфейса memcached.

0
ответ дан 5 December 2019 в 04:42
поделиться

Производительность: (…), если JIT-компилятор видит вызов к виртуальному методу с помощью изолированные типы, JIT-компилятор может произвести более эффективный код путем вызова метода нефактически. (…)

Это - большая причина действительно. Таким образом, для критических по отношению к производительности классов, sealed и друзья имеют смысл.

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

0
ответ дан 5 December 2019 в 04:42
поделиться

Не все, это важно в классе, утверждается легко в коде. Может быть семантика и отношения, существующие, которые легко повреждаются путем наследования и методы переопределения. Переопределение одного метода за один раз является простым способом сделать это. Вы разрабатываете класс/объект как единственный значимый объект, и затем кто-то приезжает и думает, был ли метод или два 'лучше', он не причинил бы вреда. Это может или не может быть верно. Возможно, можно правильно разделить все методы между частным и не частные или виртуальные и не виртуальные, но это все еще не может быть достаточно. Требовательное наследование всех классов также помещает огромную дополнительную нагрузку на исходного разработчика для предвидения всех способов, которыми наследующий класс мог завинтить вещи.
Я не знаю об идеальном решении. Я сочувствую предотвращению наследования, но это - также проблема, потому что это препятствует поблочному тестированию.

0
ответ дан 5 December 2019 в 04:42
поделиться