Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
та глава начинается с введения в объектно-ориентированное программирование.
Затем обсуждаются основные вопросы разработки программ, касающиеся на-
следования и динамического связывания. Далее приводится обзор языка Smalltalk и де-
тальное описание подмножества этого языка, которое иллюстрируется двумя полными
программами на языке Smalltalk. После этого дается краткое описание поддержки объ-
ектно-ориентированного программирования в языках C++, Java, Ada 95 и Eiffel.
11.1. Введение
Языки, поддерживающие объектно-ориентированное программирование, в настоящее
время занимают прочное положение среди основных тенденций программирования. На-
чиная с языка COBOL и заканчивая языком LISP, практически для каждого языка были
разработаны объектно-ориентированные диалекты. К ним относятся языки C++, Ada 95 и
CLOS, а также объектно-ориентированная версия языка LISP (Bobrow et al., 1988). Языки
C++ и Ada 95, кроме объектно-ориентированного программирования, поддерживают
процедурно-ориентированное и информационно-ориентированное программирование.
Язык CLOS поддерживает также функциональное программирование.
Некоторые из современных языков, разработанных для объектно-ориентированного
программирования, не поддерживают другие парадигмы программирования, но продол-
жают использовать некоторые основные структуры прежних императивных языков и
внешне на них похожи. К таким языкам относятся Eiffel и Java. Кроме того, существует
один полностью объектно-ориентированный язык, являющийся совершенно нетрадици-
онным, Smalltalk. Язык Smalltalk был первым языком, предназначенным для полной
поддержки объектно-ориентированного программирования. Конкретный способ под-
держки объектно-ориентированного программирования в разных языках обсуждается в
этой главе.
Данная глава является продолжением главы 10, поскольку объектно-ориентированное
программирование это, по сути, приложение принципа абстракции к абстрактным ти-
пам данных. Так, в объектно-ориентированном программировании часть, общая для на-
бора похожих абстрактных типов данных, выделяется в новый тип. Члены набора насле-
дуют общие части от этого нового типа. Это называется наследованием, являющимся
сердцевиной объектно-ориентированного программирования и языков, которые его под-
держивают.
11.2. Объектно-ориентированное программирование
11.2.1. Введение
Концепция объектно-ориентированного программирования (object-oriented
programming) уходит корнями в язык SIMULA 67, но она не была полностью разработа-
на, пока эволюция языка Smalltalk не привела к появлению языка Smalltalk 80 (в 1980 го-
ду, конечно). Действительно, некоторые исследователи рассматривают язык Smalltalk в
качестве единственного полностью объектно-ориентированного языка программирова-
ния. Объектно-ориентированный язык должен обеспечивать поддержку трех ключевых
языковых свойств: абстрактные типы данных, наследование и какой-либо частный вид
динамического связывания.
Процедурно-ориентированное программирование, которое было наиболее популяр-
ной парадигмой разработки программного обеспечения в 1970-х годах, фокусировалось
на подпрограммах и библиотеках подпрограмм. Данные передавались подпрограммам
для вычислений. Например, массив целых чисел, подлежавший упорядочению, переда-
вался в качестве параметра подпрограмме, сортирующей такие массивы.
Информационно-ориентированное программирование сосредоточивает внимание на
абстрактных типах данных, детально рассмотренных в главе 10. В этой парадигме вы-
числение, которое требуется выполнить для объекта, содержащего данные, определяется
вызовом подпрограмм, связанных с этим объектом. Если объект представляет собой
массив, подлежащий упорядочению, операция сортировки определяется в абстрактном
типе данных для массива. Процесс сортировки активизируется путем вызова этой опера-
ции для конкретного объекта, представляющего собой массив. Парадигма информацион-
но-ориентированного программирования была популярной в 1980-х годах и хорошо об-
служивалась средствами абстракции данных в языках Modula-2, Ada и некоторыми со-
временными языками. Языки, поддерживающие информационно-ориентированное
программирование, часто называются объектными (object-based) языками.
11.2.2. Наследование
Во второй половине 1980-х годов для многих разработчиков программного обеспече-
ния стало очевидным, что одной из наилучших возможностей для повышения произво-
дительности их труда является повторное использование программ. Вполне очевидно,
что абстрактные типы данных с их инкапсуляцией и управлением доступом должны ис-
пользоваться многократно. Проблема, связанная с повторным использованием абстракт-
ных типов данных, почти во всех случаях заключается в том, что свойства и возможно-
сти существующих типов не вполне подходят для нового использования. Старые типы
необходимо, по крайней мере минимально, модифицировать. Такие модификации могут
быть трудновыполнимыми и требовать от человека понимания части, если не всего це-
ликом, существующего кода. Кроме того, во многих случаях модификации влекут за со-
бой изменения во всех программах-клиентах.
Вторая проблема, связанная с программированием, ориентированным на данные, за-
ключается в том, что все определения абстрактных типов данных являются независимы-
ми и находятся на одном и том же уровне иерархии. Это часто не позволяет так структу-
рировать программу, чтобы она соответствовала своей проблемной области. Во многих
случаях исходная задача содержит категории связанных между собой объектов, являю-
щихся как наследниками одних и тех же предков (т.е. находящихся на одном и том же
уровне иерархии), так и предками и наследниками (т.е. состоящих в отношении некото-
рой субординации друг с другом).
Наследование позволяет решить как проблемы модификации, возникающие в резуль-
тате повторного использования абстрактного типа данных, так и проблемы организации
программ. Если новый абстрактный тип данных может наследовать данные и функцио-
нальные свойства некоторого существующего типа, а также модифицировать некоторые
из этих сущностей и добавлять новые сущности, то повторное использование значитель-
но облегчается без необходимости вносить изменения в повторно используемый абст-
рактный тип данных. Программисты могут брать существующий абстрактный тип дан-
ных и создавать по его образцу новый тип, соответствующий новым требованиям задачи.
Предположим, что в программе есть абстрактный тип данных для массивов целых чисел,
включающий в себя операцию сортировки. После некоторого периода использования
программа модифицируется и требует наличия не только абстрактного типа данных для
массивов целых чисел с операцией сортировки, но и операции вычисления арифметиче-
ского среднего для элементов объектов, представляющих собой массивы. Поскольку
структура массива скрыта в абстрактном типе данных, без наследования этот тип должен
быть модифицирован путем добавления новой операции в эту структуру. При наличии
наследования нет необходимости в модификации существующего типа; можно описать
подкласс существующего типа, поддерживающий не только операцию сортировки, но и
операцию для вычисления среднего арифметического.
Абстрактные типы данных в объектно-ориентированных языках по примеру языка
SIMULA 67 обычно называются классами (classes). Как и экземпляры абстрактных ти-
пов данных, экземпляры классов называются объектами (objects). Класс, который опре-
деляется через наследование от другого класса, называется производным классом
(derived class), или подклассом (subclass). Класс, от которого производится новый класс,
называется родительским классом (parent class), или суперклассом (superclass). Под-
программы, определяющие операции над объектами класса, называются методами
(methods). Вызовы методов называются сообщениями (messages). Весь набор методов
объекта называется протоколом сообщений (message protocol), или интерфейсом со-
общений (message interface) объекта. Сообщение должно иметь, по крайней мере, две
части: конкретный объект, которому оно должно быть послано, и имя метода, опреде-
ляющего необходимое действие над объектом. Таким образом, вычисления в объектно-
ориентированной программе определяются сообщениями, передаваемыми от одного
объекта к другому.
В простейшем случае класс наследует все сущности (переменные и методы) роди-
тельского класса. Это наследование можно усложнить, введя управление доступом к
сущностям родительского класса. Например, в определениях абстрактных типов данных
(в главе 10) некоторые сущности классифицируются как открытые, а другие как за-
крытые. Это управление доступом позволяет программисту скрыть части абстрактного
типа данных от клиентов. Такое управление доступом обычно есть в классах объектно-
ориентированных языков. Производные классы представляют собой другой вид клиен-
тов, которым доступ может быть либо предоставлен, либо запрещен. Чтобы это учесть,
некоторые объектно-ориентированные языки включают в себя третью категорию управ-
ления доступом, часто называемую защищенной (protected), которая используется для
предоставления доступа производным классам и запрещения доступа другим классам.
В дополнение к наследуемым сущностям производный класс может добавлять новые
сущности и модифицировать методы. Модифицированный метод имеет то же самое имя
и часто тот же самый протокол, что и метод, модификацией которого он является. Гово-
рят, что новый метод замещает (override) наследуемую версию метода, который поэто-
му называется замещаемым (overriden) методом. Наиболее общее предназначение за-
мещающего метода выполнение операции, специфической для объектов производного
класса и не свойственной для объектов родительского класса. Например, рассмотрим ие-
рархию классов, в которой корневой класс описывает общие архитектурные характери-
стики французских готических соборов. Этот корневой класс French_Gotic имеет ме-
тод для рисования фасада обобщенного французского готического собора. Предполо-
жим, что у класса French_Gothic есть три производных класса Reims, Amien и
Chartres, каждый из которых имеет метод для рисования конкретного фасада. Эти
версии метода draw должны замещать метод draw из родительского класса.
Классы могут иметь два вида методов и два вида переменных. Наиболее широко ис-
пользуемые методы и переменные называются методами и переменными экземпляра
(instance methods and variables). Каждый объект класса имеет свой собственный набор
переменных объекта, описывающих его состояние. Единственное различие между двумя
объектами одного и того же класса заключается в состоянии их переменных объекта.
Методы объекта оперируют только объектами данного класса. Переменные класса
(class variavles) принадлежат классу, а не объекту, так что они имеют только по одной
копии в классе. Методы класса (class methods) могут выполнять операции над классом
и, возможно, над объектами класса. В этой главе мы будем, в основном, игнорировать
методы и переменные класса.
Если класс, созданный путем наследования, имеет один родительский класс, то этот
процесс называется одиночным наследованием (single inheritance). Если класс имеет
несколько родительских классов, то такой процесс называется множественным насле-
дованием (multiple inheritance). Если несколько классов связаны между собой одиноч-
ным наследованием, то их взаимоотношения можно изобразить с помощью дерева на-
следования (derivation tree). Взаимоотношения классов при множественном наследова-
нии можно изобразить с помощью графа наследования (derivation graph).
Разработка программы для объектно-ориентированной системы начинается с опреде-
ления иерархии классов, описывающей отношения между объектами, которые войдут в
программу, решающую поставленную задачу. Чем лучше эта иерархия классов соответ-
ствует проблемной части, тем более естественным будет полное решение.
Недостаток наследования как средства, облегчающего повторное использование кода,
заключается в том, что оно создает зависимость между классами в иерархии наследования.
Это умаляет одно из преимуществ абстрактных типов данных, заключающееся в их взаим
ной независимости. Конечно, не все абстрактные типы данных должны быть полностью независимыми, но в общем случае независимость абстрактных типов данных является одним из их самых сильных положительных свойств. Однако увеличение возможности повторного использования абстрактных типов данных без создания зависимостей между некоторыми из них может оказаться трудной задачей, если не совсем безнадежной.
11.2.3. Полиморфизм и динамическое связывание
Третьим свойством объектно-ориентированных языков программирования является
вид полиморфизма, обеспечиваемый динамическим связыванием сообщений с определе-
ниями методов. Это свойство поддерживается путем разрешения определения поли-
морфных переменных типа родительского класса, которые также могут ссылаться на
объекты любых подклассов данного класса. Родительский класс может определять ме-
тод, замещаемый в его подклассах. Операции, определяемые этими методами, похожи,
но должны уточняться для каждого класса в иерархии. Когда такой метод вызывается
через полиморфную переменную, этот вызов динамически связывается с методом в со-
ответствующем классе. Одна из целей динамического связывания обеспечить более
легкое расширение программных систем при их разработке и поддержке. Такие про-
граммы можно писать для операций над объектами настраиваемых классов. Эти опера-
ции являются настраиваемыми в том смысле, что их можно применять к объектам любо-
го класса, производного от одного и того же базового класса. Для иллюстрации динами-
ческого связывания рассмотрим пример с соборами. Если программа, использующая
класс French_Gothic, имеет полиморфную переменную cathedral типа класса French_Gothic, то эта переменная может ссылаться на объекты класса
French_Gothic, а также на объекты любых производных классов. Теперь, когда для
вызова метода draw (который определен в классе French_Gothic и во всех его по-
томках) используется переменная cathedral, этот вызов динамически связывается с
соответствующей версией метода draw, выбранной по типу, на который в данном случае
ссылается полиморфная переменная.
Динамическое связывание через полиморфные переменные мощная концепция.
Предположим, что наш пример с соборами написан на языке С. Три варианта француз-
ских готических соборов следует разместить в переменных типа struct. Может суще-
ствовать одна функция draw, использующая оператор switch для вызова правильной
функции рисования, соответствующей конкретному собору. Однако при такой реализа-
ции именно программист отвечает за вызов подходящей версии функции рисования.
В рамках объектно-ориентированного программирования сделать это намного легче.
Предположим, что нам нужно дорисовать новый собор, скажем Paris. Это вынудило
бы нас модифицировать конструкцию switch в общей функции рисования, а также в
каждой подобным же образом организованной функции рисования соборов. При объект-
но-ориентированном программировании, однако, добавление рисунка нового собора не
влияет на существующий код.
Во многих случаях разработка иерархии наследования приводит к одному или не-
скольким классам, находящимся на такой высокой ступени иерархии, что создание объ-
ектов подобных классов не имеет смысла. Предположим, что существовал класс
building в качестве родительского класса, или класса-предка, для класса
French_Gotic. Возможно, нет смысла реализовывать метод draw в классе building.
Однако, поскольку все классы-потомки должны имен, реализацию такого метода, прото-
кол (но не тело) этого метода включается в класс building. Этот абстрактный метод
часто называется виртуальным методом (virtual method). Кроме того, любой класс,
включающий в себя хотя бы один виртуальный метод, называется виртуальным клас-
сом (virtual class). Объекты такого класса не могут быть созданы, поскольку не все его
методы имеют тела. Любой подкласс виртуального метода, для которого следует созда-
вать объекты, должен обеспечивать реализации всех наследуемых виртуальных методов.
11.2.4. Вычисления в объектно-ориентированных языках
Все вычисления в полностью объектно-ориентированном языке выполняются с по-
мощью передачи сообщения объекту для вызова ОДНОГО ИЗ ею методов. Ответом на со-
общение является объект, возвращающий результат вычислений, выполненных этим ме-
тодом. Выполнение программы на объектно-ориентированном языке можно описать как
моделирование набора компьютеров (объектов), взаимодействующих друг с другом с
помощью обмена сообщениями. Каждый объект абстракция компьютера в том смыс-
ле, что он хранит данные и обеспечивает выполнение процессов для манипуляции этими
данными. Кроме того, объекты могут передавать и получать сообщения. В сущности, это
основные свойства компьютера хранить и обрабатывать данные, а также передавать и
получать сообщения.
Суть объектно-ориентированного программирования состоит в решении задач с по-
мощью идентификации соответствующих реальных объектов и обработки, требуемой
для этих объектов; и последующем моделировании этих объектов, их процессов и необ-
ходимых связей между ними.
11.3. Вопросы разработки объектно-
ориентированных языков
При разработке свойств языков программирования, поддерживающих наследование и
динамическое связывание, нужно рассмотреть большое количество вопросов. В этом
разделе обсуждаются наиболее важные из них.
11.3.1. Исключительность объектов
Разработчик языка, полностью полагающийся на объектную модель вычислений,
обычно создает объектную систему, содержащую все другие концепции типа. В этом
сценарии все, от наименьшего целого числа до полной программной системы, представ-
ляет собой объект. Преимущество такого выбора заключается в элегантности и полном
единообразии языка и его использования. Основной недостаток состоит в том, что про-
стые операции должны выполняться с помощью передачи сообщений, которая часто де-
лает их более медленными, чем в императивной модели, в которой такие операции реа-
лизуются простыми и быстрыми машинными командами.
В этой наиболее чистой модели объектно-ориентированных вычислений все типы яв-
ляются классами. Нет никаких отличий между встроенными классами и классами, опре-
деленными пользователем. В действительности, все классы обрабатываются одинаково и
все вычисления выполняются путем передачи сообщений.
Существует два варианта, альтернативных исключительному использованию объек-
тов, один из них, являющийся общим для императивных языков, дополненных поддерж-
кой объектно-ориентированного программирования, сохранение императивной моде-
ли типов и простое добавление объектной модели. Это порождает более крупный по
размерам язык, структура типов которого приводит в замешательство всех пользовате-
лей, кроме экспертов.
Другой вариант создать императивную структуру для элементарных скалярных
типов. Это обеспечивает скорость операций с элементарными значениями, сравнимую с
ожидаемой скоростью в императивной модели. К сожалению, эта альтернатива также ве-
дет к усложнению языка. В любом случае необъектные значения нужно смешивать с
объектами. Для этого следует использовать так называемые интерфейсные классы
(wrapper classes) для необъектных типов, так что некоторые (обычно необходимые) опе-
рации могут быть посланы объектам со значениями необъектного типа. В разделе 11.6
мы рассмотрим пример этого явления в языке Java.
11.3.2. Являются ли подклассы подтипами
Обсуждаемый здесь вопрос довольно прост: "Сохраняется ли отношение "есть" меж-
ду родительским классом и производными классами?". Отношение "есть" гарантирует,
что переменная производного класса может появляться везде, где допустимо использо-
вание переменной типа родительского класса. Подтипы в языке Ada являются примера-
ми простой формы наследования. Например,
subtype SMALL_INT is INTEGER range -100..100;
Переменные типа SMALL_INT обладают всеми операциями переменных типа
INTEGER, но могут содержать только подмножество значений, возможных для переменных типа INTEGER. Кроме того, каждую переменную типа SMALL_INT можно использовать всюду, где используются переменные типа INTEGER. Следовательно, каждая переменная SMALL_INT является в некотором смысле переменной типа INTEGER.
Производный класс называется подтипом (subtype), если он состоит в отношении
"есть" со своим родительским классом. Характеристики подкласса, которые подтвер-
ждают, что он является подтипом, таковы: подкласс может только добавлять переменные
и методы, а также замещать наследуемые методы "совместимым" образом. Под совмес-
тимостью здесь понимается то, что замещающий метод может заменять замещаемый ме-
тод, не вызывая сообщений об ошибках несовместимости типов. Одинаковое количество
параметров, а также идентичные типы параметров и возвращаемых значений могли бы,
конечно, гарантировать согласованность. В зависимости от правил совместимости типов
в языке возможны менее строгие ограничения.
Наше определение подтипа явно запрещает иметь сущности в родительском классе,
которые не наследуются подклассом.
11.3.3. Реализация и наследование интерфейса
Концепция сокрытия информации в абстрактных типах данных предоставляет клиен-
там интерфейс доступа к своим возможностям, но скрывает их реализацию. Что еще сле-
дует сказать о подклассах? Достаточно ли им видеть только интерфейс своего родитель-
ского класса или они должны получить доступ к деталям реализации возможностей ро-
дительского класса? Если для подкласса видимым является только интерфейс, то это
называется наследованием интерфейса (interface inheritance). Если видимы также и де-
тали реализации это наследование реализации (implementation inheritance).
Такое наследование обеспечивает разработчика языка готовыми компонентами, дос-
тоинства и недостатки которых следует рассмотреть. Учет доступа подкласса к
"скрытой" части родительского класса делает подкласс зависимым от этих деталей. Лю-
бое изменение в реализации родительского класса потребует повторной компиляции
подкласса, и во многих случаях такое изменение приведет к модификации подкласса.
Это аннулирует преимущество сокрытия информации от клиентов подкласса. С другой
стороны, сокрытие реализации родительского класса от подклассов может привести к
неэффективному выполнению экземпляров таких подклассов. Причина этого может за-
ключаться в разной эффективности прямого доступа к структурам данных и доступа к
ним с помощью операторов, определенных в родительском классе. Например, рассмот-
рим класс, определяющий стек, и подкласс, который должен содержать операцию воз-
врата второго от вершины элемента. Если язык программирования использует наследо-
вание реализации, то эту операцию можно описать как простой возврат элемента, пози-
ция которого определяется вычитанием единицы из указателя на вершину стека. Однако,
если разработчик языка выбрал наследование интерфейса, этот код должен выглядеть
примерно так:
int second() {
int temp = top();
pop();
int temp_tesult = top();
push(temp);
return temp_result;
}
Очевидно, что этот процесс медленнее, чем прямой доступ ко второму от вершины эле-
менту стека. Однако, если реализация стека изменилась, этот метод, возможно, также по-
требуется изменить.
Наилучшее решение для разработчика языка предоставить разработчику про-
граммного обеспечения возможность использовать как наследование реализации, так и
наследование интерфейса, и позволить ему решить, ориентируясь по ситуации, какой из
вариантов предпочтительнее.
11.3.4. Проверка типов и полиморфизм
В разделе 11.2 полиморфизм в объектно-ориентированной сфере определен как ис-
пользование полиморфного указателя или ссылки для доступа к методу, имя которого
замещается в иерархии классов, определяющей объект, на который указывает этот указа-
тель или ссылка. Полиморфная переменная это тип родительского класса, а родитель-
ский класс, в свою очередь, определяет, как минимум, протокол метода, замещаемого в
производных классах. Полиморфная переменная может ссылаться на объекты родитель-
ского класса и производных классов, так что класс объекта, на который она ссылается,
всегда определяется статически. Сообщения, передаваемые с помощью полиморфных
переменных, должны связываться с методами динамически. Проблема здесь заключается
в определении момента, когда происходит проверка типов при этом связывании.
Этот вопрос очень важен, поскольку он сравним с вопросом о природе языка про-
граммирования. Если строгое типизирование является одной из основных целей разра-
ботки языка, как во многих современных языках программирования, эта проверка типов
должна выполняться статически, что налагает некоторые серьезные ограничения на от-
ношения между полиморфными сообщениями и методами.
Существуют два вида проверки типов, которую следует выполнять для сообщения и
метода в строго типизированном языке: типы параметров сообщения сравниваются с ти-
пами формальных параметров метода, а тип возвращаемого методом объекта с ожи-
даемым типом сообщения. Если эти типы должны в точности совпадать, то замещающий
метод должен иметь то же количество и те же типы параметров, а также тот же тип воз-
вращаемого значения, что и замещаемый метод. Единственным исключением из этого
правила может быть разрешение совместимости в отношении операции присваивания
между фактическими и формальными параметрами, а также между возвращаемым типом
метода и ожидаемым типом сообщения.
Очевидной альтернативой статической проверке типов является откладывание про-
верки до тех пор, пока полиморфная переменная не будет использована для вызова ме-
тода. Как и в других ситуациях, этот тип динамической проверки типов более дорог и от-
кладывает обнаружение ошибок несовместимости типов.
11.3.5. Одиночное и множественное наследование
Попытаемся ответить на вопрос: позволяет ли язык осуществлять множественное на-
следование (в дополнение в одиночному наследованию)? Цель множественного наследо-
вания позволить новому классу наследовать от нескольких классов, описывающих
разные абстракции. Например, на языке Java часто пишут аплеты, содержащие анима-
цию. Такая анимация часто протекает параллельно в различных частях аплета. Аплеты
поддерживаются классом Applet, а параллельность обеспечивается классом Thread.
Для такого аплета может оказаться необходимым наследование переменных и методов
от обоих классов. В разделе 11.10.2 мы обсудим, как решается эта проблема в языке Java.
Поскольку множественное наследование метода очень полезно, почему иногда разра-
ботчики не включают его в язык программирования? Причины этого разделяются на две
категории: сложность и эффективность. Дополнительная сложность порождается несколь
кими проблемами. Одна из них конфликт имен. Например, если подкласс С наследует от обоих классов А и В, а классы А и В содержат наследуемую переменную sum, каким образом класс С сможет ссылаться на две различные переменные с именем sum? Такая же ситуация возникает, если и класс А, и класс В являются производными от общего родительского класса Z. Этот случай называется бриллиантовым наследованием (diamond
inheritance). При этом классы А и В имеют наследуемые переменные из класса Z, и класс С наследует по две версии каждой из этих переменных (в предположении, что они являются наследуемыми) от классов А и В. Бриллиантовое наследование показано на рис. 11.1.
с
Рис. 11.1. Пример бриллиантового наследования
Вопрос эффективности является скорее теоретическим, чем практическим. В языке
C++, например, поддержка множественного наследования требует еще одной дополни-
тельной операции для каждого динамически связанного вызова метода.
Несмотря на то что эта операция выполняется даже, если программа не использует мно-
жественного наследования, такой подход можно назвать относительно недорогим.
Использование множественного наследования значительно усложняет организацию
программы. Применяя множественное наследование, трудно разработать классы, кото-
рые должны использоваться в качестве родительских. Поддержка систем, использующих
множественное наследование, может быть более серьезной проблемой, поскольку мно-
жественное наследование приводит к более сложным зависимостям между классами. Не-
которым людям не ясно, что выгоды от множественного наследования стоят дополни-
тельных усилий, затраченных на разработку и поддержку использующих его систем.
Т1.3.6. Размещение в памяти и удаление из памяти объектов
Существует два вопроса разработки программ, связанные с размещением объектов в
памяти и удалением из нее. Первый из них связан с местом, в котором размещаются объ-
екты. Если поведение объектов аналогично поведению абстрактных типов данных, то их
можно разместить где угодно. Это означает, что они могут быть либо статически разме-
щены компилятором, либо размещены как автоматические объекты в стеке времени вы-
полнения, либо созданы в динамической памяти с помощью такого оператора или функ-
ции, как new. Преимущество их размещения в динамической памяти состоит в единооб-
разии метода создания и доступа к ним через указатели или ссылки. Это упрощает
операцию присваивания для объектов, которая выполняется только с помощью изменения значения указателя или ссылки и позволяет неявно дифференцировать ссылки на
объекты, упрощая синтаксис доступа.
Вторая проблема связана с размещением объектов в динамической памяти. Вопрос
состоит в том, является ли удаление объекта из памяти неявным или явным, или и тем, и
другим. Если удаление объекта из памяти является неявным, требуется наличие такого
неявного метода восстановления памяти, как счетчик ссылок или сборка мусора. Если
размещение объекта в памяти может быть неявным, возникает вопрос, могут ли созда-
ваться висячие указатели или ссылки?
11.3.7. Динамическое и статическое связывание
Как мы уже обсуждали, динамическое связывание сообщений с методами в иерархии
наследования существенная часть объектно-ориентированного программирования.
Вопрос в следующем: являются ли все связывания сообщений с методами динамически-
ми. В качестве альтернативы можно позволить пользователю самому определять, быть
ли конкретному связыванию динамическим или статическим. Преимущество этого под-
хода состоит в том, что статическое связывание быстрее. Так что, если связывание не
обязано быть динамическим, зачем платить за это замедлением работы программ?