Учебник по программированию.

Первые шаги. Язык программирования PascalABC.

 

Предыдущий параграф Назад в содержание Следующий параграф


Глава VI.

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


§26. Принципы ООП. Инкапсуляция.

Принципы ООП.

Объектно-ориентированное программирование совокупность  трёх основных принципов построения программ:

  • инкапсуляция соединение в одном объекте данных (полей этого объекта) и подпрограмм (методов), которые обрабатывают эти данные (поля);
  • наследование возможность создавать объекты (потомки) на основе других (предков);
  • полиморфизм возможность объектов предков использовать методы своих потомков.

Без данных принципов невозможно себе представить современное программирование в целом. Использование ООП выводит процесс программирования на несколько иной уровень логики. Код при этом становиться  более компактным и доступным для понимания. Сокращённо «объектно-ориентированное программирование» пишется ООП.

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

И так перейдём от общих слов и рассмотрим каждый принцип отдельно.


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

Как обычно пишут в программистской литературе, инкапсуляция объединение в одном объекте полей и методов.

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

Поля объекта какие либо его данные или параметры. Например, в модуле MMyach, полями мяча являются его цвет, радиус, скорости по осям, координаты его центра, а так же координаты его замкнутой области.

Методы подпрограммы, которые производят какие либо действия, в частности эти действия могут производить над полями объекта или с использованием его полей.

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

Например, опишем тип объекта Knopka. Полями данного объекта будут координаты, ширина и высота кнопки, а так же надпись на ней. Методами этого объекта будут процедуры рисования нажатой и не нажатой кнопки.

Сделаем небольшое отступление: в данном случае у нас должен получиться объект с именем Knopka. Но перед тем как мы его объявим нам необходимо описать тип нашего объекта, поэтому мы должны придумать идентификатор ещё и для типа. Для того, что бы не придумывать различные идентификаторы типу и объекту, можно типу присвоить такой же идентификатор с префиксом Т. В нашем случае получится тип TKnopka и объект этого типа с именем Knopka. При таком подходе, если нам понадобится два объекта такого типа, то мы их можем назвать Knopka1 и Knopkа2. Такой принцип реализован, например, в языке программирования Delphi. На мой взгляд, это удобно т.к. читая чужую программу, вы будете знать, к какому типу принадлежит тот или иной объект.

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


type

  TKnopka = class

    x, y: integer;//Координаты кнопки

    Shir, Vis: integer;//Ширина и высота соответственно

    Nadpis: string;//Надпись на кнопке

    procedure Draw;//Процедура рисования кнопки

    begin

      Rectangle(x,y,x+Shir,y+Vis);

      TextOut(x+Round(Shir/2-TextWidth(Nadpis)/2),

                 y+Round(Vis/2-TextHeight(Nadpis)/2),Nadpis);

    end;

    procedure DrawNeNaj;//Процедура рисования не нажатой кнопки

    begin

      SetBrushColor(clLightBlue);

      Draw;

    end;

    procedure DrawNaj;//Процедура рисования нажатой кнопки

    begin

      SetBrushColor(clBlue);

      Draw;

    end;

  end;


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

Здесь стоит остановиться и сказать несколько слов по поводу слова class. В данном коде мы привели описание типа объекта. Логично было бы предположить, что для описания объекта должно использоваться слово object, а не class. В старом Паскале так и было сделано, однако мы программируем на языке PascalABC, и здесь используется слово class. Сделано так потому, что язык PascalABC ориентирует начинающего программиста на дальнейшее изучение платформы Microsoft.NET, а там как раз для описания типа объекта используется слово class.

На мой взгляд, использование слова class при описании типа объекта более правильно, нежели слово object. Представьте себе, что мы имеем целую библиотеку описаний различных объектов. На основе этих описаний мы можем создавать объекты, которые будут выполнять определённые функции в наших программах. Однако на практике, для решения специфичных задач, готовые описания берутся лишь за основу для создания новых, которые наследуют все свойства старых и приобретают новые. Поэтому уместно говорить, что мы имеем библиотеку готовых классов, а не объектов. Отсюда и появилось само слово класс. В переводе с английского одно из значений этого слова  это род, сословие. Т.е. когда мы описываем определённый класс, мы описываем определённый род, от которого могут в дальнейшем появиться новые. Старый класс, который берётся за основу, называется предком, а новый потомком. В этом как раз состоит принцип инкапсуляции.

