Полезное в сети

Всегда в теме

Статистика


Яндекс.Метрика


Онлайн всего: 1
Гостей: 1
Пользователей: 0

Рекомендуем



Главная » Статьи » Образовательные » Программирование

Основы объектно-ориентированного программирования на Java (часть 2)
1.5. Типы отношений между классами
Как правило, любая программа, написанная на объектно-ориентированном языке,
представляет собой некоторый набор классов, связанных между собой. Можно провести
аналогию между строительством дома и написанием программы. Подобно тому, как здание
строится из кирпичей, компьютерная программа с использованием ООП строится из классов.
Причем эти классы должны знать друг о друге, для того чтобы взаимодействовать между
собой и сообща выполнить поставленную задачу.
Возможны следующие связи между классами в рамках объектной модели (приводятся
лишь наиболее простые и часто используемые виды связей, подробное их рассмотрение
выходит за рамки этой ознакомительной главы):
• Агрегация (Aggregation)
• Ассоциация (Association)
• Наследование (Inheritance)
• Метаклассы (Metaclass)
1.5.1. Агрегация
Отношение между классами типа "содержит" или "состоит из" называется агрегацией или
включением. Например, если аквариум наполнен водой и в нем плавают рыбки, то можно
сказать, что аквариум агрегирует в себе воду и рыбок.
Такое отношение включения или агрегации (aggregation) изображается линией с ромбиком
на стороне того класса, который выступает в качестве владельца или контейнера.
Необязательное название отношения записывается посередине линии.
В нашем примере отношение "contain" является двунаправленным. Объект класса Aquarium
содержит несколько объектов Fish. В то же время каждая рыбка "знает", в каком именно
аквариуме она живет. Факт участия класса в отношении изображается посредством роли.
В примере можно видеть роль "home" класса Aquarium (аквариум является домом для
рыбок), а также роль "inhabitants" класса Fish (рыбки являются обитателями аквариума).
Название роли обычно совпадает с названием соответствующего поля в классе.
Изображение такого поля на диаграмме излишне, если уже изображено имя роли. Т.е. в
данном случае класс Aquarium будет иметь свойство (поле) inhabitants, а класс Fish -
свойство home.
Число объектов, участвующих в отношении, записывается рядом с именем роли. Запись
"0..n" означает "от нуля до бесконечности". Приняты так же обозначения:
• "1..n" - от единицы до бесконечности;
• "0" - ноль;
• "1" - один;
• "n" - фиксированное количество;
• "0..1" - ноль или один.
Код, описывающий рассмотренную модель и явление агрегации, может выглядеть,
например, следующим образом:
// определение класса Fish
public class Fish {
// определения поля home (ссылка на объект Aquarium)
private Aquarium home;
public Fish() {
}
}
// определение класса Aquarium
public class Aquarium {
// определения поля inhabitants (массив ссылок на объекты Fish)
private Fish inhabitants[];
public Aquarium() {
}
}
1.5.2. Ассоциация
Если объекты одного класса ссылаются на один или более объектов другого класса, но
ни в ту, ни в другую сторону отношение между объектами не носит характера "владения"
или контейнеризации, то такое отношение называют ассоциацией (association). Отношение
ассоциации изображается так же,как и отношение агрегации, но линия, связывающая
классы - простая, без ромбика.
В качестве примера можно рассмотреть программиста и его компьютер. Между этими
двумя объектами нет агрегации, но существует четкая взаимосвязь. Так, всегда можно
установить за какими компьютерами работает какой-либо программист, а также какие люди
пользуются отдельно взятым компьютером. В рассмотренном примере имеет место
ассоциация многие-ко-многим.
В данном случае между экземплярами классов Programmer и Computer в обе стороны
используется отношение "0..n", т.к. программист теоретически может не пользоваться
компьютером (если он теоретик или на пенсии). В свою очередь компьютер может никем
не использоваться (если он новый и еще не установлен).
Код, соответствующий рассмотренному примеру, будет, например, следующим:
public class Programmer {
private Computer computers[];
public Programmer() {
}
}
public class Computer {
private Programmer programmers[];
public Computer() {
}
}
1.5.3. Наследование
Наследование (inheritance) - это отношение между классами, при котором класс использует
структуру или поведение другого (одиночное наследование) или других (множественное
наследование) классов. Наследование вводит иерархию "общее/частное", в которой
подкласс наследует от одного или нескольких более общих суперклассов. Подклассы
обычно дополняют или переопределяют унаследованную структуру и поведение.
В качестве примера можно рассмотреть задачу, в которой необходимо реализовать классы
"Легковой автомобиль" и "Грузовой автомобиль". Очевидно, эти два класса имеют много
общей функциональности. Так, оба они имеют 4 колеса, двигатель, могут перемещаться
и т.д. Всеми этими свойствами обладает любой автомобиль, не зависимо от того грузовой
он или легковой, 5- или 12-местный. Разумно вынести эти общие свойства и
функциональность в отдельный класс, например "Автомобиль", и наследовать от него
классы "Легковой автомобиль" и "Грузовой автомобиль" чтобы избежать повторного
написания одного и того же кода в разных классах.
Отношение обобщения обозначается сплошной линией с треугольной стрелкой на одном
из концов. Стрелка указывает на более общий класс (класс-предок или суперкласс), а ее
отсутствие - на более специальный класс (класс-потомок или подкласс)
Использование наследования способствует уменьшению количества кода, написанного
для описания схожих сущностей, а так же способствует написанию более эффективного
и гибкого кода.
В рассмотренном примере применено одиночное наследование. Некоторый класс так же
может наследовать свойства и поведение сразу нескольких классов. Наиболее популярным
примером применения множественного наследования является проектирование системы
учета товаров в зоомагазине.
Все животные в зоомагазине являются наследниками класса "Животное", а также
наследниками класса "Товар". Т.е. все они имеют возраст, нуждаются в пище и воде, и в
то же время имеют цену и могут быть проданы.
Множественное наследование на диаграмме изображается точно так же как и одиночное
наследование за исключением того, что линии наследования соединяют класс-потомок
сразу с несколькими суперклассами.
Не все объектно-ориентированные языки программирования содержат в себе языковые
конструкции для описания множественного наследования.
Многие объектно-ориентированные языки программирования не поддерживают
множественное наследование и не имеют синтаксических конструкций для его реализации.
В языке Java множественное наследование имеет ограниченную поддержку через
интерфейсы и будет рассмотрено в следующих главах.
1.5.4. Метаклассы
Итак, любой объект имеет структуру, состоящую из полей и методов. Объекты, имеющие
одинаковую структуру и семантику, описываются одним классом, который и является, по
сути, определением структуры объектов, порожденных от него.
В свою очередь каждый класс, или описание, всегда имеет строгий шаблон, задаваемый
языком программирования или выбранной объектной моделью. Он определяет, например,
допустимо ли множественное наследование, какие ограничения на имен½ование классов,
как описываются п¿оля и методы, набор существующих типов данных и многое другое.
Таким образом, класс можно рассматривать как объект, у которого есть свойства (имя,
список полей и их типы, список методов, список аргументов для каждого метода и т.д.).
Также класс может обладать поведением, то есть поддерживать методы. А раз для любого
объекта существует шаблон, описывающий свойства и поведение этого объекта, то его
можно определить и для класса. Такой шаблон, задающий различные классы, называется
метакласс.
Чтобы легче представить себе, что такое метакласс, рассмотрим пример некой
бюрократической организации. Будем считать, что все классы в такой системе - некоторые
строгие инструкции, которые описывают, что нужно сделать, чтобы породить новый объект
(например, нанять нового служащего или открыть новый отдел). Как и полагается классам,
они описывают все свойства новых объектов (например, зарплату и профессиональный
уровень для сотрудников, площадь и имущество для отделов) и их поведение (обязанности
служащих и функции подразделений).
В свою очередь написание новой инструкции можно строго регламентировать. Скажем,
необходимо использовать специальный бланк, придерживаться правил оформления и
заполнить все обязательные поля (например, номер инструкции и фамилии ответственных
работников) Такая "инструкция инструкций" и будет представлять собой метакласс в ООП.
Итак, объекты порождаются от классов, а классы - от метакласса. Он, как правило, в
системе есть только один. Но существуют языки программирования, в которых можно
создавать и использовать собственные метаклассы, например язык Python. Например,
функциональность метакласса может быть следующая: при создании класса он будет
просматривать список всех методов в классе и если имя метода имеет вид set_XXX или
get_XXX, то автоматически создать поле с именем XXX, если такого не существует.
Поскольку метакласс сам является классом, то нет никакого смысла в заведении "мета-
мета-классов".
В языке Java также есть такое понятие. Это класс, который так и называется - Class
(описывает классы) и располагается в основной библиотеке java.lang. Виртуальная машина
использует его по прямому назначению. Когда загружается очередной .class-файл,
содержащий описание нового класса, JVM порождает объект класса Class, который будет
хранить его структуру. Таким образом, Java использует концепцию метакласса в самых
практических целях. С помощью Class реализована поддержка статических (static) полей
и методов. Наконец, этот класс содержит ряд методов, полезных для разработчиков. Они
будут рассмотрены в следующих главах.
1.6. Достоинства ООП
От любой методики разработки программного обеспечения мы ждем, что она поможет нам
в решении наших задач. Но одной из самых значительных проблем проектирования
является сложность. Чем больше и сложнее программная система, тем важнее становится
разбить ее на небольшие, четко очерченные части. Чтобы справиться со сложностью,
необходимо абстрагироваться от мелких деталей. Для этой цели классы представляют
собой весьма удобный инструмент.
• Классы позволяют проводить конструирование из полезных компонент, обладающих
простыми инструментами, что дает возможность абстрагироваться от деталей
реализации.
• Данные и операции над ними вместе образуют определенную сущность, и они не
разносятся по всей программе, как это нередко бывает в случае процедурного
программирования, а описываются вместе. Локализация кода и данных улучшает
наглядность и удобство сопровождения программного обеспечения.
• Инкапсуляция позволяет привнести свойство модульности, что облегчает
распараллеливание выполнения задачи между несколькими исполнителями иобновление
версий отдельных компонент.
ООП дает возможность создавать расширяемые системы. Это одно из самых значительных
достоинств ООП, и именно оно отличает данный подход от традиционных методов
программирования. Расширяемость означает, что существующую систему можно заставить
работать с новыми компонентами, причем без внесения в нее каких-либо изменений.
Компоненты могут быть добавлены на этапе исполнения программы.
Полиморфизм оказывается полезным преимущественно в следующих ситуациях.
• Обработка разнородных структур данных.
Программы могут работать, не различая вида объектов, что существенно упрощает
код. Новые виды могут быть добавлены в любой момент.
• Изменение поведения во время исполнения.
На этапе исполнения один объект может быть заменен другим, что позволяет легко без
изменения кода адаптировать алгоритм, в зависимости от того, какой используется
объект.
• Реализация работы с наследниками.
Алгоритмы можно обобщить настолько, что они уже смогут работать более чем с одним
видом объектов.
• Создание "каркаса” (framework).
Независимые от приложения части предметной области могут быть реализованы в виде
набора универсальных классов, или каркаса (framework), и в дальнейшем расширены
за счет добавления частей, специфичных для конкретного приложения.
Часто на практике многоразового использования программного обеспечения добиться не
удается из-за того, что существующие компоненты уже не отвечают новым требованиям.
ООП помогает этого достичь без нарушения работы уже имеющихся клиентов, что позволяет
нам извлечь максимум из многоразового использования компонент.
• Сокращается время на разработку, которое с выгодой может быть отдано другим
задачам.
• Компоненты многоразового использования обычно содержат гораздо меньше ошибок,
чем вновь разработанные, ведь они уже не раз подвергались проверке.
• Когда некая компонента используется сразу несколькими клиентами, то улучшения,
вносимые в ее код, одновременно оказывают свое положительное влияние и на
множество работающих с ней программ.
• Если программа опирается на стандартные компоненты, то ее структура и
пользовательский интерфейс становятся более унифицированными, что облегчает ее
понимание и упрощает ее использование.
1.7. Недостатки ООП
Документирование классов - задача более трудная, чем это было в случае процедур и
модулей. Поскольку любой метод может быть переопределен, в документации должно
говориться не только о том, что делает данный метод, но также и о том, в каком контексте
он вызывается. Ведь переопределенные методы обычно вызываются не клиентом, а самим
каркасом. Таким образом, программист должен знать, какие условия выполняются, когда
вызывается данный метод. Для абстрактных методов, которые пусты, в документации
должно даже говориться о том, для каких целей предполагается использовать
переопределяемый метод.
В сложных иерархиях классов поля и методы обычно наследуются с разных уровней. И
не всегда легко определить, какие поля и методы фактически относятся к данному классу.
Для получения такой информации нужны специальные инструменты вроде навигаторов
классов. Если конкретный класс расширяется, то каждый метод обычно сокращают перед
передачей сообщения базовому классу. Реализация операции, таким образом,
рассредоточивается по нескольким классам, и чтобы понять, как она работает, нам
приходится внимательно просматривать весь код.
Методы, как правило, короче процедур, поскольку они осуществляют только одну операцию
над данными. Зато количество методов намного выше. Короткие методы обладают тем
преимуществом, что в них легче разбираться, неудобство же их связано с тем, что код для
обработки сообщения иногда "размазан" по многим маленьким методам.
Абстракцией данных не следует злоупотреблять. Чем больше данных скрыто в недрах
класса, тем сложнее его расширять. Отправной точкой здесь должно быть не то, что
клиентам не разрешается знать о тех или иных данных, а то, что клиентам для работы с
классом этих данных знать не требуется.
Часто можно слышать, что ООП является неэффективным. Как же дело обстоит в
действительности? Мы должны четко проводить грань между неэффективностью на этапе
выполнения, неэффективностью в смысле распределения памяти и неэффективностью,
связанной с излишней универсализацией.
1. Неэффективность на этапе выполнения. В языках типа Smalltalk сообщения
интерпретируются во время выполнения программы путем осуществления поиска их
в одной или нескольких таблицах и за счет выбора подходящего метода. Конечно, это
медленный процесс. И даже при использовании наилучших методов оптимизации
Smalltalk-программы в десять раз медленнее оптимизированных C-программ.
В гибридных языках типа Oberon-2, Object Pascal и C++ посылка сообщения приводит
лишь к вызову через указатель процедурной переменной. На некоторых машинах
сообщения выполняются лишь на 10% медленнее, чем обычные процедурные вызовы.
И поскольку сообщения встречаются в программе гораздо реже других операций, их
воздействие на время выполнения влияния практически не оказывает.
Однако существует другой фактор, который влияет на время выполнения: это
инкапсуляция данных. Рекомендуется не предоставлять прямой доступ к полям класса,
а выполнять каждую операцию над данными через методы. Такая схема приводит к
необходимости выполнения процедурного вызова при каждом доступе к данным.
Однако, когда инкапсуляция используется только там, где она необходима (т.е. в
случаях, где это становится преимуществом), то замедление вполне приемлемое.
2. Неэффективность в смысле распределения памяти. Динамическое связывание и
проверка типа на этапе выполнения требуют по ходу работы информации о типе
объекта. Такая информация хранится в дескрипторе типа, и он выделяется один на
класс. Каждый объект имеет невидимый указатель на дескриптор типа для своего
класса. Таким образом, в объектно-ориентированных программах требуемая
дополнительная память выражается в одном указателе для объекта и в одном
дескрипторе типа для класса.
3. Излишняя универсальность. Неэффективность может также означать, что программа
имеет ненужные возможности. В библиотечном классе часто содержится больше
методов, чем это реально необходимо. А поскольку лишние методы не могут быть
удалены, то они становятся мертвым грузом. Это не воздействует на время выполнения,
но влияет на возрастание размера кода.
Одно из возможных решений - строить базовый класс с минимальным числом методов,
а затем уже реализовывать различные расширения этого класса, которые позволят
нарастить функциональность.
Другой подход - дать возможность компоновщику удалять лишние методы. Такие
интеллектуальные компоновщики уже доступны для различных языков и операционных
систем.
Но нельзя утверждать, что ООП неэффективно. Если классы используются лишь там, где
это действительно необходимо, то потеря эффективности из-за повышенного расхода
памяти и меньшей производительности незначительна. Кроме того, часто более важной
является надежность программного обеспечения и небольшое время его написания, а не
производительность.
Категория: Программирование | Добавил: Admin (19.04.2012)
Просмотров: 1506 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]

Поиск

Вход

Гость
  • Вход
  • Регистрация
  • Читаемое

    Заходи не жди