На этом шаге рассмотрим природу класса.
Понятия класса и объекта настолько тесно связаны, что невозможно говорить об объекте, не упоминая его класс. Однако между этими понятиями существуют важные различия.
В то время как объект обозначает конкретную сущность, определенную во времени и в пространстве, класс представляет собой лишь абстракцию существенных свойств объекта. Таким образом, можно говорить о классе МЛЕКОПИТАЮЩИЕ, описывающем характеристики, общие для всех млекопитающих. Для указания на конкретного представителя млекопитающих необходимо сказать "это млекопитающее" или "то млекопитающее".
В словаре Webster's Third New International Dictionary дано следующее определение класса: Группа, множество или разновидность сущностей, обладающих общими свойствами или отличающихся от других уровнем качества, степенью компетентности или состоянием [Webster's Third New International Dictionary of the English Language, unabridged. 1986. Chicago, IL: Merriam-Webster.].
В контексте объектно-ориентированного анализа используется следующее определение класса. Класс — это множество объектов, обладающих общей структурой, поведением и семантикой.
Отдельный объект является просто экземпляром класса.
Что же нельзя назвать классом? Например, объект не является классом.
Объекты, не имеющие общей структуры и поведения, нельзя объединить в класс, поскольку по определению они ничем не связаны между собой.
Класс описывает совокупность объектов, обладающих общей структурой и одинаковым поведением Следует подчеркнуть, что класс — как определено в большинстве языков программирования — необходимый, но недостаточный инструмент декомпозиции сложных систем. Иногда абстракции так сложны, что не могут быть выражены с помощью одного описания класса. Например, на достаточно высоком уровне абстракции графический интерфейс пользователя, база данных или система учета являются объектами, которые не могут быть экземплярами одного класса. Лучше считать их некими совокупностями классов, экземпляры которых взаимодействуют между собой, обеспечивая требуемую структуру и поведение. Страуструп (Stroustrup) называет такие кластеры компонентами [Stroustrup, B. 1991. The C+ Programming Language. Second Edition. Reading, MA: Addison-Wesley, p. 422.].
Большие задачи разделяются на множество мелких подобно тому, как крупный заказ распределяется среди субподрядчиков. Нигде эта идея не проявляется так очевидно, как в проектировании классов.
В то время как отдельный объект является конкретной сущностью, играющей некую роль в общей системе, класс описывает структуру и поведение, общую для всех родственных объектов. Таким образом, класс — это контракт, связывающий между собой абстракцию и ее клиентов. Реализуя эти решения в интерфейсе класса, языки программирования со строгим контролем типов позволяют выявить нарушения этого контракта во время компиляции.
Концепция контракта порождает различия между внешним и внутренним представлениями класса. Внешний вид класса реализуется интерфейсом, который подчеркивает абстрактный характер класса, скрывая структуру и особенности его поведения. Интерфейс, как правило, состоит из объявлений операций, применимых к его объектам, а также объявлений других классов, констант, переменных и исключительных ситуаций, необходимых для полного описания абстракции.
Внутреннее представление обеспечивает реализация класса, скрывающая секретные особенности его поведения. Реализация состоит, в основном, из описания всех операций, объявленных в интерфейсе класса.
Интерфейс класса можно разделить на четыре части:
В разных языках программирования используется разная семантика этих форм доступа.
Константы и переменные, образующие представление класса, называются по-разному. Например, в языке Smalltalk используется термин переменная экземпляра (instance variable), в языке Object Pascal и Java — поле (field), а в языке C++ — данные-члены (data member). В дальнейшем эти термины будут использоваться как синонимы.
Состояние объекта должно иметь определенное представление в соответствующем классе. Как правило, оно выражается с помощью объявлений констант и переменных, помещенных в защищенном или закрытом разделе интерфейса класса. Таким образом, представление, общее для всех экземпляров класса, инкапсулировано в нем, а его изменения не влияют на функциональные свойства всех внешних клиентов.
Разные языки программирования по-разному используют смесь открытых, защищенных и закрытых разделов, а также пакетов, устанавливая разные права доступа к интерфейсу класса и осуществляя контроль над тем, что клиент может видеть, а что нет (т.е. реализуя концепцию видимости).
В частности, язык C++ позволяет программисту провести четкое различие между всеми четырьмя перечисленными уровнями доступа.
Механизм дружественных функций и классов в языке C++ позволяет классу объявлять определенные классы привилегированными, открывая им доступ к своим защищенным и закрытым разделам. Концепция дружественных классов нарушает принцип инкапсуляции. В языке Java этого механизма нет. Вместо этого язык Java реализует аналогичный тип видимости, называемый пакетным доступом (package access). При этом все классы, входящие в один и тот же пакет, имеют доступ друг к другу. Помимо этого все остальные уровни доступа в языке Java (открытый, защищенный и закрытый) реализуются точно так же, как и в языке C++. В противоположность этому, язык Ada допускает лишь открытые и закрытые разделы, но не предусматривает защищенных. В языке Smalltalk все переменные экземпляров являются закрытыми, а все методы остаются открытыми. В языке Object Pascal все поля и операции открыты, а значит, инкапсуляция не осуществляется.
Поведение простого класса можно понять, изучая семантику, описанную с помощью операций, объявленных в открытом разделе. Однако поведение более сложных классов (например, составление расписания для экземпляра класса ТЕМПЕРАТУРНЫЙ _КОНТРОЛЕР) предусматривает взаимодействие разных операций, входящих в каждый из них. Как уже отмечалось, объекты таких классов действуют как маленькие автоматы.
Поскольку все такие объекты ведут себя одинаково, для описания их общей семантики, упорядоченной по времени и событиям, можно использовать класс. Динамику поведения некоторых интересных классов можно описать, используя конечные автоматы.
На следующем шаге рассмотрим отношения между классами.