Такие готовые библиотеки существуют на самом деле. В каком-то смысле, можно сказать, что Microsoft.NET и есть библиотека готовых классов. Используя такую библиотеку можно создавать объекты, принадлежащие к различным уже готовым классам и использовать их в своих программах.

Надеюсь, из всего вышесказанного вам понятно, почему для описания типа объекта используется слово class, а не слово object. Далее в тексте книги сочетание слов «описание типа объекта» использоваться не будет. Это сочетание использовалось для того, что бы было понятнее о чём идет речь. Вместо этого сочетания будет использоваться сочетание «описание класса», которое более точно формулирует данный аспект программирования. Здесь же стоит сказать, что идентификатор типа класса будем называть просто именем класса. Например, в нашем случае идентификатор TKnopka будет именем. Когда речь будет идти об этом классе, мы будем говорить класс TKnopka.

Итак, вернёмся к нашему объекту. Мы произвели описание класса TKnopka. Теперь можем объявить переменную данного типа Knopka:


var Knopka;


В данной строчке мы объявили переменную типа TKnopka, однако таким объектом в программе пользоваться нельзя. Объект вначале необходимо создать. Для его создания необходимо в теле программы выполнить операцию new:


Knopka:=new TKnopka;


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

Итак, подытожим вышесказанное: для того, что бы объектом можно было пользоваться в программе, необходимо вначале объявить переменную типа нужного нам класса, а затем присвоить ей значение созданного операцией new объекта. Т.е. операция new создаёт объект определённого класса, и этот объект можно присвоить переменной типа такого же класса. Только после этого объект готов к работе.

Так же объект можно создать сразу при объявлении переменной:


var Knopka:TKnopka:=new TKnopka;


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


  Knopka.x:=1;

  Knopka.y:=1;

  Knopka.Shir:=70;

  Knopka.Vis:=20;

  Knopka.Nadpis:='Кнопка';

  Knopka.DrawNeNaj;


Так же доступ к полям и методам можно осуществить, используя слово with:


  with Knopka do

    begin

      x:=1; y:=1;

      Shir:=70; Vis:=20;

      Nadpis:='Кнопка';

      DrawNeNaj;

    end;


В результате работы такой программы на экране появится следующее:



Обратите внимание, использование объекта кнопка очень похоже на использование модулей M1, M2 и M3 из программы TriMyacha одного из предыдущих параграфов. Так как эти модули абсолютно одинаковые, различные только названия, то для обращения к переменным или подпрограммам этих модулей использовалась так же точка. Для наглядности приведу вырезку из кода программы TriMyacha:


  M1.X1:=10;M1.X2:=630;M1.Y1:=10;M1.Y2:=470;

  M2.X1:=10;M2.X2:=630;M2.Y1:=10;M2.Y2:=470;

  M3.X1:=10;M3.X2:=630;M3.Y1:=10;M3.Y2:=470;


В случае с объектами получим следующий код:


............................................

var Knopka_1:TKnopka:=new TKnopka;

    Knopka_2:TKnopka:=new TKnopka;

    Knopka_3:TKnopka:=new TKnopka;


begin

  Knopka_1.x:=1;    Knopka_1.y:=1;  Knopka_1.Shir:=70;  Knopka_1.Vis:=20;

  Knopka_1.Nadpis:='Кнопка_1';       Knopka_1.DrawNeNaj;

  Knopka_2.x:=80;   Knopka_2.y:=1;  Knopka_2.Shir:=70;  Knopka_2.Vis:=20;

  Knopka_2.Nadpis:='Кнопка_2';       Knopka_2.DrawNeNaj;

  Knopka_3.x:=160;  Knopka_3.y:=1;  Knopka_3.Shir:=70;  Knopka_3.Vis:=20;

  Knopka_3.Nadpis:='Кнопка_3';       Knopka_3.DrawNeNaj;

end.


