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

10

Я читал о шаблонах проектирования и читал, что шаблон проектирования прототипа избавляет от чрезмерного создания подклассов.

Почему подклассы плохо? Какое преимущество даст использование прототипа по сравнению с подклассами?

Дэвид Фокс
источник
говорит кто? У вас есть ссылка ?
Камю
Я читал с.118 этой книги. goo.gl/6JGw1
Дэвид

Ответы:

19

Почему подклассы слишком плохи

«Слишком много» - это суждение; но отнять это у меня плохо. Код, с которым я работаю, имеет наследование DEEP, и этот код чертовски трудно читать, понимать, отслеживать, отслеживать, отлаживать и т. Д. И т. Д. По сути, невозможно написать тестовый код для этого материала.

Образец прототипа избавляется от подклассов

Или вопрос означает «избавляет от слишком большого количества подклассов?». Этот шаблон требует клонирования объекта «шаблон», чтобы избежать создания подклассов, по крайней мере, на этом этапе. Нет правила, согласно которому «шаблон» не может быть подклассом.

Пользуйся композицией, а не наследством

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

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

Тестирование проще . Вы можете использовать поддельные части (например, "издевательства", "двойники" и другие разговоры о тестировании). Глубокое наследование нашего кода означает, что мы должны создать экземпляр всей иерархии для проверки любого ее фрагмента. В нашем случае это невозможно без запуска кода в реальной среде. Например, нам нужна база данных для создания экземпляров бизнес-объектов.

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

radarbob
источник
Мне было бы очень интересно увидеть некоторые примеры, когда наследование затрудняет тестирование (особенно насмешливое) и как помогает в этом композиция.
Арманд
@alison: метод в производном классе, который использует другой метод из базового класса, где реализация в базовом классе делает практически невозможным тестирование сценариев ошибок. Если бы это было композицией, было бы легче ввести объект, который вызывает ошибку
Rune FS
@allison: примеры? Когда база кода очень велика, когда базовые классы используются десятками классов напрямую, а сотни - наследованием. Потому что точка глубокого наследования - это повторное использование, и поэтому базовый класс обобщается до такой степени, что трассировка стека отладчика не может показать, какие методы фактически вызываются. Наконец, когда нет инъекций зависимостей, уже встроенных в такого Франкенштейна. Класс высшего уровня может «быть» полдюжиной или более другими вещами, которые в совокупности «имеют» десятки внутренних объектов - каждый со своим собственным домом развлечений наследства.
Радар Боб
8

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

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

jmq
источник
2

Две причины

  1. Производительность во время выполнения. Каждый раз, когда вы вызываете подкласс из суперкласса, вы выполняете вызов метода, поэтому, если вы вложили пять классов и вызываете метод в базовом классе, вы будете выполнять пять вызовов метода. Большинство компиляторов / сред выполнения попытаются распознать это и оптимизировать лишние вызовы, но это безопасно сделать только для очень простых случаев.

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

Джеймс Андерсон
источник
6
-1: Ты прав насчет # 2; но не о № 1. Несколько уровней наследования не обязательно влияют на производительность во время выполнения.
Джим Г.
Беспокоитесь о нескольких дополнительных вызовах процедур на уровне машинного кода и использовании OP и наследования, и вы беспокоитесь о
здравом уме
@JimG. Возможно, нет, но может. :) Существует также возможность использования памяти, о которой стоит беспокоиться: если вы не используете все переменные базы, они все равно могут быть выделены как часть конструирования объекта.
Адам Лир
2

«Слишком много» - это суждение.

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

Тем не менее, предпочтение композиции перед наследованием является хорошим эмпирическим правилом.

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

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

Одним из примеров, который я могу вспомнить, является java.util.Properties, который наследуется от Hashtable. Класс Properties предназначен только для хранения пар String-String (это отмечено в Javadocs). Из-за наследования методы put и putAll из Hashtable были предоставлены пользователям Properties. В то время как «правильный» способ сохранить свойство - это setProperty (), абсолютно ничто не мешает пользователю вызвать put () и передать String и Object.

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

seggy
источник
2

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


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

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

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

По сути, это не всегда так, но

Легко неправильно использовать наследование, чтобы нарушить инкапсуляцию

Разрешение подклассов может быть неправильным, если эта точка.

Дипан Мехта
источник
1

Краткий быстрый ответ:

Это зависит от того, что вы делаете.

Расширенный скучный ответ:

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

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

Дополнительное примечание:

Некоторые ответы относятся к «множественному наследованию». Хотя и не главный вопрос, это связано с темой. Есть несколько похожих сообщений о «множественном наследовании против состава». У меня был случай, когда я смешал оба.

umlcat
источник