Существует разные подходы к
программированию. Любому из них присущ свой собственный способ абстрагирования
сущностей, с которыми он работает.
Так, процедурно-ориентированный подход
оперирует абстракцией алгоритма. Язык С – типичный процедурный язык,
хотя на нем возможно писать программы, напоминающие по стилю
объектно-ориентированные.
Логико-ориентированный имеет в
виду цели, выражаемые на языке логических предикатов (язык Prolog).
Наконец, объектно-ориентированное
программирование абстрагирует классы и объекты (язык С++).
В чем же состоит его суть?
Известный теоретик Грейди Буч так
определяет этот подход:
"Объектно-ориентированное
программирование – это методология программирования, основанная на организации
программы, в виде совокупности объектов, каждый из которых является
представителем определенного класса, а классы образуют иерархию наследования.”
В "строго” объектно-ориентированных
языках объектами являются решительно все элементы программы, в том числе она
сама. Таковы программы на языке Java. Язык C++ сохраняет все возможности
процедурного языка С. В C++ можно создать, например, совершенно отдельно
стоящую глобальную переменную, да и главная функция main() – совершенно "внеклассовая”. Такая универсальность
C++ может быть как преимуществом, так и недостатком. У программиста, впервые
применяющего объектный подход в С++, всегда имеется тенденция мыслить старыми,
процедурными категориями.
Итак, центральным элементом абстракции
объектно-ориентированной методологии является объект.
Объекты нашего программного мира
моделируют объекты реального или воображаемого мира (например, мира некоторой
игры, хотя это мир тоже мыслимый, а стало быть, реальный в смысле логики). Как
модель, программный объект представляет собой некоторую абстракцию объекта
реального, что предполагает выделение существенных свойств последнего и
игнорирование тех, что безразличны с насущной, "сиюминутной”, точки зрения.
Объект в программе мало чем отличается от
предмета реального мира. Он является моделью последнего. Поэтому, в
известном смысле, пока не важно, о каких объектах пойдет речь – реальных или программных.
Объект, прежде всего, характеризуется
своим состоянием. Возьмем, к примеру, базу данных (совокупность записей
и система управления ими). Она – объект, поскольку есть нечто цельное. К числу
моментов, определяющих ее состояние, относится текущее число записей. Каждая
запись тоже, очевидно, является объектом. Отдельные поля существующей записи
могут модифицироваться, и их совокупность определяет текущее состояние записи,
а вместе с ней и состояние всей базы данных.
Таким образом, состояние программного
объекта полностью определяется некоторым набором (структурой) характеристик и
их текущими значениями. Эти характеристики называют полями или (в C++) элементами
данных объекта. Но не все характеристики состояния объекта должны быть
непосредственно видимы извне. Классический пример, который неизменно приводят
американские авторы, – автомобиль. С точки зрения водителя у него есть приборный
щиток, отражающий скорость, обороты, температуру двигателя и т. д., и органы
управления. Но в автомобиле масса деталей, спрятанных под капотом, состояние
которых не исчерпывается показаниями приборов и эти детали водителю не интересны,
пока автомобиль «на ходу».
На самом деле состояние, как таковое,
может быть вообще скрыто от внешнего взгляда. Оно, как говорят, инкапсулировано
в объекте. Однако в зависимости от своего состояния, объект по-разному
взаимодействует со своим окружением, что приводит нас к следующему понятию -
поведению объекта.
Поведение – это то, как объект
взаимодействует с окружением (другими объектами). Объект может подвергаться
воздействию окружения или сам воздействовать на него. Объект может
рассматриваться как аналог предмета, а поведение – как реакция на манипуляции с
ним или действия, инициированные самим объектом. В некоторых объектных системах
(например, OLE 2) говорят о глаголах, т. е. действиях, которые могут
связываться с объектом. Для каждого объекта существует определенный набор
возможных действий над ним.
Действия в отношении к объектам иногда
называют передачей сообщений между ними. В языках, подобных Object
Pascal, операции над объектами называют обычно методами. В C++ благодаря
его «процедурному наследству» чаще говорят о функциях-элементах объекта.
Эти функции являются структурными элементами определения класса, к которому
принадлежит объект.
Естественно, программа, т. е. цельная
система, реализуется только во взаимодействии всех ее объектов. Здесь можно
выделить в основном два типа взаимодействий, или отношений: связь и агрегацию.
Связь является довольно очевидной
разновидностью взаимодействий – один объект может воздействовать на другой,
являющийся в известном смысле автономной сущностью. Существует отношение
подчинения – "А использует В”. Один объект является активным, другой –
пассивным. Понятно, что в системе один и тот же объект может выступать как в
активной, так и в пассивной роли по отношению к различным объектам.
Другой тип отношений – агрегация, когда
один объект является составной частью, т. е. элементом другого – "А содержит
В”. Агрегация может означать физическое вхождение одного объекта в другой; в C++
это соответствует описанию первого объекта в качестве элемента данных другого
объекта. Но это не обязательно. Например, в Windows есть понятие дочернего
окна. Здесь имеет место отношение агрегации, хотя на физическом уровне
родительское и дочернее окна автономны.
Класс
Класс – это множество объектов, имеющих
одинаковую структуру. Класс в программировании является аналогом понятия,
или категории. В то время как объект представляет собой конкретную сущность,
класс является абстракцией сущности объекта. Конкретный объект является представителем,
или (не совсем грамотно) экземпляром класса.
Другими словами, структура характеристик
объекта и все потенциальные отношения между объектами заложены в классе.
Однако классы могут находиться еще и в специфических отношениях между собой.
Если существенными видами отношений между
объектами являются связь и агрегация, то фундаментальное отношение между
классами – это наследование. Один класс может наследовать другому. В C++
в таком случае говорят, что один класс является базовым, а другой
(который наследует первому) – производным. Еще их называют
соответственно классом-предком и классом-потомком. Наследование может быть прямым,
когда один класс является непосредственным предком (потомком) другого, или косвенным,
когда имеют место промежуточные наследования.
Производный класс наследует всю структуру
характеристик и поведение базового, однако может дополнять или модифицировать
их. Если класс В является производным по отношению к А, то с логической точки
зрения "В есть А”. Например, понятие, или класс, "автомобиль” (В) является
производным по отношению к понятию "средство передвижения” (А). Автомобиль есть
средство передвижения ? Конечно, да.
Как и в логике, здесь существует
взаимосвязь между "содержанием” и "объемом” понятия. Производный класс имеет
большее содержание, но меньший объем, чем базовый.
Наследование может быть простым,
когда производный класс имеет всего одного непосредственного предка, и сложным,
если в наследовании участвуют несколько базовых классов.
Полиморфизм, наряду с
наследованием, является фундаментальной концепцией объектной модели
программирования. Без него объектно-ориентированное программирование потеряло
бы значительную долю своего смысла. Суть полиморфизма в том, что с объектами
различных классов, имеющих один и тот же базовый класс, можно при определенных
условиях обращаться, как с объектами базового класса; однако объект,
являющийся, по видимости, объектом базового класса, будет вести себя по-разному
в зависимости от того, что он такое на самом деле, т. е. представитель какого
из производных классов.
В C++ полиморфное поведение объектов
обеспечивается механизмом виртуальных функций-элементов. Рассмотрим
пример "полиморфизма”, реализованного на языке С. Допустим, программа должна в
числе всего прочего выводить на экран различные геометрические фигуры. Она
определяет класс "фигура”, в котором предусмотрен виртуальный метод "нарисовать” (в C++ это был бы абстрактный
класс). От данного класса "фигура” можно произвести несколько классов –
"точка”, "линия”, "круг” и т. д., – каждый из которых будет по-своему
определять метод "нарисовать”.
Указатель на класс "фигура” может
ссылаться на объект любого из производных классов (поскольку все они являются
фигурами), и для указываемого им объекта можно вызвать метод "нарисовать”, не
имея представления о том, что это на самом деле за фигура.
Об этих принципах объектного подхода мы
уже упоминали. На самом деле это принципы программирования, присущие не только
объектно-ориентированной модели. Но хотелось бы несколько уточнить их в
отношении к организации классов.
Чтобы абстрагировать объект, он
должен быть сравнительно "слабо связан” с окружающим миром. Он должен обладать
сравнительно небольшим набором (существенных) свойств, характеризующих его
отношения с другими объектами. С другой стороны, выделение класса как
некоторого понятия, охватывающего целый ряд различных объектов, также является
моментом абстракции.
Поэтому абстрагирование, как таковое,
имеет два аспекта: выделение общих и в тоже время существенных
свойств, описывающих поведение ряда схожих предметов.
С абстракцией неразрывно связан принцип инкапсуляции.
Инкапсуляция – это сокрытие второстепенных деталей объекта. Для этого нужно
выделить сначала существенные его свойства. Но чтобы выделить существенные
свойства, нужно сначала отвлечься от второстепенных. Так что в действительности
речь может идти только о едином акте, в котором можно лишь отвлеченно выделить
два отдельных момента.
С технической точки зрения абстракция и
инкапсуляция выражаются в том, что классы состоят из интерфейса и реализации.
Интерфейс представляет абстрагированную сущность объектов. Реализация скрыта в
своих деталях от пользователя класса.
|