Если мы в программе используем модули, то такой подход называется модульным программированием. Если используем объекты объектно-ориентированным программированием. Оба похода могут быть вами использованы при программировании, однако ООП имеет множество преимуществ, о которых вы узнаете по мере изучения данного параграфа. Если рассматривать приведённые выше примеры, то явным преимуществом ООП является то, что нет необходимости создавать для каждого объекта отдельный файл. Достаточно один раз описать класс и потом можно создавать сколь угодно много одинаковых объектов.

Есть ещё одна схожая черта модуля и объекта. При загрузке программы, если в ней подключён модуль, то прежде чем начнёт работать основанная программа, выполняется код, который описан в модуле в разделе инициализации. При создании объекта, так же можно произвести какие либо действия. Сделать это можно в подпрограмме, которая называется конструктором. Эта подпрограмма вызывается всегда, когда объект создаётся с помощью операции new. Данная подпрограмма описывается при описании класса так же как обычные процедуры и функции, только вместо слова procedure или function используется слово constructor.

Например, опишем конструктор в классе TKnopka, который будет устанавливать поля и рисовать кнопку на экране:


...................

type

  TKnopka = class

    ...................

    constructor Create;

    begin

      x:=1; y:=1;

      Shir:=70; Vis:=20;

      Nadpis:='Кнопка';

      DrawNeNaj;

    end;

    ...................

  end;


var Knopka:TKnopka:=new TKnopka;


begin

end.


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

Так же как и в любую подпрограмму, в конструктор можно передать какие либо параметры. Например, в нашем случае можно передать положение кнопки и надпись на ней:


...................

type

  TKnopka = class

    ...................

   constructor Create(cx,cy:integer;cNadpis:string);

    begin

      x:=cx; y:=cy;

      Shir:=70; Vis:=20;

      Nadpis:=cNadpis;

      DrawNeNaj;

    end;

     ...................

  end;


var Knopka:TKnopka:=new TKnopka(1,1,'Кнопка');


begin

end.


Результат работы такой программы будет такой же, как и в предыдущем случае.

Обратите внимание, что параметры передаются после слова TKnopka  в скобках. На этом аспекте стоит  немного остановиться. Выше по тексту, было сказано, что объект создаётся операцией new. Это не совсем верно. На самом деле операция new вызывает конструктор класса, который и создаёт объект. Т.е. любой объект создаётся конструктором. Если конструктор не был описан программистом, то он будет создан автоматически, и все поля класса при создании конструктором будут установлены в нулевые значения. Так же стоит сказать, что тело конструктора в любом случае создаётся автоматически, и если программист описал конструктор при описании класса, то код написанный программистом будет добавлен в то, что будет создано автоматически.

Из вышесказанного вы должны уяснить только то, что объект создаётся конструктором, в который вы можете дописать нужный вам код, который будет выполнен при создании объекта.

Так как объект создаётся конструктором, то для его создания можем использовать несколько другой код:


var Knopka:TKnopka:=TKnopka.Create(1,1,'Кнопка');


Получается, что объекты можно создавать двумя способами. Первый способ, с помощью операции new, является заимствованным из языка C#. Второй способ, с помощью явного вызова конструктора, является заимствованным из языка ObjectPascal. Первый способ является предпочтительней, т.к. PascalABC написан на платформе Microsoft.NET. Если вы в дальнейшем планируете изучать эту платформу и работать в Microsoft Visual Studio, то пользуйтесь первым способом. Если планируете изучать Delphi, то пользуйтесь вторым. Различия в работе программы вы не заметите.

Так же стоит сказать то, что в PascalABC конструктор должен всегда иметь имя Create, поэтому при его описании, это имя можно не указывать:

  

    ...........................

    constructor (cx,cy:integer;cNadpis:string);

    begin

      x:=cx; y:=cy;

      Shir:=70; Vis:=20;

      Nadpis:=cNadpis;

      DrawNeNaj;

    end;

    ...........................


Далее в примере продемонстрировано преимущество использования конструктора при создании трёх кнопок:


............................................

var Knopka_1:TKnopka:=new TKnopka(1,1,'Кнопка_1');

    Knopka_2:TKnopka:=new TKnopka(80,1,'Кнопка_2');

    Knopka_3:TKnopka:=new TKnopka(160,1,'Кнопка_3');


