Что выполняет иерархия классов

Иерархия классов

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

Отношения, установленные в области объектно-ориентированного проектирования и стандартах интерфейса объектов, определяются наиболее распространенным использованием, создателями языков (Java, C++, Smalltalk, Visual Prolog) и комитетами по стандартизации, как например, Object Management Group.

Смотреть что такое «Иерархия классов» в других словарях:

Иерархия — У этого термина существуют и другие значения, см. Gerarchia. Иерархия (от др. греч. ἱεραρχία, из ἱερός «священный» и ἀρχή «правление») порядок подчинённости низших звеньев высшим, организация их в структуру типа дерево; принцип управления в … Википедия

Иерархия (духовная) — Великая цепь бытия, 1579 год Духовная Иерархия (от др. греч. ἱεραρχία, из … Википедия

Иерархия компьютерной памяти — концепция построения взаимосвязи классов разных уровней компьютерной памяти на основании иерархической структуры. Иерархия оперативной памяти реализуемая в вычислительной системе на базе процессора … Википедия

ИЕРАРХИЯ — классификация тех или иных математич. объектов в соответствии с их сложностью. Первые И. были построены в дескриптивной теории множеств (см. [3]). В этих И. переход к более сложному классу множеств осуществляется путем применения теоретико… … Математическая энциклопедия

ИЕРАРХИЯ — (hierarchy, от гр. hierarchia от hieros священный, arche власть) строго определенная система подчинения нижестоящих органов и должностных лиц вышестоящим, последовательное расположение чинов и званий в порядке следования от низших к высшим, так… … Власть. Политика. Государственная служба. Словарь

Полиномиальная иерархия — В теории сложности полиномиальная иерархия это иерархия классов сложности которая обобщает классы P, NP, co NP до вычислений с оракулом. Определение Существует множество эквивалентных определений классов полиномиальной иерархии. Приведём… … Википедия

Духовная иерархия — Великая цепь бытия, 1579 год Духовная Иерархия (от др. греч … Википедия

Класс сложности — В теории алгоритмов классами сложности называются множества вычислительных задач, примерно одинаковых по сложности вычисления. Говоря более узко, классы сложности это множества предикатов (функций, получающих на вход слово и возвращающих ответ 0… … Википедия

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

ООАП — Объектно ориентированное программирование (ООП) парадигма программирования, в которой основными концепциями являются понятия объектов и классов (либо, в менее известном варианте языков с прототипированием прототипов). Класс это тип, описывающий… … Википедия

Источник

17.1. Определение иерархии классов

17.1. Определение иерархии классов

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

OrQuery // Shakespeare || Marlowe

AndQuery // William && Shakespeare

В каждом классе определим функцию-член eval(), которая выполняет соответствующую операцию. К примеру, для NameQuery она возвращает вектор позиций, содержащий координаты (номера строки и колонки) начала каждого вхождения слова (см. раздел 6.8); для OrQuery строит объединение векторов позиций обоих своих операндов и т.д.

Таким образом, запрос

состоит из объекта класса OrQuery, который содержит два объекта NameQuery в качестве операндов. Для простых запросов этого достаточно, но при обработке составных запросов типа

Alice || Emma && Weeks

возникает проблема. Данный запрос состоит из двух подзапросов: объекта OrQuery, содержащего объекты NameQuery для представления слов Alice и Emma, и объекта AndQuery. Правым операндом AndQuery является объект NameQuery для слова Weeks.

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

Решение, не согласующееся с объектной ориентированностью, состоит в том, чтобы определить тип операнда как объединение и включить дискриминант, показывающий текущий тип операнда:

// не объектно-ориентированное решение

// объединение не может содержать объекты классов с

Not_query=1, O_query, And_query, Name_query

* opTypes хранит информацию о фактических типах операндов запроса

opTypes _lop_type, _rop_type;

Хранить указатели на объекты можно и с помощью типа void*:

opTypes _lop_type, _rop_type;

Нам все равно нужен дискриминант, поскольку напрямую использовать объект, адресуемый указателем типа void*, нельзя, равно как невозможно определить тип такого объекта по указателю. (Мы не рекомендуем применять описанное решение в C++, хотя в языке C это весьма распространенный подход.)

Основной недостаток рассмотренных решений состоит в том, что ответственность за определение типа возлагается на программиста. Например, в случае решения, основанного на void*-указателях, операцию eval() для объекта AndQuery можно реализовать так:

// не объектно-ориентированный подход

// ответственность за разрешение типа ложится на программиста

// определить фактический тип левого операнда

AndQuery *paq = static_castAndQuery*(_lop);

OrQuery *pqq = static_castOrQuery*(_lop);

NotQuery *pnotq = static_castNotQuery*(_lop);

AndQuery *pnmq = static_castNameQuery*(_lop);

// то же для правого операнда

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

Объектно-ориентированное программирование предлагает альтернативное решение, в котором работа по разрешению типов перекладывается с программиста на компилятор. Например, так выглядит код операции eval() для класса AndQuery в случае применения объектно-ориентированного подхода (eval() объявлена виртуальной):

// ответственность за разрешение типов перекладывается на компилятор

// их определения будут приведены ниже

Если потребуется добавить или исключить какие-либо типы, эту часть программы не придется ни переписывать, ни перекомпилировать.

17.1.1. Объектно-ориентированное проектирование

Из чего складывается объектно-ориентированное проектирование четырех рассмотренных выше видов запросов? Как решаются проблемы их внутреннего представления?

Основное преимущество иерархии наследования в том, что мы программируем открытый интерфейс абстрактного базового класса, а не отдельных производных от него специализированных типов, что позволяет защитить наш код от последующих изменений иерархии. Например, мы определяем eval() как открытую виртуальную функцию абстрактного базового класса Query. Пользовательский код, записанный в виде:

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

Когда мы говорим о полиморфизме в языке C++, то имеем в виду главным образом способность указателя или ссылки на базовый класс адресовать любой из производных от него. Если определить обычную функцию eval() следующим образом:

// pquery может адресовать любой из классов, производных от Query

void eval( const Query *pquery )

то мы вправе вызывать ее, передавая адрес объекта любого из четырех типов запросов:

OrQuery *oq = new OrQuery;

NameQuery nq( «Botticelli» );

// правильно: любой производный от Query класс

// компилятор автоматически преобразует в базовый класс

Источник

Что выполняет иерархия классов

Имя класса shape можно использовать точно так же, как и имя обычного типа данных в Cи. Например, можно сделать следующее объявле- ние для массивов:

Инкапсуляция или скрытие данных

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

Borland C++ предоставляет программистам три уровня доступа к элементам объектов:

Элементы, объявленные общими, будут доступны любому внешнему элементу класса, любой функции элементу или выражению в программе, когда объект является видимым.

Приватные элементы доступны только другим элементам своего же класса. Они не доступны извне, за исключением специальных функций, называемых «дружественными».

К защищенным элементам имеют доступ лишь некоторые из объектов. Они доступны только элементам своего класса и любым его потомкам. Поэтому защищенные элементы занимают промежуточное положение между общими и приватными.

Примечание: приватные элементы недоступны потомкам своего класса. Поэтому и понадобились защищенные элементы.

Уровень доступности к элементам класса проиллюстрирован ниже:

Скрытие данных в потомках

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

Замечание. Если вы хотите использовать в потомке класса элементы базового класса, то они должны быть объявлены как защищенные или общие. Доступ к приватным элементам базового класса невозможен никак иначе, как из их приватных функций элементов или с помощью дружественных функций, объявленных как friend.

В приведенной ниже таблице показана доступность элементов базового класса для его потомка (в зависимости от определенного типа доступа в базовом классе и потомке):

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

Наследование

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

Иерархия классов

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

Используя наследование, можно наращивать иерархию классов, создавая как-бы древовидные формы. Еще более сложные иерархии образуются через множественное наследование.

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

Под множественным наследованием понимается способность наследования свойств и поведения не от одного базового класса, а от нескольких. Такое наследование легко проследить по классификации языков программирования. У каждого из языков, как правило несколько предков. Так язык программирования С++ является потомком Си и Симулы, язык Ада потомком целого спектра языков и так далее.

Примечание: по установившейся практике стрелки показывают от порожденного класса на базовый.

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

Выгода от наследования

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

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

Расширяемость кода

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

Для дополнения класса shape (фигура) классом circle (круг), достаточно лишь объявления его в классе shape (без изменения функций элементов класса) в модуле shape.h и скомпилировать исходный текст в shape.obj. Таким образом нет необходимости изменять исходный текст shape.c.

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

Возможно вы заметили по картинке, что класс цилиндр имеет три функции area(). Действительно, унаследована одна функция area() у окружности, и одна у фигуры. Заимствование этих двух функций, для цилиндра оставляет возможность доступа к ним.

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

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

Пример наследования

Чтобы задать отношения наследования между классами, надо при описании нового класса после имени класса поставить двоеточие и далее перечислить через запятую имена потомков.
В этом примере из базового класса shape порождается класс circle:

Класс Shape

Конструктор Shape

Виртуальная функция элемент поверхности Shape

Поверхность фигуры shape в качестве исходного значения имеет 0. Порожденные классы вероятно подменят эту функцию.

Класс Circle

Конструктор circle

Класс Cylinder

Ниже показано как можно произвести класс cylinder из circle:

Конструктор cylinder

Функция vol() вычисляет объем цилиндра vol = pi*rЩh. Обратите внимание на изменения функции area() по сравнению с той, что была у окружности.

Максимальное заимствование!!

Класс Rectangle

Конструктор rectangle

Класс Box3d

Конструктор Box3d

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

Сейчас мы на самом деле достигли наследования текста программы!

Странные вызовы конструктора

Несколько слов о проектировании иерархии классов

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

Умелое использование наследования позволяет с небольшими усилиями модифицировать огромные по объему программы. К тому же необходимо помнить, что постоянно растущее число поставщиков предоставляют пользователям объектно-ориентированные библиотеки, совместимые с Турбо и Borland С++. Так что не следует «высасывать их из пальцев».

Обзор и важные концепции

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

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

Полиморфизм

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

Связывание функций

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

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

Так в библиотеке Turbo Vision любой объект, порожденный от TView, должен быть способен к самоизображению в любой момент времени. Объект TView определяет виртуальную функцию элемент draw, и каждый порожденный от него объект должен также иметь эту функцию элемент. Это имеет большое значение, т.к. часто отображаемый объект может быть закрыт другим отображаемым объектом (или на него может быть наложен другой отображаемый объект) и когда другой отображаемый объект убирается или смещается, то отображаемый объект должен быть способен к изображению своей части, которая была скрыта. Рисунок: программа, написанная с помощью библиотеки Turbo Vision с множеством открытых окон.

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

Так как меню окно перерисовывается иначе, чем диалоговая панель или окно, то каждый объект в отображаемой иерархии должен знать, как перерисовать себя. В библиотеке Turbo Vision этим занимается функция элемент draw, имеющийся в каждом объекте-потомке класса TView. Следовательно, если требуется перерисовать объект, то программе не нужно анализировать, к какому типу отображаемого объекта он относится (как это требовалось бы при раннем связывании). Она просто вызывает функцию элемент данного объекта draw. Функция исполняется и корректно перерисовывает свой объект на экране. Такая множественность действий, которую может выполнять функция элемент с одним и тем же именем, называется полиморфизмом.

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

Ранее связывание с помощью переопределяемых функций

Действие механизма переопределяемых функций несколько сходно с механизмом функции переключателя switch, используемого в Паскале. Однако, если switch, в зависимости от значения, выполняет тот или иной блок процедур, то переопределяемые функции, в зависимости от заданных аргументов вызывают и выполняют, тот или иной код, используя одно и то же имя функции. Дело в том, что компиляторы Турбо и Borland С++, различают функции не только по их именам, но и по типу их аргументов.

Достичь полиморфизма через «игру» с аргументами можно: потерей одного из аргументов (осторожно, так чтобы компилятор не спутал отсутствие аргумента с особой «потерей» аргументов, принимаемыми «по умолчанию», описываемыми далее), изменить тип аргумента, или использовать различное число аргументов. Например, функцию, вычисляющую площадь прямоугольника можно дополнить одноименной, но вычисляющей площадь квадрата, имеющую отличный тип у аргумента x и меньшее число параметров:

Компилятор предварительно просматривает содержащиеся в функции типы и по отличию в типе аргумента определяет ту или иную функцию.

Однако нужно иметь в виду, что компилятор не отличит эти же функции, если тип аргумента сделать одинаковым, а изменить лишь тип возвращаемого значения, например:

Переопределение операций

Поскольку в С++ операции, рассматриваются как функции, то очевидно, что и их можно переопределять так, что они будут работать не только с числами, а даже с графическими объектами, строками и вообще с чем угодно. В С++ число операций ограничено стандартным набором:

Чтобы определить операцию, необходимо определить функцию, именем которой является специальное ключевое слово oрerator, за которым сле- дует символ операции.

Тип операнда определяет какую из переопределенных операций необ- ходимо использовать.

Источник

Иерархия классов

14.1 Иерархия

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

Наследование применяется для следующих взаимосвязанных целей:

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

Кроме механизма наследования в данном разделе мы рассмотрим такие важные понятия ООП как полиморфизм и инкапсуляцию (см. «Технология объектно-ориентированного программирования» ), которые также принимают участие в формировании иерархии классов.

Наследование

Класс в С# может иметь произвольное количество потомков и только одного предка. При описании класса имя его предка записывается в заголовке класса после двоеточия. Если имя предка не указано, предком считается базовый класс всей иерархии System.Object. Синтаксис наследования:

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

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

Рассмотрим наследование классов на примере геометрических фигур на плоскости. В качестве базового класса создадим класс DemoPoint (точка на плоскости), в качестве производного класса от DemoPoint класс DemoLine (отрезок на плоскости):

Что выполняет иерархия классов. Смотреть фото Что выполняет иерархия классов. Смотреть картинку Что выполняет иерархия классов. Картинка про Что выполняет иерархия классов. Фото Что выполняет иерархия классов

Использование защищенного доступа

Обратите внимание на то, что доступ к полям х и y из класса Program невозможен, а из производного класса DemoLine возможен.

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

В предыдущем примере классы создавались за счет автоматического вызова средствами С# конструктора по умолчанию. Добавим конструктор только в производный класс DemoLine :

В данном случае конструктор определяется только в производном классе, поэтому часть объекта, соответствующая базовому классу, создается автоматически с помощью конструктора по умолчанию, а часть объекта, соответствующая производному классу, создается собственным конструктором.

Производный класс может вызывать конструктор, определенный в его базовом классе, используя расширенную форму объявления конструктора и ключевое слово base. Формат расширенного объявления:

В общем случае с помощью ключевого слова base можно вызвать конструктор любой формы, определенный в базовом классе. Реально же выполнится тот конструктор, параметры которого будут соответствовать переданным при вызове аргументам. Например:

Здесь в качестве элемента член_класса можно указывать либо метод, либо поле экземпляра. Эта форма ссылки base наиболее применима в тех случаях, когда имя члена в производном классе скрывает член с таким же именем в базовом классе.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *