Наследование (объектно-ориентированное программирование) - Inheritance (object-oriented programming)

В объектно-ориентированного программирования, наследование механизм базирования объект или же учебный класс на другой объект (наследование на основе прототипов ) или класс (наследование на основе классов ), сохранив аналогичную реализацию. Также определяется как создание новых классов (подклассы ) от существующих, таких как суперкласс или базовый класс а затем формируя их в иерархию классов. В большинстве объектно-ориентированных языков на основе классов объект, созданный посредством наследования, «дочерний объект», приобретает все свойства и поведение «родительского объекта», за исключением: конструкторы, деструктор, перегруженные операторы и функции друзей базового класса. Наследование позволяет программистам создавать классы, основанные на существующих классах,[1] для указания новой реализации при сохранении того же поведения (реализация интерфейса ), чтобы повторно использовать код и независимо расширять исходное программное обеспечение через общедоступные классы и интерфейсы. Отношения объектов или классов посредством наследования порождают ориентированный граф.

Наследование было изобретено в 1969 году для Симула[2] и теперь используется во многих объектно-ориентированных языках программирования, таких как Ява, C ++ или Python.

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

Наследование не следует путать с подтип.[3][4] В некоторых языках наследование и подтипирование согласуются,[а] тогда как в других они отличаются; в общем, выделение подтипов создает это отношения, тогда как наследование только повторно использует реализацию и устанавливает синтаксические отношения, не обязательно семантические отношения (наследование не гарантирует поведенческий подтип ). Чтобы различать эти концепции, выделение подтипов также известно как наследование интерфейса, тогда как наследование, как здесь определено, известно как наследование реализации или же наследование кода.[5] Тем не менее, наследование - это часто используемый механизм для установления отношений подтипов.[6]

Наследование противопоставляется состав объекта, где один объект содержит другой объект (или объекты одного класса содержат объекты другого класса); видеть композиция выше наследования. Композиция реализует имеет отношения, в отличие от отношения подтипов.

Типы

Одиночное наследование
Множественное наследование

Существуют различные типы наследования, основанные на парадигме и конкретном языке.[7]

Одиночное наследование

где подклассы наследуют свойства одного суперкласса. Один класс приобретает свойства другого класса.

Множественное наследование

где один класс может иметь более одного суперкласса и наследовать функции от всех родительских классов.

"Множественное наследование ... считалось, что будет очень сложно реализовать эффективно. Например, в резюме C ++ в его книге о Цель C, Брэд Кокс фактически утверждал, что добавить множественное наследование в C ++ невозможно. Таким образом, множественное наследование казалось более сложной задачей. Поскольку я рассматривал множественное наследование еще в 1982 году и нашел простую и эффективную технику реализации в 1984 году, я не мог устоять перед этой задачей. Я подозреваю, что это единственный случай, когда мода повлияла на последовательность событий ".[8]

Многоуровневое наследование

где подкласс наследуется от другого подкласса. Нередко класс является производным от другого производного класса, как показано на рисунке «Многоуровневое наследование».

Многоуровневое наследование

Класс А служит базовый класс для производный класс B, который, в свою очередь, служит базовый класс для производный класс C. Класс B известен как средний базовый класс, потому что он обеспечивает связь для наследования между А и C. Цепь ABC известен как путь наследования.

Производный класс с многоуровневым наследованием объявляется следующим образом:

Учебный класс А(...);      // Базовый классУчебный класс B : общественный А(...);   // B получено из AУчебный класс C : общественный B(...);   // C получено из B

Этот процесс можно расширить до любого количества уровней.

Иерархическое наследование

Здесь один класс служит суперклассом (базовым классом) для более чем одного подкласса. Например, родительский класс A может иметь два подкласса B и C. Родительский класс B и C - это A, но B и C - это два отдельных подкласса.

Гибридное наследование

Гибридное наследование - это когда происходит сочетание двух или более из вышеперечисленных типов наследования. Примером этого является случай, когда у класса A есть подкласс B, который имеет два подкласса, C и D. Это смесь как многоуровневого, так и иерархического наследования.

