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

Думаю, что количество книг по объектно-ориентированному программированию перевалило за сотни, а может и тысячи. До сих пор остается классическим труд Гради Буча «Объектно — ориентированный анализ и проектирование с примерами приложений на С++». Вы наверняка сможете найти этот труд на просторах интернета.
Также можно посмотреть в Интернете другие ссылки и статьи — их очень много. Например тут — Объектно-ориентированное программирование или тут — Введение в программирование на Java. Второй ресурс содержит введение в язык Java. Весьма неплохо читается, так что отнеситесь к нему с вниманием.

Я попробую описать идеи ООП со своей колокольни. Сама идея появления очень простая: сложность программ растет. Человек уже давно разработал весьма несложное идеологическое решение этого вопроса — декомпозицию, т.е. разделение задачи на крупные блоки, потом на более мелкие, еще мельче и т.д.
По большому счету из этого понимания и родилось процедурное программирование. Программа представляля собой последовательный вызов процедур, которые принимали какие-то параметры на вход, отдавали какие-то параметры. Когда мы были студентами, то практически все писали подрограммы числового интегрирования, построения графиков и прочая.
Но программы становились еще сложнее. Причем с катострофической скоростью. В качестве примера могу привести статистику (правда она была сделана уже после появления ООП) — за 10 лет с 1990 по 2000 сложность программ выросла в 100 раз.
И идея, которую предложили разработчики языка Simula 67 оказалась великой. Позже на этих идеях были построены самые известные объектно-ориентированные языки, такие как Java, C++, C#, Smalltalk.

Объекты

В реальном мире нас окружают объекты. Мы живем в квартирах, катаемся на машинах, заказываем товары, оплачиваем счета, делаем заказы. Все это — объекты реального мира. Если вы помните — мы с самого начала ввязались в историю с объектом «робот». Он у нас должен был передвигаться, поворачиваться. А значит писать программы, которые использовали бы именно такой подход будет удобно.
Но что важно отметить — если вы помните, в самом начале я описал две задачи, которые решает программист. (Введение). Для решения обеих задач подход ООП в огромном количестве случаев прекрасно подходит. Описать задачу на обычном языке удобно в объектах. И перевести задачу, описанную в объектах, на язык программирования, который также поддерживает объекты — опять таки удобно. Наверно это является ключевым, поэтому я повторю свою мысль еще раз — в ООП важно понимать, что во-первых, описание задачи удобно вести в объектах. И переводить описание в язык программирования с объектами — тоже удобно. Давайте порассуждаем немного в отрыве от языков программирования — просто попробуем потеоретизировать.

Классы

Развивая идею «при программировании задачи удобно использовать объекты» мы переходим к следующему шагу — объекты удобно классифицировать. В вашей программе может быть много тех же роботов — список замечательных роботов, каждый из которых умеет делать одно и тоже. Мы можем их распределить по нескольким точкам и начать рисовать сразу десятки квадратов. Еще более может впечатлять идея создать описания объектов типа «воин» и начать сражение сил добра и зла :).
Причем я не просто так привел такой пример — если вы создаете объект, то вы получаете механизмы, чтобы научить этот объект выполнять какие-то операции. Можно научить нашего «воина» оценивать ситуацию вокруг него, принимать решения и выполнять определенные действия. Нападать, защищаться, передвигаться. А теперь представьте, что вы создали описание такого объекта в виде шаблона. И под этот шаблон создали программно штук 500-600 таких «воинов». Каждый «воин» обладает набором параметров — координаты, положение рук и ног, возможно качество брони, набор оружия. Поместив их на поле боя мы теперь можем заставить их сражаться. Причем они будут это делать уже по сути независимо от нас. Не надо писать сложный программный код для управления этими «воинами». Обычный цикл — оценить, принять решение, начать двигаться, рассчитать новые координаты. И все наше поле боя рассчитывается само собой. Остается только нарисовать воинов по уже готовым координатам.
Добавление новых воинов не создаст никаких сложностей — лишь мощность процессора надо прибавить. Мы просто каждого просим делать в цикле определенный набор операций до тех пор, пока он жив. Оценили удобство ?

