Концепция объектно-ориентированного программирования

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

Давайте предположим, что перед нами поставили задачу создать простой графический редактор, в котором пользователь может рисовать графические примитивы: линию, треугольник, прямоугольник и эллипс:

Если бы в Java не было классов, то нам пришлось бы хранить фигуры в памяти устройства в виде набора координат вершин:

        int N = 0;
        int points_x[] = new int[100];
        int points_y[] = new int[100];
 
        // треугольник
        points_x[0] = 10; points_y[0] = 5;
        points_x[1] = 20; points_y[1] = 10;
        points_x[2] = 5; points_y[2] = 10;
 
        // линия
        points_x[3] = 100; points_y[3] = 100;
        points_x[4] = 200; points_y[4] = 50;
 
        N = 5;  // число вершин

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

Фундаментальным элементом ООП является класс. И в нашем примере, можно объявить четыре класса для каждого типа примитива: линии, треугольника, прямоугольника и эллипса. Например, класс для треугольника в самом простом варианте можно записать так:

class Triangle {
    int x1, x2, x3;
    int y1, y2, y3;
    int width, color;
}

Здесь за ключевым словом class идет имя формируемого класса – Triangle (треугольник), а внутри (в фигурных скобках) – набор необходимых переменных (их еще принято называть полями класса, членами класса или просто – переменными класса). То есть, мы создали общее описание треугольника в виде трех вершин, толщины линии и цвета линии. Но вы можете заметить, что класс у нас один, а треугольников может быть множество. Все верно. Дело в том, что класс следует воспринимать как некий шаблон, чертеж, проект, по которому, затем, можно сформировать любое количество конкретных объектов:

Triangle t1 = new Triangle();
Triangle t2 = new Triangle();
Triangle t3 = new Triangle();

Видите, у нас теперь каждый треугольник представляется в виде целостного объекта, на которые ссылаются отдельные переменные t1, t2, t3. И, далее, мы уже работаем с ними через эти ссылки: меняем координаты вершин, толщину и цвет линии. Все стало гораздо удобнее благодаря использованию классов.

По аналогии мы можем объявить еще три класса с именами:

class Line {}
class Rectangle {}
class Ellipse {}

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

Наследование классов

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

А, далее, с помощью механизма наследования образовывать дочерние классы, которые будут включать все то, что определено в базовом классе Properties. Такой подход позволяет исключать дублирование в тексте программы и следовать общепринятому принципу:

DRY – Don’t Repeat Yourself

Причем, при создании объектов классы Line, Triangle и остальные будут представляться единым целым с базовым классом Properties. И, разумеется, у каждого объекта будет своя копия этого базового класса.

В общем случае механизм наследования можно воспринимать как расширение функциональности базового класса через дочерние классы.

Инкапсуляция

Следующий кит ООП носит «умное» название – инкапсуляция. Но за этим умным словом скрывается очень простая идея: возможность скрывать внутри класса его элементы, чаще всего – это переменные (поля) и функции (методы). Давайте конкретизируем этот момент. Предположим, в классе Triangle имеется еще одна переменная id – идентификатор объекта. Это значение формируется в момент создания объекта и не должно в последствии меняться. Для этого это поле id можно указать скрытым и тогда доступ к нему извне класса будет невозможен:

То есть, мы разрешаем использовать координаты объекта, но запрещаем обращаться к переменной id. В этом и есть смысл инкапсуляции: управление уровнем доступа к элементам класса. Конечно, когда мы говорим «доступ», то подразумевается внешний доступ к переменным и методам класса. Изнутри, любая функция по-прежнему может использовать все имеющиеся элементы.

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

Полиморфизм

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

В рамках ООП полиморфизм часто проявляется в переопределении методов базового класса. Например, в рамках нашего графического редактора в базовом классе Properties можно определить метод draw(), который будет отвечать за рисование графических примитивов. Затем, этот метод переопределить в дочерних классах и тогда, вызывая draw() по ссылке на базовый класс, будет вызываться переопределенный метод дочернего класса:

То есть, мы, используя единый интерфейс – метод draw(), имеем возможность рисовать самые разные графические примитивы. В этом сила третьего кита под названием полиморфизм.

Вот эти три момента: наследование, инкапсуляция и полиморфизм совместно с определением классов и созданием объектов, образуют общую концепцию ООП. Эта концепция соблюдается практически во всех современных языках программирования:

С++, С#, Python, Java

И популярность этого подхода не случайна. Он существенно упрощает проектирование и разработку больших приложений. А объектами могут быть самые разные элементы программы. Фактически, класс – это фрагмент программы, который можно многократно использовать и частично модифицировать под свои конкретные нужды. На этом принципе основаны многочисленные библиотеки: начиная от обычных, вычислительных, и заканчивая графическими и игровыми движками. Без ООП современная разработка больших программ была бы куда сложнее и запутаннее. И, конечно, далее на наших занятиях мы поближе познакомимся с миром классов и объектов на примере языка Java.

Видео по теме