begin

end.


Как видите, использование ООП делает код компактнее и нагляднее.

Плюсом ко всему выше сказанному стоит сказать, что любое поле класса может быть доступно в любом методе этого каласса, так же любой метод можно вызвать из любого другого метода. В отличие от структуры основной программы, метод, описанный ниже, может быть доступен в методе, который описан выше.

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


program TriKnopki;

uses

  GraphABC;


type

  TKnopka = class

    x, y: integer;//Координаты кнопки

    Shir, Vis: integer;//Ширина и высота соответственно

    Nadpis: string;//Надпись на кнопке

    constructor Create(cx,cy:integer;cNadpis:string);

    begin

      x:=cx; y:=cy;

      Shir:=70; Vis:=20;

      Nadpis:=cNadpis;

      DrawNeNaj;

    end;

    procedure Draw;//Процедура рисования кнопки

    begin

      Rectangle(x,y,x+Shir,y+Vis);

      TextOut(x+Round(Shir/2-TextWidth(Nadpis)/2),

                    y+Round(Vis/2-TextHeight(Nadpis)/2),Nadpis);

    end;

    procedure DrawNeNaj;//Процедура рисования не нажатой кнопки

    begin

      SetBrushColor(clLightBlue);

      Draw;

    end;

    procedure DrawNaj;//Процедура рисования нажатой кнопки

    begin

      SetBrushColor(clBlue);

      Draw;

    end;

    function Najatie(mx,my:integer):boolean;

    begin

      if (mx>x) and (mx<(x+Shir)) and (my>y) and (my<(y+Vis)) then

        begin

          Najatie:=true;

          DrawNaj;

        end;

    end;

  end;


var Knopka_1:TKnopka:=new TKnopka(1,1,'Кнопка_1');

    Knopka_2:TKnopka:=new TKnopka(80,1,'Кнопка_2');

    Knopka_3:TKnopka:=new TKnopka(160,1,'Кнопка_3');


procedure MouseDown(x,y,mb:integer);

procedure DrawText(s:string);

begin

  SetBrushColor(clWhite);

  TextOut (100,40,'Нажата '+s+' кнопка');

end;

begin

  if mb=1 then

    begin

      if Knopka_1.Najatie(x,y) then DrawText('первая');   

      if Knopka_2.Najatie(x,y) then DrawText('вторая');   

      if Knopka_3.Najatie(x,y) then DrawText('третья');   

    end;

end;


procedure MouseUp(x,y,mb:integer);

begin

  Knopka_1.DrawNeNaj;

  Knopka_2.DrawNeNaj;

  Knopka_3.DrawNeNaj;

  SetBrushColor(clWhite);

  FillRect(100,40,240,57);

end;


begin

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

end.


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


В данном параграфе мы познакомились с принципами ООП. Научились создавать объекты и пользоваться ими в программе. А так же узнали, что такое конструктор.


Задача.

Переписать программу TriMyacha из 24 параграфа с использованием принципов ООП. Т.е. оформить мяч не в виде отдельного модуля, а в виде объекта. Усовершенствовать программу, добавив в неё ещё один мяч. Организовывать счёт столкновений и предусматривать возможность производить какие-либо действия при столкновении не надо. В программе просто должны прыгать четыре мяча и отталкиваться друг от друга. Главное, что бы был создан класс TMyach для создания мячей. Цвет, радиус и начальные параметры движения мячей должны выбираться случайным образом в конструкторе класса TMyach.


Решение.


unit MClassMyach;


uses GraphABC;


//Константы для хранения направления движения

Const L=1;//Движение влево

      P=2;//Движение вправо

      V=3;//Движение вверх

      N=4;//Движение вниз


Var  X1,X2,Y1,Y2:integer;//Координаты замкнутой области


type

  TMyach = class

    dVy:=1;//Изменение скорости относительно оси Y

      r,//Радиус мяча

    x,y,//Координаты мяча

//Скорости движения относительно оси Х и Y соответственно

    Vx,Vy:integer;