Теперь давайте посмотрим внимательно на наши рассуждения — мы создавали объекты по одному образу и подобию. Мы говорим о том, что мы создали образец, чертеж, тип или КЛАСС. Именно понятие «класс» принято в ООП. Программист разрабатывает класс (Class) и потом программа создает объекты класса. Можно сказать, что мы, как инженеры, создаем чертеж машины, робота, «воина», а потом создаем объекты по этому чертежу. Вернее программа их создает.
И тут надо отметить принципиальное отличие класса и объекта. Класс — это описание, объект — это реальное воплощение.
Это различие порождает еще одну важную мысль: например робот как класс (описание, чертеж) вряд ли требует понятия местоположения (координаты робота). Но для реального робота это важно. Теперь давайте подумаем, что наш робот может быть достаточно интеллектуальным — например, он может быть заброшен на Марс. В этом случае алгоритм его работы потребует знания координат. Заметьте — алгоритм требует координат. И эти координаты будут уникальны для каждого робота, находящегося на Марсе (вдруг мы туда десант забросим).
Потихонечкау мы пришли к некоторым важным выводам:

  1. Объект/класс обладает переменными, которые характеризуют его состояние (например, координаты). Не всегда параметры нужны и классу и объекту.
  2. Объект/класс умеет что-то делать (например двигаться, оценивать ситуацию и прочая)

 

Пример класса на Java

Напишем несложный класс с подробным описанием всего того, что мы в нем увидим. Давайте сразу делать это в рамках проекта на NetBeans. Для этого создаем простой проект. Назовем его Robot1. Запускаем NetBeans и делаем то, что описывалось раньше — выбираем пункт File -> New Project. Далее выбираем простой проект и устанавливаем параметры, которые показаны на рисунке.

У нас сразу будет создан класс RobotManager, который мы указали в строке «Create Main Class». Пока не будем обращать на него внимания. Он нам потребуется немного позже. Сейчас создадим новый класс Robot.
Для этого выберем пункт меню File -> New File. Вы сможете увидеть, что там есть комбинация Ctrl+N а также видна иконка кнопки на верхней панели. В следующий раз можно использовать их. Перед нами откроется окошко в котором нам надо выбрать Categories: java и File Types: Java Class. Далее нажимаем «Next».

На следующем экране устанавливаем имя класса (Robot). Обратите внимание на сообщение внизу. Оно просит о том, чтобы класс был помещен в пакет. На данный момент будем игнорировать это сообщение, но мы обязательо вернемся к нему, когда будем разбирать пакеты. А пока просто нажимаем «Finish». (Забегая немного вперед — помещать класс в пакет хорошо. А класс без пакета — не хорошо. Но на данный момент можно).

После создания класса мы увидим вот такой код.

Есть еще один способ создать новый класс. Для этого щелкните правой кнопкой мыши на проекте на панели Projects и в выпадающем меню выберите пункт New -> Java Class.
Итак, мы создали класс Robot. Он пока ничего не умеет делать и не содержит никаких параметров, но тем не менее это полноценный класс. И объекты этого класса можно создавать и что-нибудь с ними делать. (Забегая немного вперед скажу, что даже в таком состоянии класс уже многое умеет, но об этом мы поговорим несколько позже).
А пока давайте расширим возможности нашего класса — сделаем для нашего робота поля, которые будут хранить его координаты и курс — поля x, y и course. Сделать это несложно — мы описываем переменные как показано ниже. (Я убрал лишние комментарии и добавил более полезные).

Это весьма простой класс. Мало того, он не полный. В нем нет очень важной составляющей — наш класс ничего не умеет делать. Сейчас у него есть просто три параметра: координаты X и Y, а также курс. Мало того — все параметры изначально равны нулю.
Но здесь мы видим как можно (и нужно) определять параметры класса. А теперь попробуем немного поработать с нашим классом Robot. Откройте в редакторе описание класса RobotManager. Для этого его надо просто найти слева в панели Projects и дважды щелкнуть левой кнопкой мыши. Обратите внимание на идею — класс RobotManager будет управлять объектами Robot. Название класса не является обязательным — здесь важно «увидеть» взаимодействие объектов. Перед нами класс RobotManager.

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

