Процедурное и объектно-ориентированное программирование. Инкапсуляция

Объектно-ориентированное программирование (ООП) - это методология программирования, опирающаяся на три базовых принципа:

- инкапсуляцию,

- наследование,

- полиморфизм.

Язык Java является объектно-ориентированным и в полном объёме использует эти принципы. В данном параграфе рассматривается принцип инкапсуляции, наследованию и полиморфизму посвящены отдельные параграфы.

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

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

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

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

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

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

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

Самым простым из указанных в начале параграфа принципов является инкапсуляция. Это слово в общем случае означает “заключение внутрь капсулы”. То есть ограничение доступа к внутреннему содержимому снаружи и отсутствие такого ограничения внутри капсулы. В объектном программировании “инкапсуляция” означает использование классов – таких типов, в которых кроме данных описаны подпрограммы, позволяющие работать с этими данными, а также выполнять другие действия. Такие подпрограммы, инкапсулированные в класс, называются методами. Поля данных и методы, заданные в классе, часто называют членами класса (class members).

Класс – это описание того, как будет устроен объект, являющийся экземпляром данного класса,а также какие методы объект может вызывать. Заметим, что методы, в отличие от других подпрограмм, могут напрямую обращаться к данным своего объекта. Так как экземплярами классов (“воплощением” в реальность того, что описано в классе) являются объекты, классы называют объектными типами.

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

Классы в Java задаются следующим образом. Сначала пишется зарезервированное слово class, затем имя класса, после чего в фигурных скобках пишется реализация класса – задаются его поля (глобальные переменные) и методы.

Объектные переменные – такие переменные, которые имеют объектный тип. В Java объектные переменные – это не сами объекты, а только ссылки на них. То есть все объектные типы являются ссылочными.

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

Например, если мы задаём переменную obj1 типа Circle, “окружность”, её задание осуществляется так :

Circle obj1;

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

Создаётся объект с помощью вызова специальной подпрограммы, задаваемой в классе и называемой конструктором. Конструктор возвращает ссылку на созданный объект. Имя конструктора в Java всегда совпадает с именем класса, экземпляр которого создаётся. Перед именем конструктора во время вызова ставится оператор new – “новый”, означающий, что создаётся новый объект. Например, вызов

obj1=new Circle();

означает, что создаётся новый объект типа Circle, “окружность”, и ссылка на него (адрес объекта) записывается в переменную obj1. Переменная obj1 до этого уже должна быть объявлена. Оператор new отвечает за динамическое выделение памяти под создаваемый объект.

Часто совмещают задание объектной переменной и назначение ей объекта. В нашем случае оно будет выглядеть как

Circle obj1=new Circle();

У конструктора, как и у любой подпрограммы, может быть список параметров. Они нужны для того, чтобы задать начальное состояние объекта при его создании. Например, мы хотим, чтобы у создаваемой окружности можно было при вызове конструктора задать координаты x, y её центра и радиус r. Тогда при написании класса Circle можно предусмотреть конструктор, в котором первым параметром задаётся координата x, вторым – y, третьим – радиус окружности r. Тогда задание переменной obj1 может выглядеть так:

Circle obj1=new Circle(130,120,50);

Оно означает, что создаётся объект-окружность, имеющий центр в точке с координатами x=130, y=120, и у которой радиус r=50.

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

Отметим ещё одно правило, касающееся используемых имён. Как мы помним, имена объектных типов принято писать с заглавной буквы, а имена объектных переменных – с маленькой. Если объектная переменная имеет тип Circle, она служит ссылкой на объекты-окружности. Поэтому имя obj1 не очень удачно – мы используем его только для того, чтобы подчеркнуть, что именно с помощью этой переменной осуществляется связь с объектом, и чтобы читатель не путал тип переменной, её имя и имя конструктора. В Java принято называть объектные переменные так же, как их типы, но начинать имя со строчной буквы. Поэтому предыдущий оператор мог бы выглядеть так:

Circle circle=new Circle(130,120,50);

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

Circle circle1=new Circle(130,120,50);

Circle circle2=new Circle(130,120,50);

С помощью объектных переменных осуществляется доступ к полям данных или методам объекта: сначала указывается имя переменной, затем точка, после чего пишется имя поля данных или метода. Например, если имя объектной переменной obj1, а имя целочисленного поля данных x, то присваивание ему нового значения будет выглядеть как

obj1.x=5;

А если имя подпрограммы show, у неё нет параметров и она не возвращает никакого значения, то её вызов будет выглядеть как

obj1.show();

Методы делятся на методы объектов и методы классов. Чаще всего пользуются методами объектов. Они так называются потому, что пользуются полями данных объектов, и поэтому их можно вызывать только из самих объектов. Методы классов, напротив, не пользуются полями данных объектов, и могут работать при отсутствии объекта. Поэтому их можно вызывать как из классов, так и из объектов. Формат вызова: имяКласса.имяМетода(список параметров) или имяОбъекта. имяМетода(список параметров).

При задании метода класса перед его именем необходимо поставить модификатор static – “статический”. Это крайне неудачное название, пришедшее в язык Java из C++. Мы никогда не будем называть такие методы статическими, а будем называть их методами класса, как это принято в теории программирования.

Точно так же переменные (поля данных) делятся на переменные объектов и переменные классов. При задании переменной класса перед её именем необходимо поставить модификатор static. Переменные класса, как и методы класса, можно вызывать как из классов, так и из объектов. Формат вызова: имяКласса.имяПеременной или имяОбъекта.имяПеременной.

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

Как уже было сказано, кроме полей данных в классе описываются методы. Несмотря на схожесть задания в классе полей и методов их реальное размещение во время работы программы отличается. Методы не хранятся в объектах, но объекты могут их вызывать. Каждый объект имеет свой комплект полей данных – “носит свои данные с собой”. Если имеется сотня объектов одного типа, то есть являющихся экземплярами одного и того же класса, в памяти компьютера будет иметься сотня ячеек памяти, устроенных так, как это описано в классе. Причём у каждого объекта значения этих данных могут быть свои. Например, если объект является окружностью, отрисовываемой на экране, у каждой окружности будет свой набор координат, радиусов и цветов. Если мы будем отрисовывать окружности с помощью метода show(), нет необходимости в каждом объекте хранить код этого метода – он для всех ста объектов будет одним и тем же. Поэтому методы не хранятся в объектах – они хранятся в классах. Класс – более общая сущность, чем объект, и до того, как во время работы программы в памяти компьютера будет создан объект, сначала должен быть загружен в память соответствующий ему класс. В Java имеется возможность создавать переменные типа “класс”, и с их помощью обращаться к классам таким образом, как будто это объекты особого рода. Но, в отличие от обычных объектов, такие “объекты” не могут существовать в нескольких экземплярах, и правила работы с ними принципиально отличаются от работы с объектами. Такие сущности называются метаобъектами.

Объявление переменных может осуществляться либо в классе, либо в методе. В первом случае мы будем говорить, что переменная является полем данных объекта, или глобальной переменной. Во втором – что она является локальной переменной.