//направление движения по оси X и по оси Y соответственно

    naprX,naprY:byte;

    col:Color;//Цвет мяча

    constructor ;

      begin

        r:=Random(10,60);

        x:=Random(X1+r,X2-r); y:=Random(Y1+r,Y2-r);

        naprX:=Random(1,2); naprY:=Random(3,4); 

        Vx:=Random(2,10); Vy:=Random(4,20);

        var RandomColor:=Random(1,5);

        case RandomColor of

          1:col:=clRed;

          2:col:=clYellow;

          3:col:=clGreen;

          4:col:=clOrange;

          5:col:=clBrown;

        end;

        Draw;

      end;

    procedure Draw;//Рисуем мяч

      begin

        SetBrushColor(col);//Рисуем мяч

        FillCircle(x,y,r-1);//Рисуем мяч

      end;

    procedure Clear;//Стираем мяч

      begin

        SetBrushColor(clWhite);//Рисуем мяч

        FillCircle(x,y,r);//Рисуем мяч

      end;

    procedure Koordinati;//Вычисляем координаты x,y

      begin

      //Координаты по оси X

        case naprX of

          P:begin

              x:=x+Vx;

              if x>=(X2-r) then //Если достигли правой стенки 

                begin

                  naprX:=L;

                  x:=(X2-r)-(x-(X2-r));

                end

            end;

          L:begin

              x:=x-Vx;

              if x<=(X1+r) then //Если достигли левой стенки

                begin

                  naprX:=P;

                  x:=(X1+r)+(X1+r-x);

                end

            end;

        end;

      //Координаты по оси Y

        case naprY of

          V:begin

             Vy:=Vy-dVy;

             if Vy<=0 then //Мяч достиг мёртвой точки и начал падать вниз

               begin

                naprY:=N;

                if Vy<0 then //Если скорость получилась меньше нуля

                  begin

                    if (y-(dVy+Vy))>=(Y1+r) then //Если мяч не долетел до

                                                 //потолка

                       y:=y-(dVy+Vy)+Abs(Vy)

                      else //Мяч всё-таки долетел до потолка и от него

                           //оттолкнулись

                       y:=(Y1+r+1)+((dVy+Vy)+Abs(Vy))-(y-(Y1+r+1));

                  end;

               end

              else //Мяч либо летит вверх,либо долетел до потолка и

                   //оттолкнулся от него

               begin

                 y:=y-Vy;

                 if y<=(Y1+r+1) then//Мяч долетел до потлка и оттолкнулся

                   begin

                     naprY:=N;

                     if y>=0 then//Если мяч не вылетел за пределы экрана

                         y:=(Y1+r+1)-y+(Y1+r+1)

                       else//Мяч вылетел за пределы экрана

                         y:=Abs(y) + 2*(Y1+r+1);

                   end;

               end;

            end;

          N:begin

             Vy:=Vy+dVy;

             y:=y+Vy;

             if y>=(Y2-r-1) then//Если достигли пола

               begin

                naprY:=V;

                if y>(Y2-r-1) then//Если упали ниже пола

                   y:=y-2*(y-(Y2-r-1));

               end;

            end;

        end;

      end;

  end;


begin

//Устанавливаем координаты замкнутой области

  X1:=10;  X2:=630;  Y1:=10;  Y2:=470;

//Отключаем рисование непосредственно на экране

  LockDrawing;

end.

=========================================================================

unit MStolknovenie;

uses MClassMyach;


//Процедура изменяет координаты мячей при их столкновении

procedure IzmeneniyeKoordinatPriStolknovenii(var M1,M2:TMyach);

var S:real;//Расстояние между центрами мячей

    Vst1,Vn1,Vst2,Vn2,b:real;//Составляющие скоростей и угол бетта

    x1r,x2r,y1r,y2r,//Реальные координаты центров мячей

    Vx1r,Vy1r,Vx2r,Vy2r:real;//Реальные проекции скоростей после уточнения


function Ugol(Vx,Vy:real):real;