<тип> имя;

Это же правило распространяется и на классы. Давайте объявим две переменные типа Robot.

И вот тут начинается путь к отличиям переменных типа класс от элементарной переменной. Когда мы с вами объявляли элементарную переменную, мы сразу получали готовый кусочек памяти, куда можно было поместить число, символ или логическое значение. С классами не так. То, что мы с вами объявили Robot r1; не создает реальный объект типа Robot. В данном случае переменные r1, r2 только ССЫЛКИ (указатели). Прежде чем использовать их, нам надо создать объект и указать на него ссылкой. Выглядит это вот так:

Robot r1 = new Robot();

Обратите внимание на эту строку — она является очень важной. Мы используем ключевое слово new для того, чтобы СОЗДАТЬ ОБЪЕКТ.
Т.е. при объявлении переменной типа Robot (или другого класса) мы не создаем объект — мы объявляем ссылку на объект. КОТОРЫЙ НАМ НАДО ЕЩЕ СОЗДАТЬ. Если этого не сделать, то ссылка будет указывать в никуда — для этого есть даже специальное слово — null.

И вот только после создания объектов наши ссылки указывают на них. И только теперь нашими объектами можно пользоваться.
Если немного углубиться в механизм ссылок и объектов, то он достаточно сильно напоминает механизм указателей в C++. В области памяти, которая называется «куча» (heap) выделяется память, в которой находятся данные объекта. А ссылка указывает на эту область памяти. У тех. кто знаком с C++ или Delphi может возникнуть вопрос: «Если память выделена, то ее надо будет очищать?». Так вот в Java существует специальный механизм «Сборщик мусора» (garbage collector), который делает это автоматически. На данный момент мы не будем погружаться в его тонкости — просто имейте в виду, что создавать объекты вам надо самим, а удалять их из памяти вам не требуется. Позже мы подробнее остановимся на этом вопросе.

Оператор обращения к полю класса

Давайте еще расширим наши знания. Только что мы с вами расширили наш класс переменными, которые содержат значения координат и курса. Нам необходим механизм, который позволит обращаться к этим полям. Выгляит он не просто, а … очень просто. Вот так: «.». Да-да, самая простая точка. Давайте рассмотрим на примере.

Как можно видеть, обращение к полю X объекта, на который указывает ссылка r1, осуществляется через обычную точку. Я специально достаточно долго расписываю «обращение к полю X объекта, на который указывает ссылка r1» — я считаю очень важным моментом понимание механизма ссылок. В принципе он не сложный, но хорошо понимать его очень важно.
А теперь рассмотрим еще один пример:

Чем он важен? Самое главное в нем — увидеть, что мы создали ДВА объекта. На один указывает сылка r1, на другой — r2. Также не менее важно увидеть, что для КАЖДОГО объекта мы отдельно устанавливаем поле X. И у КАЖДОГО объекта это поле имеет свое значение. Иными словами — каждый объект имеет свой личный набор полей, которые описаны в классе.

Только что мы посмотрели. как объявлять поля в классе. Выполнили эту фразу: «Объект/класс обладает переменными, которые характеризуют его состояние». Теперь обратим наши взоры на вторую часть: «Объект/класс умеет что-то делать».
Для наделения объектов умением что-то делать существует механизм объявления методов класса. Метод можно определить как процедура/функция, которая определена в классе. Например мы можем определить метод, который «передвигает» нашего робота на какое-то количество метров вперед в соответствии с его курсом. В упрощенном варианте — меняет его координаты в соответствии с заданной дистанцией.
Если вы постараетесь вспомнить школьный курс тригонометрии, то для вас не составит труда понять эти формулы:

Поясним: координата X будет увеличиваться на дистанцию (distance), умноженную на косинус угла курса (course). Координата Y — дистанция на синус угла курса. Надеюсь, что это не вызывает больших вопросов. А теперь давайте сразу возьмем быка за рога — посмотрим код, который передвигает нашего робота на нужную дистанцию.

 

Давайте потихонечку разбираться в нашем коде. Как можно видеть мы изменили описание нашего робота. Теперь у него появились МЕТОДЫ. До этого мы с вами рассматривали только поля.
Метод — это действие, которое может производить объект. Вы можете найти и другие определения, но мне это нравится больше всего. Форма записи метода в очень простом виде выглядит вот так:

