Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

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

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

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

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

Типы [ править ]

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

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

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

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

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

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

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

-  Бьярне Страуструп
Многоуровневое наследование

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

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

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

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

Класс  А (...);  // Базовый класс Class  B  :  public  A (...);  // B производный от A Class  C  :  public  B (...);  // C получено из B

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

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

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

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

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

Подклассы и суперклассы [ править ]

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

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

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

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

Неклассифицируемые классы [ править ]

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

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

Непереопределяемые методы [ править ]

Так же, как классы могут быть неподклассифицированными, объявления методов могут содержать модификаторы методов, которые предотвращают переопределение метода (т. Е. Замену новой функцией с тем же именем и сигнатурой типа в подклассе). Частный метод не-переопределение просто потому , что она не доступна другим , чем класс это является функцией членом классов (это не относится к C ++ , хотя). finalМетод в Java , A sealedметод C # или frozenфункцию в Eiffel не может быть переопределен.

Виртуальные методы [ править ]

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

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

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

Приложения [ править ]

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

Переопределение [ править ]

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

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

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

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

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

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

класс  SumComputer :  def  __init__ ( self ,  a ,  b ):  self . = а сам . б = б      def  transform ( self ,  x ):  поднять  NotImplementedError def  input ( self ):  диапазон возврата  ( self . a , self . b )  def  compute ( self ):  вернуть  сумму ( self . transform ( value )  для  значения  в  self . inputs ())class  SquareSumComputer ( SumComputer ):  def  transform ( self ,  x ):  return  x  *  xclass  CubeSumComputer ( SumComputer ):  def  transform ( self ,  x ):  return  x  *  x  *  x

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

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

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

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

class  A  {  public :  void  DoSomethingALike ()  const  {} };класс  B  :  общественность  A  {  public :  void  DoSomethingBLike ()  const  {} };void  UseAnA ( const  A &  a )  {  a . DoSomethingALike (); }void  SomeFunc ()  {  B  b ;  UseAnA ( b );  // b можно заменить на A. }

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

Ограничения дизайна [ править ]

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

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

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

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

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

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

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

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

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

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

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

См. Также [ править ]

  • Образец архетипа
  • Задача круг – эллипс
  • Разрешаемое рассуждение  - рассуждение, которое является рационально убедительным, но не дедуктивно валидным.
  • Интерфейс (вычисления)  - Понятие информатики; точка взаимодействия между двумя вещами
  • Переопределение метода
  • Mixin
  • Полиморфизм (информатика)
  • Протокол
  • Ролевое программирование  - парадигма программирования, основанная на концептуальном понимании объектов.
  • Третий манифест
  • Черта (компьютерное программирование)
  • Виртуальное наследование

Заметки [ править ]

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

Ссылки [ править ]

  1. Джонсон, Ральф (26 августа 1991 г.). «Проектирование многоразовых классов» (PDF) . www.cse.msu.edu .
  2. ^ Майк Минц, Роберт Экендал (2006). Проверка оборудования с помощью C ++: Справочник практикующего специалиста . Соединенные Штаты Америки: Спрингер. п. 22. ISBN 978-0-387-25543-9.
  3. ^ Кук, Уильям R .; Хилл, Уолтер; Каннинг, Питер С. (1990). Наследование - это не подтип . Proc. 17-я конференция ACM SIGPLAN-SIGACT. по принципам языков программирования (POPL). С. 125–135. CiteSeerX 10.1.1.102.8635 . DOI : 10.1145 / 96709.96721 . ISBN  0-89791-343-4.
  4. ^ Карделли, Лука (1993). Типовое программирование (Технический отчет). Корпорация цифрового оборудования . п. 32–33. Отчет об исследовании SRC 45.
  5. ^ a b c Михайлов, Леонид; Секерински, Эмиль (1998). Исследование проблемы хрупкого базового класса (PDF) . Proc. 12-я Европейская конф. по объектно-ориентированному программированию (ECOOP). Конспект лекций по информатике. 1445 . С. 355–382. DOI : 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. ^ E Balagurusamy (2010). Объектно-ориентированное программирование на C ++ . Tata McGrawhill Education Pvt. ООО п. 213. ISBN 978-0-07-066907-9.
  11. ^ переопределить (Справочник по C #)
  12. ^ "GotW # 60: Дизайн классов, безопасных для исключений, Часть 2: Наследование" . Gotw.ca . Проверено 15 августа 2012 .
  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. ^ a b c Голуб, Аллен (1 августа 2003 г.). «Почему простирается - зло» . Проверено 10 марта 2015 года .
  17. ^ Seiter, Linda M .; Палсберг, Йенс; Либерхерр, Карл Дж. (1996). «Эволюция поведения объекта с помощью контекстных отношений» . Примечания по разработке программного обеспечения ACM SIGSOFT . 21 (6): 46. CiteSeerX 10.1.1.36.5053 . DOI : 10.1145 / 250707.239108 . 
  18. ^ Америка, Пьер (1991). Разработка объектно-ориентированного языка программирования с поведенческими подтипами . Школа / семинар REX по основам объектно-ориентированных языков. Конспект лекций по информатике. 489 . С. 60–90. DOI : 10.1007 / BFb0019440 . ISBN 978-3-540-53931-5.

Дальнейшее чтение [ править ]

  • Построение объектно-ориентированного программного обеспечения , второе издание, Бертран Мейер , Prentice Hall, 1997, ISBN 0-13-629155-4 , глава 24: Использование наследования. 
  • Наследование реализации - зло