begin

  if (Vx>0) and (Vy>=0) then Ugol:=ArcTan(Vy/Vx);

  if (Vx=0) and (Vy>0) then Ugol:=Pi/2;

  if ((Vx<0) and (Vy>=0)) or ((Vx<0) and (Vy<0)) then Ugol:=Pi+ArcTan(Vy/Vx);

  if (Vx=0) and (Vy<0) then Ugol:=3*Pi/2;

  if (Vx>0) and (Vy<0) then Ugol:=2*Pi+ArcTan(Vy/Vx);

end;


//Процедура определяет составляющую скорости участвующую в столкновении,

//А так же нормальную составляющую

procedure VStolknoveniya(Vx,Vy:real;var Vst,Vn:real);

var V,a,fi:real;//Углы альфа и фи

begin

  V:=Sqrt(Vx*Vx+Vy*Vy);

  a:=Ugol(Vx,Vy);

  fi:=a-b;

  Vn:=V*Sin(fi);

  Vst:=V*Cos(fi);

end;


//Процедура определяет проекции скорости на оси Х и Y

procedure VozvratKProekciyam(Vst,Vn:real;var Vx,Vy:real);

var V,a,g:real;//Основная скорость, углы альфа и гамма

begin

  V:=Sqrt(Vst*Vst+Vn*Vn);

  g:=Ugol(Vst,Vn);

  a:=g+b;

  Vx:=V*Cos(a);

  Vy:=V*Sin(a);

end;


begin

//Определяем произошло ли столкновение

  if M1.x<>M2.x then

    begin

      b:=ArcTan((M1.y-M2.y)/(M1.x-M2.x));

      S:=abs(M1.x-M2.x)/Cos(Abs(b));

    end

   else

    begin

      b:=Pi/2;

      S:=Abs(M2.y-M1.y);

    end

  if S >(M1.r+M2.r) then exit;


//Переводим параметры в реальные

  if M1.naprx=P then Vx1r:=M1.Vx else Vx1r:=-M1.Vx;

  if M2.naprX=P then Vx2r:=M2.Vx else Vx2r:=-M2.Vx;

  if M1.NaprY=N then Vy1r:=M1.Vy else Vy1r:=-M1.Vy;

  if M2.NaprY=N then Vy2r:=M2.Vy else Vy2r:=-M2.Vy;

  x1r:=M1.x; x2r:=M2.x;

  y1r:=M1.y; y2r:=M2.y;


//Уточнение координат 

  var cDoley:=30;

  var dVx1:=Vx1r/cDoley;

  var dVx2:=Vx2r/cDoley;

  var dVy1:=Vy1r/cDoley;

  var dVy2:=Vy2r/cDoley;

  var count:integer;//счётчик проходов

//Предыдущие значения

  var x1rPred,x2rPred,y1rPred,y2rPred,bPred,SPred:real;

  count:=0;

//Возвращаем мячи обратно

  repeat

    Inc(count);

    x1rPred:=x1r;  x2rPred:=x2r;  y1rPred:=y1r;  y2rPred:=y2r;

    x1r:=x1r-dVx1; x2r:=x2r-dVx2; y1r:=y1r-dVy1; y2r:=y2r-dVy2;

    bPred:=b;      SPred:=S;

    if x1r<>x2r then b:=ArcTan((y1r-y2r)/(x1r-x2r)) else b:=Pi/2;

    if b<>Pi/2 then S:=Abs(x1r-x2r)/Abs(Cos(b)) else S:=Abs(y1r-y2r);

  until (M1.r+M2.r)<=S;

//Если мячи вернули слишком, то берём предыдущие значения

  if Abs(S-(M1.r+M2.r))>Abs(SPred-(M1.r+M2.r)) then

    begin  

      x1r:=x1rPred; x2r:=x2rPred; b:=bPred;

      y1r:=y1rPred; y2r:=y2rPred; Dec(count);

    end;


//Находим составляющую скорости участвующую в столкновении

  if (b<>Pi/2) then

    begin

      VStolknoveniya(Vx1r,Vy1r,Vst1,Vn1);

      VStolknoveniya(Vx2r,Vy2r,Vst2,Vn2)

    end 

   else

    begin

      Vst1:=Vy1r; Vst2:=Vy2r

    end

 

//Изменение составляющей скорости, участвующей в столкновении