Наш робот теперь умеет делать следующее:

  1. Печатать текущие координаты — printCoordinates
  2. «Ехать» вперед на некоторое количество метров и рассчитывать новые координаты с учетом курса — forward(int distance)

Сначала рассмотрим печать координат — void printCoordinates().
Зарезервированное слово «void» означает, что метод ничего не возвращает. Мы позже посмотрим более подробно этот вопрос. А пока просто примем этот факт — если метод не должен ничего возвращать, то он предваряется словом «void». После типа идет имя метода. Синтаксис имени подобен синтаксису имени переменной. Лучше называть методы так, чтобы было понятно, что он делает.
Теперь обратим внимание на внутренню часть методы printCoordinates. Внутри метода мы выполняем вывод данных — System.out.println(x + «,» + y);. Но на что еще надо обратить внимание — мы обращаемся к переменным x и y. Причем в самом методе мы эти переменные не объявляли. Думаю, что многие из вас догадались — это поля, которые объявлены внутри класса. Т.е. внутри метода мы можем обратиться к полям. Причем обращаемся мы к полям того объекта, у которого вызываем метод. Сам вызов метода делается так же, как и обращение к полю — через оператор «.».

 

Теперь рассмотрим метод forward(int distance). В отличии от метода printCoordinates в методе forward мы передаем данные — параметр int distance. В теле метода мы опять же обращаемся к полям объекта и делаем вычисления новых координат. И используем переданный параметр по имени.

Наш робот конечно еще далек от совершенства, но тем не менее его уже можно использовать. Можно создать объект (отметим, что при создании его координаты устанавливаются в 0), попросить его проехать вперед и напечатать его текущие координаты. Пришла пора пообщаться с нашим роботом. Смотрим код:

Думаю, что вы уже видите, что мы из одного класса — RobotManager — управляем созданием и поведением объекта класса Robot. Просто внимательно смотрите код и читайте комментарии. Если мы запустим наш проект, то увидим вот такие строки

Если вы внимательно смотрели код, то думаю, что для вас результат не является неожиданностью.

Я выложил архив нашего первого примера здесь — Robot1. Вы можете его закачать и распаковать в директорию C:\JavaLessons. Для того, чтобы открыть проект в NetBeans вам надо выбрать пункт меню File -> Open Project. Посмотрите, что это также можно сделать с помощью комбинации клавиш Ctrl+Shift+O. В появившемся окне выберите папку Robot1 и нажмите «Open Project».

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

