Изучаем Java

Вы здесь: Главная >> Java-учебник >> Объекты и классы: начинаем изучение

Объекты и классы: начинаем изучение


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

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


Введение в объектно-ориентированное программирование

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


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


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


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


В основе ООП лежит та же идея. Любая программа состоит из объектов, обладающих определенными свойствами и возможностями выполнять определенные операции. Создавать объект самому или покупать его у других программистов — решение зависит лишь от наличия денег и времени. Однако, в основном, если объекты удовлетворяют вашим требованиям, неважно, как именно они сделаны. В ООП нужно заботиться лишь о том, что представляет собой объект. Итак, точно также, как производители компьютеров не вникают в детали внутреннего устройства блоков питания, пока эти блоки работают так, как надо, большинство программистов на языке Java не интересует, как именно реализован тот или иной объект, пока он удовлетворяет ихтребованиям.


Традиционное структурное программирование заключается в разработке набора функций (или алгоритмов) для решения поставленной задачи. Определив эти функции, программист должен задать подходящий способ хранения данных. Вот почему создатель языка Pascal Никлаус Вирт (Niklaus Wirth) назвал свою знаменитую книгу по программированию "Алгоритмы + Структуры данных = Программы".
Заметим, что в названии этой книги алгоритмы стоят на первом месте, а структуры данных— на втором. Это отражает образ мышления программистов того времени.
Во-первых, они решали, как манипулировать данными; затем решали, какую структуру применить для организации этих данных, чтобы работать с ними было легче.

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


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


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


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


Словарь объектно-ориентированного программирования

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


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


Инкапсуляция (иногда называемая сокрытием данных) — это ключевое понятие при работе с объектами. Формально инкапсуляция — это просто объединение данных и операций над ними в одном пакете и сокрытие данных от пользователя объекта. Данные в объекте называются полями экземпляра (instance fields), а функции и процедуры, выполняющие операции надданными, — ею методами (methods). В указанном объекте, т.е. экземпляре класса, поля экземпляра имеют определенные значения. Множество этих значений называется текущим состоянием (state) объекта. Применение любого метода к какому-нибудь объекту может изменить его состояние.

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


Еще один принцип ООП облегчает разработку собственных классов в языке Java: класс можно сконструировать на основе других классов. В этом случае говорят, что вновь созданный класс расширяет класс, на основе которого он построен. Язык Java, по существу, создан на основе "глобального суперкласса", называемого, что вполне естественно, Object. Все остальные объекты расширяют его. В следующей главе мы рассмотрим этот вопрос подробнее.

Новый класс содержит все свойства и методы расширяемого класса, а также новые методы и поля данных. Концепция расширения класса для создания нового называется наследованием (inheritance). Детали наследования описаны в следующей главе.


Объекты

Следуя принципам ООП, необходимо указать следующие ключевые свойства объектов.

Поведение (behavior) объекта — что с ним можно делать и какие методы к нему можно применять.

Состояние объекта — как этот объект реагирует на применение методов.

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

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

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


Эти основные характеристики могут влиять друг на друга. Например, состояние объекта может влиять на его поведение. (Если заказ "выполнен" или "оплачен", объект может отказаться выполнить вызов метода, требующего добавить или удалить предмет заказа. И наоборот, если заказ "пуст", т.е. ни один предмет не был заказан, он не может быть выполнен.)
 
В традиционной процедурно-ориентированной программе выполнение начинается сверху, с функции main. При разработке объектно-ориентированной системы понятие "верха" не существует, и новички в ООП часто интересуются, с чего начинать. Ответ таков: "Сначала найдите классы и добавьте методы в каждый класс".


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

 

Например, при описании системы обработки заказов используются следующие имена существительные:

предмет;
заказ;
адрес доставки;
оплата;
счет.

Эти имена существительные соответствуют классам Item, Order и т.д.


Рассмотрим теперь глаголы.

Предметы заказываются. Заказы выполняются или отменяются. Оплата заказа осуществляется. Используя эти глаголы, можно определить объект, выполняющий такие действия. Например, если поступил новый заказ, то ответственность за его обработку должен нести объект класса Order, поскольку именно в нем содержится информация о способе хранения и видах заказываемых предметов. Следовательно, в классе Order должен существовать метод add, получающий объект Item в качестве параметра.


Разумеется, правило "имен существительных и глаголов" — это просто совет, и только опыт может помочь программисту решить, какие имена существительные и глаголы важны при создании класса.


Отношения между классами

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

Зависимость ("использует").
•    Агрегирование ("содержат").
•    Наследование ("является").

Отношение зависимости (dependence— "uses-a") наиболее очевидное и распространенное. Например, класс Order использует класс Account, поскольку объекты класса Order должны иметь доступ к объектам класса Account, чтобы проверить кредитоспособность заказчика.

Однако класс Item не зависит от класса Account, так как объекты класса Item никогда не интересуются состоянием счета заказчика. Следовательно, класс зависит от другого класса, если его методы манипулируют объектами этого класса.

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


Отношение агрегирования (agg regat ion—"Ьа<;-а")довольно просто понять, поскольку оно является вполне конкретным. Например, объект Order содержит объекты класса Item. Агрегирование означает, что класс А содержит объекты класса В.

Некоторые специалисты по методологии пренебрегают концепцией агрегирования и предпочитают использовать отношение "ассоциированности" ("association"). С точки зрения моделирования это разумно. Однако для программистов отношение "содержит" обладает большим смыслом. Мы предпочитаем использовать понятие агрегирования еще по одной причине — стандартное обозначение отношения ассоциированности менее понятно (см. табл. 4.1).

Наследование (inheritance — "is-a") выражает отношение между конкретным и более общим классом. Например, класс RushOrder является производным от класса Order. Класс RushOrder имеет специальные методы дня обработки приоритетов и разные методы дня вычисления стоимости доставки, в то время как другие его методы, например, дня заказа предмета и выписывания счета, унаследованы им от класса Order. В частности, если класс А расширяет класс В, то говорят, что класс А наследует методы класса В, имея, кроме них, еще и дополнительные возможности. (Более полно наследование описано в следующей главе.)

Многие программисты используют символы языка UML (Unified Modeling Language) для изображения диаграмм класса (class diagrams), описывающих отношения между классами. Пример такой диаграммы приведен на рис. 4.1.


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

 

Табл. 4.1.

Обозначения, принятые в языке UML для характеристики отношений между классами.


На рис. 4.1 показан пример диаграммы класса.
 

Рис. 4.1.Диаграмма класса

 

Для создания диаграмм на языке UML разработано много инструментов. Некоторые поставщики предлагают очень мощные (и очень дорогие) программы, призванные стать основным средством проектирования.


Партнеры сайта