//Коэффициенты для решения квадратного уравнения и дискриминант

  var KA,KB,KC,D,

      KorenVst11,KorenVst12,//Два корня решения уравнений

      Vst12:real;//Составляющая первой скорости после столкновения

  KA:=M1.r+M1.r*M1.r/M2.r;

  KB:=-2*M1.r*(M1.r*Vst1+M2.r*Vst2)/M2.r;

  KC:=(M1.r*Vst1+M2.r*Vst2)*(M1.r*Vst1+M2.r*Vst2)/M2.r-

                                    (M1.r*Vst1*Vst1+M2.r*Vst2*Vst2);

  D:=KB*KB-4*KA*KC;

  if D>0 then

    begin

      KorenVst11:=(-KB+Sqrt(D))/(2*KA);

      KorenVst12:=(-KB-Sqrt(D))/(2*KA);

      if abs(KorenVst11-Vst1)<0.001 then

         Vst12:=KorenVst12

        else

         Vst12:=KorenVst11;

    end

  if D=0 then

    begin

      Vst12:=-KB/2*KA;

    end;

  if D<0 then exit;//Т.к. в таком случае неизвестно как менять координаты


  Vst2:=(M1.r*Vst1+M2.r*Vst2-M1.r*Vst12)/M2.r;

  Vst1:=Vst12;


//Возвращаемся к проекциям на оси Х и Y

  if (b<>Pi/2) then

    begin

       VozvratKProekciyam(Vst1,Vn1,Vx1r,Vy1r);

       VozvratKProekciyam(Vst2,Vn2,Vx2r,Vy2r);

    end 

   else

    begin

       Vy1r:=Vst1; Vy2r:=Vst2

    end;


//Находим координаты мячей после столкновения

  M1.x:=Round(x1r+Vx1r*count/cDoley); 

  M2.x:=Round(x2r+Vx2r*count/cDoley); 

  M1.y:=Round(y1r+Vy1r*count/cDoley); 

  M2.y:=Round(y2r+Vy2r*count/cDoley); 

//Возвращаем реальные значения в целые

  M1.Vx:=Abs(Round(Vx1r));

  M2.Vx:=Abs(Round(Vx2r));

  M1.Vy:=Abs(Round(Vy1r));

  M2.Vy:=Abs(Round(Vy2r));

  if Vx1r<0 then M1.NaprX:=L else M1.NaprX:=P;

  if Vy1r<0 then M1.NaprY:=V else M1.NaprY:=N;

  if Vx2r<0 then M2.NaprX:=L else M2.NaprX:=P;

  if Vy2r<0 then M2.NaprY:=V else M2.NaprY:=N;

end;

end.

=========================================================================

program ChetireMyacha;

uses MClassMyach,MStolknovenie,GraphABC;


var Myach_1:TMyach:=new TMyach;

    Myach_2:TMyach:=new TMyach;

    Myach_3:TMyach:=new TMyach;

    Myach_4:TMyach:=new TMyach;


begin

  while true do

    begin

      Myach_1.Draw;       Myach_2.Draw;      

      Myach_3.Draw;     Myach_4.Draw;

      DrawRectangle(X1-1,Y1-1,X2+1,Y2+1);

      Redraw;

      Sleep(30);

      Myach_1.clear;      Myach_2.clear;     

      Myach_3.clear;    Myach_4.clear;

      Myach_1.koordinati; Myach_2.koordinati;

      Myach_3.koordinati; Myach_4.koordinati;

      IzmeneniyeKoordinatPriStolknovenii(Myach_1,Myach_2);

      IzmeneniyeKoordinatPriStolknovenii(Myach_1,Myach_3);

      IzmeneniyeKoordinatPriStolknovenii(Myach_1,Myach_4);

      IzmeneniyeKoordinatPriStolknovenii(Myach_2,Myach_3);

      IzmeneniyeKoordinatPriStolknovenii(Myach_2,Myach_4);

      IzmeneniyeKoordinatPriStolknovenii(Myach_4,Myach_3);

    end;

end.


Хочется сделать несколько замечаний по получившемуся коду.

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

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

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




Предыдущий параграф Назад в содержание Следующий параграф