17 comments to ООП

  • javaNoob  says:

    Спасибо за доходчивое разъяснение.
    Ни для кого не секрет, что не смотря на всю простоту(в силу логичности) ООП парадигма не сразу осваивается.Хоть сто книжек на эту тему прочитай…А тут буквально парой Ваших предложений(своего виденья) картинка стала проясняться :). С удовольствием читаю Ваши статьи. И конечно буду задавать вопросы:)…

  • Дмитрий  says:

    Всем добрый день!
    Уважаемые администратор и ученики прошу Вас помочь с моим роботом, точнее с тем, что я с ним сделал. В качестве эксперимента я добавил в метод printCoordinates цикл for вот так:
    // Печать координат робота
    void printCoordinates() {
    System.out.println(x + «,» + y);
    for(double i=0; i<=x; i++){
    for(double k=0; k<=y; k++){
    System.out.print("*");
    }
    System.out.println();
    }
    И я никак не могу понять, почему у меня не получается треугольник???
    Всем заранее спасибо!

    • Grif  says:

      В указанном примере скорее всего получится прямоугольник со сторонами У*Х, потому что не соблюдён принцип построения треугольников (например — первая строка = 1 символ, каждая последующая стока длинее предыдущей).
      Поздний конечно комментарий, но вдруг кому-то пригодится.

      У меня всё получается замечательно, правда я использую другую среду разработки — IntelliJ IDEA 14.1.3

  • inefoy  says:

    Он не сложный

    • admin  says:

      Но он требует понимание ООП, которого к этому этапу пока нет.

  • Владимир  says:

    Ребята у меня вот так получается
    20.0,0.0
    0.0,3.9163938347251766E-14
    1.9581969173625883E-14,20.00000000000004
    Хотя делал все по уроку. Посоветуйте где не так.

  • Владимир  says:

    Разобрался

  • Alexander  says:

    Здравствуйте! Ссылка на «Введение в программирование на языке Java» не работает.

    • admin  says:

      Видимо устарела — исправил на другую.

  • sl  says:

    добрый день!
    попытался написать самостоятельно аналогичный код и сразу возник вопрос над которым долго мучался.
    в метод void forward(int distance) передаються несколько перемынных. при этом значения переменных присваиваються разнными способами:
    robot.forward(20) и robot.course = 45.
    правильно ли я понимаю,
    1) что выражения со скобками, в частности robot.forward(20), всегда вызывают методы класса???
    2) а переменные внутри скобок указываються, если они меняються очень часто при выполнении программы. а те переменные, которые меняються редко или не достаточно часто, объявляються стандартным способом robot.course = 90? (ведь можно было вызвать метод класса robot.forward(20,значение course))?

    • admin  says:

      Вы похоже не совсем правильно поняли.

    • StickOfFaith  says:

      Эти 2 способа работы с полями класса никак не связаны с частотой их использования. Почему автор сделал именно так — не знаю. Почитайте про инкапсуляцию и найдете ответ на первый вопрос. А ответ на второй гораздо проще. Использовать robot.forward(20,значение course) можно.

  • Юрий  says:

    x = x + distance * Math.cos(course / 180 * Math.PI);
    y = y + distance * Math.sin(course / 180 * Math.PI);

    Почему с помощью этих формул не получается вернуть робота обратно в центр?
    По идее, он у него должен быть курс 90°, потом 180°, потом 270° и он возвращается в начальные координаты x=0 и y=0.
    Я в каких-то других координатах живу? Помогите разобраться.

    • Юрий  says:

      Формулы неверные. Они в принципе не дают возможности получить отрицательное значение. А отрицательные значения должны быть, чтобы кординаты уменьшались. Жаль я не математик, не могу правильные формулы вот так навскидку написать)

      • admin  says:

        Я не вижу Вашего кода. Формулы верные — математика там еще школьная.


        double distance = 10.0;
        double course = 0.0;
        double x = 0.0;
        double y = 0.0;
        for (int i = 0; i < 4; i++) { x = x + distance * Math.cos(course / 180 * Math.PI); y = y + distance * Math.sin(course / 180 * Math.PI); System.out.println("X:" + x + ", Y=" + y); course += 90; }

        Результатом выполнения этого кода получается следующее
        X:10.0, Y=0.0
        X:10.0, Y=10.0
        X:0.0, Y=10.000000000000002
        X:-1.8369701987210296E-15, Y=1.7763568394002505E-15

        Последняя строка имеет числа в минус 15-й степени - это практически нулевое значение.

        • Юрий  says:

          Спасибо за ответ. Я был не прав, все ок с формулами. Математика школьная, в этом вся и проблема, в школе я даже на выпускном выбрал сдавать геометрию, любил ее. Но это было 22 года назад и все это время я ей не пользовался)) надо вспоминать!
          Подскажите, как округлить ответ до сотых?
          Нашел, что бывает printf, но в данном случае у меня не работает. Ошибку выдает. Я мешаю мух с котлетами?

          public class Robot {

          double x = 0;
          double y = 0;
          double course = 0;

          void printCoordinates() {
          System.out.printf(«X = » + » %.2f\n»,x + «, » + «Y = » + y);
          }

          void forward(int distance) {

          x = x + distance * Math.cos(course / 180 * Math.PI);

          y = y + distance * Math.sin(course / 180 * Math.PI);

          }

          }

          • admin  says:

            Такой код работает нормально.
            System.out.printf(«x=%.2f, y=%.2f\n\r», x, y);

            Общее замечание — когда пишите, что выдает ошибку, лучше указывать какую именно.
            А то это равносильно ставить диагноз по фразе «Доктор, у меня голова болит, чем я болен ?».

Leave a reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.