Подклассы и суперклассы

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

Общая форма определения производного класса:[9]

учебный класс Подкласс: видимость СуперКласс{    // члены подкласса};
  • Двоеточие указывает, что подкласс наследуется от суперкласса. Видимость не является обязательной и, если есть, может быть либо частный или же общественный. Видимость по умолчанию частный. Видимость указывает, являются ли функции базового класса частный или же публично полученный.

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

Неклассифицируемые классы

В некоторых языках класс может быть объявлен как неклассифицированный добавив определенные модификаторы класса к объявлению класса. Примеры включают окончательный ключевое слово в Ява и C ++ 11 вперед или запечатанный ключевое слово в C #. Такие модификаторы добавляются к объявлению класса перед учебный класс ключевое слово и объявление идентификатора класса. Такие неподклассифицируемые классы ограничивают возможность повторного использования, особенно когда разработчики имеют доступ только к предварительно скомпилированным двоичные файлы и нет исходный код.

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

Непереопределяемые методы

Так же, как классы могут быть неподклассифицированными, объявления методов могут содержать модификаторы методов, которые предотвращают переопределение метода (т. Е. Замену новой функцией с тем же именем и сигнатурой типа в подклассе). А частный метод не может быть переопределен просто потому, что он недоступен для других классов, кроме класса, функцией-членом которого он является (это неверно для C ++, хотя). А окончательный метод в Ява, а запечатанный метод в C # или замороженный особенность в Эйфель не может быть отменено.

Виртуальные методы

Если метод суперкласса виртуальный метод, то вызовы метода суперкласса будут динамически отправляется. Некоторые языки требуют, чтобы методы были специально объявлены как виртуальные (например, C ++ ), а в других - все методы виртуальные (например, Ява ). Вызов не виртуального метода всегда будет отправляться статически (т.е.адрес вызова функции определяется во время компиляции). Статическая отправка выполняется быстрее, чем динамическая, и позволяет выполнять такие оптимизации, как встроенное расширение.

Видимость унаследованных членов

В следующей таблице показано, какие переменные и функции наследуются в зависимости от видимости, заданной при создании класса.[10]

Видимость базового классаВидимость производного класса
Публичное происхождениеЧастное происхождениеЗащищенный вывод
  • Частный →
  • Защищено →
  • Общедоступные →
  • Не наследуется
  • Защищено
  • Общественные
  • Не наследуется
  • Частный
  • Частный
  • Не наследуется
  • Защищено
  • Защищено

Приложения

Наследование используется для соотнесения двух или более классов друг с другом.

Отмена

Иллюстрация переопределения метода

Много объектно-ориентированные языки программирования разрешить классу или объекту заменять реализацию унаследованного им аспекта - обычно поведения. Этот процесс называется преобладающий. При переопределении возникает сложность: какую версию поведения использует экземпляр унаследованного класса - ту, которая является частью его собственного класса, или версию родительского (базового) класса? Ответ зависит от языка программирования, и некоторые языки предоставляют возможность указать, что конкретное поведение не должно быть отменено и должно вести себя так, как определено базовым классом. Например, в C #, базовый метод или свойство можно переопределить в подклассе, только если он помечен модификатором virtual, abstract или override, тогда как в языках программирования, таких как Java, можно вызывать различные методы для переопределения других методов.[11] Альтернативой переопределению является прячется унаследованный код.

Повторное использование кода

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

В следующем примере Python подклассы КвадратСуммаКомпьютер и КубСуммаКомпьютер преодолеть преобразовать () метод базового класса SumComputer. Базовый класс содержит операции по вычислению суммы квадраты между двумя целыми числами. Подкласс повторно использует все функции базового класса, за исключением операции, которая преобразует число в его квадрат, заменяя его операцией, которая преобразует число в его квадрат. квадрат и куб соответственно. Следовательно, подклассы вычисляют сумму квадратов / кубиков между двумя целыми числами.

Ниже приведен пример Python.

учебный класс SumComputer:    def __в этом__(себя, а, б):        себя.а = а        себя.б = б    def преобразовать(себя, Икс):        поднимать NotImplementedError    def входы(себя):        возвращаться классифицировать(себя.а, себя.б)    def вычислить(себя):        возвращаться сумма(себя.преобразовать(ценить) за ценить в себя.входы())учебный класс КвадратСуммаКомпьютер(SumComputer):    def преобразовать(себя, Икс):        возвращаться Икс * Иксучебный класс КубСуммаКомпьютер(SumComputer):    def преобразовать(себя, Икс):        возвращаться Икс * Икс * Икс

В большинстве случаев наследование классов с единственной целью повторного использования кода вышло из моды.[нужна цитата ] Основная проблема заключается в том, что наследование реализации не дает никаких гарантий полиморфный заменяемость - экземпляр класса многократного использования не обязательно может быть заменен экземпляром унаследованного класса. Альтернативный метод, явный делегация, требует больше усилий по программированию, но позволяет избежать проблемы заменяемости.[нужна цитата ] В C ++ частное наследование может использоваться как форма наследование реализации без заменяемости. В то время как публичное наследование представляет собой "это «отношения и делегирование представляют собой»имеет «отношения», частное (и защищенное) наследование можно рассматривать как «реализовано в терминах» отношений.[12]

Еще одно частое использование наследования - гарантия того, что классы поддерживают определенный общий интерфейс; то есть они реализуют одни и те же методы. Родительский класс может быть комбинацией реализованных операций и операций, которые должны быть реализованы в дочерних классах. Часто между супертипом и подтипом не происходит изменений интерфейса - дочерний класс реализует описанное поведение вместо своего родительского класса.[13]

Наследование против подтипов

Наследование похоже на, но отличается от подтип.[14]Подтипирование позволяет заменять данный тип другим типом или абстракцией и, как говорят, устанавливает это связь между подтипом и некоторой существующей абстракцией, явно или неявно, в зависимости от языковой поддержки. Отношения могут быть явно выражены через наследование на языках, которые поддерживают наследование как механизм подтипа. Например, следующий код C ++ устанавливает явное отношение наследования между классами. B и А, куда B является подклассом и подтипом А, и может использоваться как А где бы B указан (через ссылку, указатель или сам объект).

учебный класс А { общественный:  пустота DoSomethingALike() const {}};учебный класс B : общественный А { общественный:  пустота Сделать что-то() const {}};пустота UseAnA(const А& а) {  а.DoSomethingALike();}пустота SomeFunc() {  B б;  UseAnA(б);  // b можно заменить на A.}

В языках программирования, которые не поддерживают наследование как механизм подтипа, связь между базовым классом и производным классом - это только связь между реализациями (механизм повторного использования кода) по сравнению с отношением между типы. Наследование, даже в языках программирования, которые поддерживают наследование как механизм подтипа, не обязательно влечет за собой поведенческий подтип. Вполне возможно создать класс, объект которого будет вести себя неправильно при использовании в контексте, где ожидается родительский класс; увидеть Принцип подстановки Лискова.[15] (Сравнивать коннотация / значение.) В некоторых языках ООП понятия повторного использования кода и создания подтипов совпадают, потому что единственный способ объявить подтип - это определить новый класс, который наследует реализацию другого.

Ограничения дизайна

Широкое использование наследования при разработке программы накладывает определенные ограничения.

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

При определении этой иерархии наследования мы уже определили определенные ограничения, не все из которых желательны:

Безбрачие
Используя одиночное наследование, подкласс может наследовать только от одного суперкласса. Продолжая приведенный выше пример, Человек может быть либо Ученик или Наемный рабочий, но не то и другое одновременно. С помощью множественное наследование частично решает эту проблему, так как затем можно определить СтудентСотрудник класс, который наследуется от обоих Ученик и Наемный рабочий. Однако в большинстве реализаций он по-прежнему может наследовать от каждого суперкласса только один раз и, таким образом, не поддерживает случаи, когда студент имеет две работы или посещает два учреждения. Модель наследования, доступная в Эйфель делает это возможным благодаря поддержке повторное наследование.
Статический
Иерархия наследования объекта зафиксирована на реализация когда тип объекта выбран и не меняется со временем. Например, граф наследования не позволяет Ученик объект стать Наемный рабочий объект при сохранении состояния его Человек суперкласс. (Однако такого поведения можно добиться с помощью декоратор шаблон.) Некоторые критиковали наследование, утверждая, что оно ограничивает разработчиков их исходными стандартами проектирования.[16]
Видимость
Когда клиентский код имеет доступ к объекту, он обычно имеет доступ ко всем данным суперкласса объекта. Даже если суперкласс не объявлен общедоступным, клиент все равно может В ролях объект к его типу суперкласса. Например, невозможно дать функции указатель на Учениксредний балл и стенограмма без предоставления этой функции доступа ко всем личным данным, хранящимся в Человек суперкласс. Многие современные языки, включая C ++ и Java, предоставляют "защищенный" модификатор доступа, который позволяет подклассам получать доступ к данным, не разрешая доступ к ним какому-либо коду вне цепочки наследования.

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

Проблемы и альтернативы

Наследование реализации вызывает споры среди программистов и теоретиков объектно-ориентированного программирования, по крайней мере, с 1990-х годов. Среди них авторы Шаблоны проектирования, которые выступают за наследование интерфейса и предпочитают композиция выше наследования. Например, декоратор шаблон (как уже упоминалось над ) было предложено преодолеть статический характер наследования между классами. В качестве более фундаментального решения той же проблемы, ролевое программирование вводит отчетливые отношения, В исполнении, объединив свойства наследования и композиции в новую концепцию.[нужна цитата ]

В соответствии с Аллен Голуб, основная проблема с наследованием реализации заключается в том, что оно вводит ненужные связь в виде "проблема хрупкого базового класса":[5] модификации реализации базового класса могут вызвать непреднамеренные поведенческие изменения в подклассах. Использование интерфейсов позволяет избежать этой проблемы, потому что нет общей реализации, только API.[16] Другой способ заявить об этом - "нарушение наследования инкапсуляция ".[17] Проблема явно проявляется в открытых объектно-ориентированных системах, таких как рамки, где ожидается, что клиентский код будет наследовать от классов, предоставляемых системой, а затем заменять классы системы в своих алгоритмах.[5]

Как сообщается, изобретатель Java Джеймс Гослинг высказался против наследования реализации, заявив, что не включил бы его, если бы он перепроектировал Java.[16] Языковые конструкции, которые отделяют наследование от подтипов (наследование интерфейса), появились еще в 1990 году;[18] современный пример этого - Идти язык программирования.

Сложное наследование или наследование, используемое в недостаточно зрелом дизайне, может привести к йо-йо проблема. Когда в конце 1990-х годов наследование использовалось в качестве основного подхода к структурированию кода в системе, разработчики, естественно, начали разбивать код на несколько уровней наследования по мере роста функциональности системы. Если команда разработчиков объединила несколько уровней наследования с принципом единой ответственности, она создала множество супертонких слоев кода, многие из которых имели бы только 1 или 2 строки кода на каждом уровне. До того, как команды на собственном горьком опыте узнали, что 2 или 3 уровня являются оптимальным числом уровней, уравновешивающим преимущество повторного использования кода с увеличением сложности с каждым уровнем, не было необычным работать над структурами наследования с 10 и до 30 уровней. Например, 30 уровней сделали отладку серьезной проблемой, просто чтобы знать, какой уровень нужно отлаживать. PowerBuilder создал одну из лучших библиотек кода, которая в основном использовала наследование, она была построена с 3-4 уровнями. Количество слоев в библиотеке наследования имеет решающее значение и должно составлять не более 4 слоев, иначе библиотека станет слишком сложной и требует много времени для использования.

Другая проблема с наследованием заключается в том, что подклассы должны быть определены в коде, что означает, что пользователи программы не могут добавлять новые подклассы. Другие шаблоны проектирования (например, Сущность – компонент – система ) позволяют пользователям программы определять варианты объекта во время выполнения.

Смотрите также

Примечания

  1. ^ Обычно это справедливо только для объектно-ориентированных языков со статически типизированными классами, таких как C ++, C #, Ява, и Scala.

Рекомендации

  1. ^ Джонсон, Ральф (26 августа 1991 г.). «Разработка многоразовых классов» (PDF). www.cse.msu.edu.
  2. ^ Майк Минц, Роберт Экендал (2006). Проверка оборудования с помощью C ++: Справочник практикующего специалиста. Соединенные Штаты Америки: Спрингер. п. 22. ISBN  978-0-387-25543-9.
  3. ^ Кук, Уильям Р.; Хилл, Уолтер; Каннинг, Питер С. (1990). Наследование - это не подтип. Proc. 17-я конференция ACM SIGPLAN-SIGACT. по принципам языков программирования (POPL). С. 125–135. CiteSeerX  10.1.1.102.8635. Дои:10.1145/96709.96721. ISBN  0-89791-343-4.
  4. ^ Карделли, Лука (1993). Типичное программирование (Технический отчет). Корпорация цифрового оборудования. п. 32–33. Отчет об исследовании SRC 45.
  5. ^ а б c Михайлов, Леонид; Секерински, Эмиль (1998). Исследование проблемы хрупкого базового класса (PDF). Proc. 12-я Европейская конф. по объектно-ориентированному программированию (ECOOP). Конспект лекций по информатике. 1445. С. 355–382. Дои:10.1007 / BFb0054099. ISBN  978-3-540-64737-9.
  6. ^ Темперо, Эван; Ян, Хун Юл; Благородный, Джеймс (2013). Что программисты делают с наследованием в Java (PDF). ECOOP 2013 – объектно-ориентированное программирование. С. 577–601.
  7. ^ «Наследование C ++». www.cs.nmsu.edu.
  8. ^ Бьярне Страуструп. Дизайн и эволюция C ++. п. 417.
  9. ^ Герберт Шильдт (2003). Полный справочник C ++. Tata McGrawhill Education Private Limited. п.417. ISBN  978-0-07-053246-5.
  10. ^ Э Балагурусамы (2010). Объектно-ориентированное программирование на C ++. Tata McGrawhill Education Pvt. ООО п. 213. ISBN  978-0-07-066907-9.
  11. ^ переопределить (Справочник по C #)
  12. ^ "GotW # 60: Дизайн класса, безопасного для исключений, часть 2: Наследование". Gotw.ca. Получено 2012-08-15.
  13. ^ Д-р К. Р. Венугопал, Раджкумар Буйя (2013). Освоение C ++. Tata McGrawhill Education Private Limited. п. 609. ISBN  9781259029943.
  14. ^ Cook, Hill & Canning 1990.
  15. ^ Митчелл, Джон (2002). «10» понятий в объектно-ориентированных языках"". Понятия на языке программирования. Кембридж, Великобритания: Издательство Кембриджского университета. п.287. ISBN  978-0-521-78098-8.
  16. ^ а б c Голуб, Аллен (1 августа 2003 г.). «Почему простирается зло». Получено 10 марта 2015.
  17. ^ Seiter, Linda M .; Палсберг, Йенс; Либерхерр, Карл Дж. (1996). «Эволюция поведения объекта с помощью контекстных отношений». Примечания по разработке программного обеспечения ACM SIGSOFT. 21 (6): 46. CiteSeerX  10.1.1.36.5053. Дои:10.1145/250707.239108.
  18. ^ Америка, Пьер (1991). Разработка объектно-ориентированного языка программирования с поведенческими подтипами. Школа REX / Мастерская по основам объектно-ориентированных языков. Конспект лекций по информатике. 489. С. 60–90. Дои:10.1007 / BFb0019440. ISBN  978-3-540-53931-5.

дальнейшее чтение