Методы построения классов
Одним из наиболее значимых достоинств ООП является то, что большинство классов для реализации объектов не приходится разрабатывать «с нуля». Обычно классы строят на базе уже существующих, используя механизмы, реализующие определенное отношение существующего и строящего классов между собой: наследование, композицию, агрегацию и полиморфное наследование.
Наследованием или обобщением называют отношение между классами, при котором один класс строится на базе второго посредством добавления полей и определения новых методов. При этом исходный класс, на базе которого выполняется построение, называют родительским, или базовым, или супертипом, а строящийся класс - потомком, или производным классом, или подтипом.
При наследовании поля и методы родительского класса повторно не определяют, специальный механизм наследования позволяет использовать эти компоненты класса, особо этого не оговаривая.
Примечание. В Borland Pascal реализовано только простое наследование, при котором класс может иметь всего одного родителя. В теории программирования определено также множественное наследование, предполагающее наличие у класса двух и более родителей. Такой вариант наследования реализован, например, в C++.
Наследование свойств в иерархии существенно упрощает работу программиста. В настоящее время созданы библиотеки наиболее часто встречающихся классов, которые можно использовать вновь и вновь, строя на их основе классы для решения различных задач.
Отношения между различными классами проекта принято иллюстрировать диаграммой отношений классов, или просто диаграммой классов. Если на диаграмме классов показано только отношение наследования, то такую диаграмму называют иерархией классов. На диаграмме классов наследование изображают линией с треугольной незакрашенной стрелкой на конце, направленном к классу-родителю (рис. 3, а, б). При необходимости допускается произвольное расположение классов родителей и потомков (рис. , в).
Кроме отношения между классами на диаграмме классов целесообразно указывать поля и методы каждого или только строящегося класса, так как это позволяет уточнить структуру разрабатываемых классов.
Композицией называют такое отношение между классами, когда один является неотъемлемой частью второго. Физически композиция реализуется
Рис. 3. Примеры иерархии классов:
а – с одним потомком; б – с двумя потомками; в – с нестандартным расположением классов
Рис. 4. Примеры диаграммы классов, изображающих композицию:
а – с одним объектным полем; б – с несколькими объектными полями различных типов
включением в класс фиксированного количества полей, являющихся объектами другого класса. Такие поля обычно называют объектными.
На диаграмме классов композицию изображают линией с закрашенным ромбом, указывающим на класс большей сложности, в который происходит включение объектных полей (рис. 4, а). Для большей информативности имеет смысл указывать над стрелкой количество объектов включаемого класса в каждый объект включающего класса. При этом допускается указывать точное значение или диапазон (рис. 4, б).
Наполнением или агрегацией называют такое отношение между классами, при котором точное количество объектов одного класса, включаемых в другой класс, не ограничено и может меняться от 0 до достаточно больших значений. Физически наполнение реализуется с использованием указателей на объекты. В отличие от объектного поля, которое включает в класс точно указанное количество объектов (1 или более - при использовании массива объектов или нескольких объектных полей) конкретного класса, использование указателей позволяет включить 0 или более объектов, если они собраны в массив или списковую (линейную или нелинейную) структуру.
На диаграмме классов наполнение изображают аналогично композиции, но ромб не закрашивают (рис. 5), обозначая таким образом менее жесткую связь между объектами соответствующих классов. Количество объектов указывают в виде диапазона, например, «0..*» или «1 ..*», или просто «*», что означает неопределенное множество объектов.
Рис. 5. Пример диаграммы классов, изображающей агрегацию или наполнение
Полиморфным наследованием называют наследование, при котором осуществляют переопределение методов класса-родителя потомком. Метод потомка в этом случае имеет то же имя, что и метод родителя, но выполняет другие действия.
Переопределение методов - частный случай реализации полиморфизма в программировании.
Примечание. Термин «полиморфизм» в соответствии со своим изначальным смыслом («многообразие») в программировании используют для обозначения возможности изменения кода программы в соответствии со значением некоторых параметров. Такая возможность существует не только в ООП.
Различают:
§ чистый полиморфизм - возможность различной интерпретации кода функции в зависимости от типа аргументов; используется в языках высокого уровня абстракции, например, в языке LISP или SMALLTALK;
§ перегрузку (полиморфные имена) функций - возможность определения нескольких функций с одним именем - одно и то же имя функции может многократно использоваться в разных местах программы; выбор нужной функции может определяться типами аргументов, областью видимости (внутри модуля, файла, класса и т.д.); если выбор определяется типом аргументов, то перегрузка называется параметрической; например, язык C++ позволяет разработчику выполнять параметрическую перегрузку функций вне классов, а в Borland Pascal возможно определение функций с одинаковыми именами в различных модулях или в модуле и основной программе;
§ переопределение методов - в ООП возможность различных определений методов в классе-потомке и классе-родителе; конкретный метод определяется классом объекта, для которого он вызывается;
§ обобщенные функции, или шаблоны - возможность описания параметризованных классов и шаблонов функций (реализована в C++), параметрами таких описаний являются типы аргументов методов или функций.
При переопределении методов различают простой и сложный полиморфизм.
Простой полиморфизм используют, если при вызове переопределенного метода тип объекта, для которого вызывается этот метод, точно известен, а, следовательно, и точно известно, какой метод должен быть подключен: метод родителя или метод потомка. В этом случае нужный метод определяется на этапе компиляции программы {раннее связывание).
Сложный полиморфизм используют, если при вызове переопределенного метода необходимо уточнить, какой метод должен быть подключен: метод родителя или метод потомка, так как объект, для которого вызывается переопределенный метод, может быть как объектом класса родителя, так и объектом класса потомка. В этом случае нужный метод определяется на этапе выполнения программы (позднее связывание), когда тип объекта точно известен.
Для выявления отношения имеющегося и строящегося классов необходимо выполнить анализ структуры объектов предметной области, полученных в результате объектной декомпозиции.
Если объекты предметной области слишком сложны, чтобы ставить им в соответствие некий простой класс, то процесс декомпозиции можно продолжить, выделяя внутри сложных объектов более простые.
При этом возможны следующие варианты.
1. Внутри объекта можно выделить объект близкого назначения, но с более простой структурой и/или поведением - класс для реализации данного объекта следует строить на базе более простого, используя наследование. Если при этом объекты строящегося класса отличаются от объектов базового класса некоторыми аспектами поведения, то следует изменить поведение объектов строящегося класса при наследовании, используя полиморфное наследование с переопределением методов.
2. Внутри объекта можно выделить определенное количество объектов более простой структуры со своим поведением - класс строится объединением объектов других классов с добавлением полей и методов строящегося класса - композиция классов.
3. Внутри объекта можно выделить заранее не предсказуемое количество объектов более простой структуры со своим поведением - класс строится с возможностью динамического подключения объектов других классов (наполнение) и добавлением полей и методов строящегося класса.
Рассмотрим два примера.
Пример 2. Разработать класс для реализации объекта «Текст», который должен:
§ для каждого слова некоторой последовательности слов хранить его атрибуты (тип шрифта и размер букв);
§ определять количество слов в тексте;
§ добавлять слова после слов с указанными номерами;
§ удалять заданные слова из текста;
§ менять местами слова с заданными номерами;
§ заменять заданное слово на другое заданное во всем тексте;
§ переопределять атрибуты слова с заданным номером.
Итак, реализуемый объект должен оперировать с некоторыми внутренними объектами «Словами», для которых можно определить собственное состояние и поведение. Естественно создать специальный класс TWord для реализации «Слов». Класс TText для реализации «Текста» может быть построен как с использованием композиции, так и с использованием наполнения.
В первом случае он должен включать массив объектов класса TWord. Максимальное количество элементов массива должно быть определено заранее и, следовательно, ограничено (рис. 6, а). При выполнении операций удаления и вставки придется сдвигать и раздвигать элементы массива.
Рис. 6. Диаграммы классов для реализации класса Текст:
а – с композицией; б – с наполнением
Во втором случае класс TText должен включать список объектов класса TWord (рис. 6, б). Ограничения предыдущей реализации будут сняты, но реализация со списком имеет несколько большую трудоемкость, и, кроме того, при обращении к слову по номеру придется каждый раз последовательно отсчитывать нужный элемент. Выбор конкретного варианта реализации зависит от условий решаемой задачи.
Пример 3. Разработать классы для реализации объектов Табулятор, Определитель корней и Определитель экстремумов из примера 1.
Объекты Табулятор, Определитель корней и Определитель экстремумов отвечают за реализацию методов решения некоторых частных задач при исследовании функций. Они имеют много общего. Попробуем определить это общее.
Любой объект, получив управление, должен ввести диапазон изменения аргумента [а, b], решить подзадачу, вывести результаты, а затем вернуть управление меню операций. Общее поведение и поля объектов опишем в классе TMetod. Основной метод этого класса Run должен реализовывать общее поведение и обеспечивать возможность изменения элементов этого поведения (решения конкретных подзадач) для объектов классов, которые будут от него наследоваться. Решение конкретной подзадачи реализуем как внутренний метод Task, вызываемый из метода Run. Этот внутренний метод для класса TMetod определять не будем (абстрактный метод).
Классы для реализации разрабатываемых объектов наследуем от TMetod, переопределяя метод Task и добавляя недостающие поля (рис. 7). На диаграмме курсивом показано, что класс TMetod и метод Task являются абстрактными. При необходимости это указывают явно, используя слово «abstruct».
Рис. 7. Иерархия классов для реализации объектов Рис. 8. Пример
примера ассоциации
Помимо определенных выше, на диаграмме классов показывают также отношения ассоциации между классами, объекты которых обмениваются сообщениями, но не связаны отношениями композиции или агрегации. Они обозначаются линиями без стрелок. Если сообщения передаются в одну сторону, то направление ассоциации можно показать стрелкой над линией. Кроме того, ассоциации можно дать имя, которое в данном контексте называют ролью (рис. 8).
После разработки диаграммы классов переходят к их реализации в конкретном языке программирования.
Дата добавления: 2015-12-01; просмотров: 1954;