Наследование действительно необходимо?
Я должен признаться, что я немного скептик ООП. Плохие педагогические и трудовые переживания с объектной ориентацией не помогли. Поэтому я превратился в горячего верующего в Visual Basic (классический!).
Затем однажды я узнал, что С++ изменился, и теперь у него есть STL и шаблоны. Мне это очень понравилось! Сделал язык полезным. Затем в другой день MS решила применить лицевую хирургию к VB, и я действительно ненавидел конечный результат за безвозмездные изменения (использование "end while" вместо "wend" превратит меня в лучшего разработчика? Почему бы не отказаться от "следующего" для "end for", а также так много факторов Java, которые я нашел бесполезными (например, наследование, а также концепция иерархической структуры).
И теперь, спустя несколько лет, я задаюсь этим философским вопросом: нужно ли наследование действительно?
Банды из четырех говорят, что мы должны поддерживать композицию объекта над наследованием. И, подумав об этом, я не могу найти что-то, что вы можете сделать с наследованием, которое вы не можете сделать с агрегацией объектов и интерфейсами. Поэтому мне интересно, почему у нас даже есть это в первую очередь?
Любые идеи? Мне бы хотелось увидеть пример того, где наследование будет обязательно необходимо, или где использование наследования вместо компоновки + интерфейсов может привести к упрощению и упрощению модификации дизайна. В прежних работах, которые я нашел, если вам нужно изменить базовый класс, вам нужно также изменить почти все производные классы, поскольку они зависят от поведения родителя. И если вы создадите виртуальные методы базового класса... тогда не происходит обмена кодами: (
Иначе, когда я, наконец, создаю свой собственный язык программирования (длинное невыполненное желание, которое я нашел, большинство разработчиков разделяет), я не вижу смысла добавлять наследование к нему...
Ответы
Ответ 1
Действительно очень короткий ответ: Нет. Наследование не требуется, потому что действительно нужен только байт-код. Но, очевидно, байт-код или сборка не являются практически способом написания вашей программы. ООП - не единственная парадигма для программирования. Но, я отвлекся.
В начале 2000-х годов я учился в области компьютерных наук, когда наследование (а), композиции (есть), а интерфейсы (а) преподавались на равных основаниях. Из-за этого я использую очень мало наследования, потому что он часто лучше подходит по композиции. Это было подчеркнуто, потому что многие из профессоров видели плохой код (вместе с тем, что вы описали) из-за злоупотребления наследованием.
Независимо от создания языка с или без наследования, можете ли вы создать язык программирования, который предотвратит вредные привычки и плохие дизайнерские решения?
Ответ 2
Я думаю, что запрашивать ситуации, когда наследование действительно, нужно немного пропустить. Вы можете подделывать наследование с помощью интерфейса и некоторой композиции. Это не означает, что наследование бесполезно. Вы можете делать все, что вы делали в VB6 в ассемблере с некоторой дополнительной настройкой, что не означает, что VB6 бесполезен.
Обычно я просто начинаю использовать интерфейс. Иногда я замечаю, что на самом деле хочу наследовать поведение. Это обычно означает, что мне нужен базовый класс. Это просто.
Ответ 3
Наследование определяет отношение "Is-A".
class Point( object ):
# some set of features: attributes, methods, etc.
class PointWithMass( Point ):
# An additional feature: mass.
Выше я использовал наследование, чтобы официально объявить PointWithMass точкой.
Существует несколько способов обработки объекта P1
как PointWithMass, так и Point. Вот два.
-
Имеет ссылку от объекта PointWithMass
P1
на объект Point p1-friend
. p1-friend
имеет атрибуты Point
. Когда P1
должно участвовать в Point
-подобном поведении, ему необходимо делегировать работу своему другу.
-
Полагайтесь на наследование языка, чтобы гарантировать, что все функции Point
также применимы к моему объекту PointWithMass
, P1
. Когда P1
должно участвовать в Point
-подобном поведении, он уже является объектом Point
и может просто делать то, что нужно сделать.
Я бы предпочел не управлять дополнительными объектами, плавающими вокруг, чтобы гарантировать, что все функции суперкласса являются частью объекта подкласса. Я бы предпочел иметь наследование, чтобы быть уверенным, что каждый подкласс является экземпляром его собственного класса, плюс также является экземпляром всех суперклассов.
Изменить.
Для статически типизированных языков есть бонус. Когда я полагаюсь на язык для обработки этого, PointWithMass
можно использовать везде, где ожидался Point
.
Для действительно неясного злоупотребления наследованием читайте о странной "композиции с использованием частного наследования" С++. См. Любые разумные примеры создания наследования без создания отношений подтипирования? для дальнейшего обсуждения этого вопроса. Он объединяет наследование и состав; он не добавляет ясности или точности в полученный код; он применим только к С++.
Ответ 4
GoF (и многие другие) рекомендуют вам только одобрять композицию над наследованием. Если у вас есть класс с очень большим API, и вы хотите добавить к нему очень малое количество методов, оставив базовую реализацию в одиночку, я считаю неуместным использовать композицию. Вам нужно будет повторно реализовать все общедоступные методы инкапсулированного класса, чтобы просто вернуть их значение. Это пустая трата времени (программист и процессор), когда вы можете просто наследовать все это поведение и тратить свое время на новые методы.
Итак, чтобы ответить на ваш вопрос, вам не обязательно нужно наследование. Однако есть много ситуаций, когда это правильный выбор дизайна.
Ответ 5
Проблема с наследованием заключается в том, что он объединяет проблему подтипа (утверждение отношения is-a) и повторного использования кода (например, частное наследование используется только для повторного использования).
Итак, это не перегруженное слово, которое нам не нужно. Я бы предпочел подтипирование (используя ключевое слово "tools" ) и import (вроде Ruby делает это в определениях классов)
Ответ 6
Наследование позволяет мне оттолкнуть целую кучу бухгалтерского учета на компилятор, потому что это дает мне полиморфное поведение для иерархии объектов, которое мне в противном случае пришлось бы создавать и поддерживать. Независимо от того, насколько хорош ООП с серебряной маркой, всегда будут случаи, когда вы хотите использовать определенный тип поведения, потому что это имеет смысл делать. И в конечном счете, что точка ООП: это делает определенный класс проблем намного легче решить.
Ответ 7
Кто-нибудь еще помнит всех ОО-пуристов, баллистических по внедрению COM "сдерживания" вместо "наследования"? Он достиг практически той же цели, но с другим видом реализации. Это напоминает мне ваш вопрос.
Я стараюсь избегать религиозных войн в разработке программного обеспечения. ( "vi" ИЛИ "emacs"... когда все знают его "vi"!) Я думаю, что они являются признаком маленьких умов. Профессора Comp Sci могут позволить себе сидеть и обсуждать эти вещи. Я работаю в реальном мире, и мне все равно. Все это просто попытки дать полезные решения для реальных проблем. Если они будут работать, люди будут их использовать. Тот факт, что языки и инструменты OO были коммерчески доступны в широких масштабах для перехода на 20 лет, - довольно хорошая ставка, что они полезны для многих людей.
Ответ 8
В языке программирования есть много функций, которые не нужны. Но они существуют по целому ряду причин, которые в основном сводятся к повторному использованию и ремонтопригодности.
Все деловые заботы производят (качество, конечно) дешево и быстро.
Как разработчик, вы помогаете делать это, становясь более эффективным и продуктивным. Поэтому вам нужно убедиться, что код, который вы пишете, легко повторно использовать и поддерживаемый.
И, помимо всего прочего, это то, что дает вам наследство - способность повторно использовать, не изобретая колесо, а также возможность легко поддерживать ваш базовый объект без необходимости обслуживания на всех подобных объектах.
Ответ 9
Недостатки композиции в том, что она может маскировать взаимосвязь элементов, и для других может быть труднее понять. С, скажем, классом 2D Point и желанием распространить его на более высокие размеры, вам, вероятно, придется добавить (по крайней мере) Z getter/setter, изменить getDistance() и, возможно, добавить метод getVolume(). Таким образом, у вас есть элементы Объекты 101: связанное состояние и поведение.
Разработчик с композиционным мышлением предположительно определил метод getDistance (x, y) → double и теперь определит метод getDistance (x, y, z) → double. Или, думая в целом, они могут определить метод getDistance (lambdaGeneratingACoordinateForEveryAxis()) > double. Тогда они, вероятно, будут писать методы createTwoDimensionalPoint() и createThreeDimensionalPoint() factory (или, возможно, createNDimensionalPoint (n)), которые будут сшивать различные состояния и поведение.
Разработчик с мышлением OO будет использовать наследование. Такая же сложность в реализации характеристик домена, меньше сложность с точки зрения инициализации объекта (конструктор позаботится об этом по сравнению с методом factory), но не является гибким с точки зрения того, что можно инициализировать.
Теперь подумайте об этом с точки зрения понятности/удобочитаемости. Чтобы понять композицию, у вас есть большое количество функций, которые программируются внутри другой функции. Таким образом, мало что можно сказать о статической структуре кода (файлы и ключевые слова и т.д.), Из-за чего происходит смещение Z и расстояния(). В мире OO у вас есть большой большой мигающий красный свет, рассказывающий вам об иерархии. Кроме того, у вас есть по существу универсальный словарь для обсуждения структуры, широко известных графических обозначений, естественной иерархии (по крайней мере для одиночного наследования) и т.д.
Теперь, с другой стороны, хорошо подобранный и построенный метод factory часто будет делать более явное из-за неясных отношений между состоянием и поведением, поскольку композиционное мышление облегчает функциональный код (то есть код, который проходит состояние через параметры, а не через этот).
В профессиональной среде с опытными разработчиками гибкость композиции обычно превосходит ее более абстрактный характер. Однако никогда не следует умалять важность понятности, особенно в командах, которые имеют разную степень опыта и/или высокий уровень оборота.
Ответ 10
Наследование - это решение для реализации. Интерфейсы почти всегда представляют собой лучший дизайн и обычно должны использоваться во внешнем API.
Зачем писать много вызовов метода переадресации кода шаблона в составной объект-член, когда компилятор сделает это для вас с наследованием?
Этот ответ на другой вопрос очень хорошо отражает мое мышление.
Ответ 11
Там много полезных применений наследования и, возможно, столько же, сколько менее полезно. Одним из полезных является класс потока.
У вас есть метод, который должен иметь возможность передавать данные. Используя базовый класс потока в качестве входных данных для метода, вы гарантируете, что ваш метод может использоваться для записи во многие виды потоков без изменений. К файловой системе, по сети, сжатию и т.д.
Ответ 12
Нет.
для меня ООП в основном касается инкапсуляции состояния, поведения и полиморфизма.
и это так. но если вам нужна статическая проверка типов, вам понадобится способ группировки разных типов, поэтому компилятор может проверить, все еще позволяя вам использовать новые типы вместо другого связанного типа. создание иерархии типов позволяет использовать ту же концепцию (классы) для типов и групп типов, поэтому она наиболее широко используется.
но есть и другие способы, я думаю, что наиболее общим будет утиная типизация и тесно связанный с ним прототип ООП (который на самом деле не является наследованием, но обычно называется наследованием на основе прототипов).
Ответ 13
Зависит от вашего определения "необходимо". Нет, нет ничего невозможного без наследования, хотя альтернатива может потребовать более подробный код или серьезную переписку вашего приложения.
Но есть определенные случаи, когда наследование полезно. Как вы говорите, состав плюс интерфейсы охватывают почти все случаи, но что, если я хочу, чтобы по умолчанию было поведение? Интерфейс не может этого сделать. Базовый класс может. Иногда то, что вы хотите сделать, действительно просто переопределяет отдельные методы. Не переопределяйте класс с нуля (как с интерфейсом), а просто измените один его аспект. или вы можете не хотеть, чтобы все члены класса были переопределяемы. Возможно, у вас есть только один или два метода-члена, которые вы хотите, чтобы пользователь переопределял, а остальная часть, которая их вызывает (и выполняет валидацию и другие важные задачи до и после переопределенных пользователем методов), задается раз и навсегда в базовом классе, и его нельзя переопределить.
Наследование часто используется как костыль людьми, которые слишком одержимы узким определением ООП (и одержимостью) Java, хотя в большинстве случаев я согласен, что это неправильное решение, как если бы глубже ваша иерархия классов, лучше ваше программное обеспечение.
Ответ 14
Наследование - это хорошая вещь, когда подкласс действительно является тем же самым видом объекта, что и суперкласс. Например. если вы реализуете шаблон Active Record, вы пытаетесь сопоставить класс с таблицей в базе данных и экземплярами класса в строке в базе данных. Следовательно, весьма вероятно, что ваши классы Active Record будут совместно использовать общий интерфейс и реализацию таких методов, как: какой первичный ключ, сохраняется ли текущий экземпляр, сохранение текущего экземпляра, проверка текущего экземпляра, выполнение обратных вызовов при проверке и/или сохранение, удаление текущего экземпляра, запуск SQL-запроса, возврат имени таблицы, к которой относится класс, и т.д.
Похоже, из того, как вы формулируете свой вопрос, что вы предполагаете, что наследование одно, но не много. Если нам нужно многократное наследование, мы должны использовать интерфейсы плюс композицию, чтобы снять работу. Чтобы сказать об этом, Java предполагает, что наследование реализации является сингулярным, а наследование интерфейсов может быть несколько. Не нужно идти этим путем. Например. С++ и Ruby разрешают множественное наследование для вашей реализации и вашего интерфейса. Тем не менее, следует использовать множественное наследование с осторожностью (т.е. Сохранять свои абстрактные классы виртуальными и/или без гражданства).
Тем не менее, как вы отмечаете, слишком много реальных классов иерархии, где подклассы наследуют от суперкласса из удобства, а не имеют истинное отношение is-a. Поэтому неудивительно, что изменение суперкласса будет иметь побочные эффекты в подклассах.
Ответ 15
Не нужно, но полезно.
У каждого языка есть свои методы, чтобы писать меньше кода. OOP иногда запутывается, но я думаю, что это ответственность разработчиков, платформа OOP полезна и острая, когда она хорошо используется.
Ответ 16
Я согласен со всеми другими в отношении необходимого/полезного различия.
Причина, по которой мне нравится ООП, заключается в том, что она позволяет мне писать код, более чистый и логически организованный. Одно из самых больших преимуществ - это способность "факторизовать" логику, общую для ряда классов. Я мог бы дать вам конкретные примеры, где ООП серьезно сократил сложность моего кода, но это было бы скучно для вас.
Достаточно сказать, я сердце ООП.
Ответ 17
Абсолютно необходимо? нет,
Но подумай о лампадах. Вы можете создавать новую лампу с нуля каждый раз, когда вы ее создаете, или можете использовать свойства оригинальной лампы и создавать всевозможные новые стили лампы, которые имеют те же свойства, что и оригинал, каждый со своим стилем.
Или вы можете сделать новую лампу с нуля или сказать людям, чтобы она смотрела на нее определенным способом, чтобы увидеть свет, или, или, или
Не требуется, но приятно:)
Ответ 18
Спасибо всем за ваши ответы. Я сохраняю свою позицию, что, строго говоря, наследование не требуется, хотя я считаю, что нашел новую оценку этой функции.
Что-то еще: в моем опыте работы я обнаружил, что наследование приводит к более простым и понятным проектам, когда оно появилось в конце проекта, после того, как он заметил, что многие классы имеют много общего, и вы создаете базовый класс. В проектах, где грандиозная схема была создана с самого начала, с множеством классов в иерархии наследования, рефакторинг обычно является болезненным и dificult.
Увидев некоторые ответы, в которых упоминается что-то подобное, я задаюсь вопросом, может ли это быть не так, как предполагалось использовать наследование: ex post facto. Напоминает мне слова Степанова: "вы не начинаете с аксиом, вы заканчиваете аксиомами после того, как у вас есть куча связанных доказательств". Он математик, поэтому он должен что-то знать.
Ответ 19
Самая большая проблема с интерфейсами заключается в том, что они не могут быть изменены. Сделайте интерфейс общедоступным, затем измените его (добавьте к нему новый метод) и разбейте миллионы приложений по всему миру, потому что они внедрили ваш интерфейс, но не новый метод. Приложение может даже не запускаться, VM может отказаться от его загрузки.
Использовать базовый класс (не абстрактный), который другие программисты могут наследовать (и при необходимости переопределять методы); затем добавьте к нему метод. Каждое приложение, использующее ваш класс, по-прежнему будет работать, этот метод просто не будет отменен кем-либо, но поскольку вы обеспечиваете базовую реализацию, этот будет использоваться, и он может отлично работать для всех подклассов вашего класса... он может также вызывает странное поведение, потому что иногда переопределение было бы необходимым, хорошо, возможно, так и было, но по крайней мере все эти миллионы приложений в мире все равно будут запущены!
Я хочу, чтобы мое приложение Java все еще выполнялось после обновления JDK с 1,6 до 1,7 с некоторыми незначительными ошибками (которые могут быть исправлены с течением времени), чем отсутствие его запуска (принудительное немедленное исправление, или это будет бесполезно для человек).
Ответ 20
//Я нашел, что этот QA очень полезен. Многие ответили на это правильно. Но я хотел добавить...
1: Возможность определения абстрактного интерфейса - например, для разработчиков плагинов. Конечно, вы можете использовать указатели на функции, но это лучше и проще.
2: Наследование помогает типам моделей очень близко к их фактическим отношениям. Иногда во время компиляции возникает много ошибок, потому что у вас есть правильная иерархия типов. Например, shape <-- triangle
(можно сказать, что много повторного использования кода). Вы можете создать треугольник с объектом shape
, но shape
является неполным типом. Вставка фиктивных реализаций, таких как double getArea() {return -1;}
, сделает, но вы открываете место для ошибки. Этот return -1
может быть выполнен однажды!
3: void func(B* b); ... func(new D());
Неявное преобразование типов дает отличное нотационное удобство, так как Derived
есть Base
. Я помню, как прочитал Страуструп, говоря, что он хотел сделать классы первоклассных граждан, как фундаментальные типы данных (следовательно, перегружать операторов и т.д.). Неявное преобразование из Derived в Base ведет себя точно так же, как неявное преобразование из типа данных в более общий, совместимый (short to int).
Ответ 21
Наследование и Состав имеют свои плюсы и минусы.
Обратитесь к этому связанному вопросу SE о преимуществах наследования и недостатков композиции.
Предпочитаете композицию над наследованием?
Посмотрите пример в этой ссылке документации:
В этом примере показаны различные варианты использования переопределения, используя наследование как среднее для достижения полиморфизма.
Ответ 22
В дальнейшем наследование используется для представления конкретного свойства для всех нескольких конкретных воплощений того же типа. В этом случае функция GeneralPresenation имеет свойства, имеющие отношение ко всему "представлению" (данные, переданные в представление MVC). Мастер-страница - это единственное, что ее использует, и ожидает, что GeneralPresentation, хотя конкретные взгляды ожидают больше информации, соответствующей их потребностям.
public abstract class GeneralPresentation
{
public GeneralPresentation()
{
MenuPages = new List<Page>();
}
public IEnumerable<Page> MenuPages { get; set; }
public string Title { get; set; }
}
public class IndexPresentation : GeneralPresentation
{
public IndexPresentation() { IndexPage = new Page(); }
public Page IndexPage { get; set; }
}
public class InsertPresentation : GeneralPresentation
{
public InsertPresentation() {
InsertPage = new Page();
ValidationInfo = new PageValidationInfo();
}
public PageValidationInfo ValidationInfo { get; set; }
public Page InsertPage { get; set; }
}