Программирование на языке высокого уровня Паскаль

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

Показывать лекцию целиком

Презентацию к данной работе Вы можете скачать здесь.

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

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

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

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

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

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

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

Скрытие деталей реализации называется инкапсуляцией (от слова "капсула"). Ничего сложного в этом понятии нет: ведь и в обычной жизни мы пользуемся объектами через их интерфейсы. Сколько информации пришлось бы держать в голове, если бы для просмотра новостей надо было знать устройство телевизора!

Таким образом, объект является "черным ящиком", закрытым по отношению к внешнему миру. Это позволяет представить программу в более укрупненном виде — на уровне объектов и их взаимосвязей, а следовательно, управлять большим объемом информации и успешно отлаживать более сложные программы.

Сказанное выше можно сформулировать более кратко и строго: объект — это инкапсулированная абстракция с четко определенным интерфейсом.

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

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

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

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

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

Подводя итог сказанному выше, сформулирую преимущества ООП:

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

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

    Описание объектов

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

    type имя = object
        [ private ]
            описание полей
        [ public ]
            заголовки методов
        end;

    Поля и методы называются элементами объекта. Их видимостью управляют директивы private и public. Ключевое слово private ( закрытые ) ограничивает видимость перечисленных после него элементов файлом, в котором описан объект. Действие директивы распространяется до другой директивы или до конца объекта. В объекте может быть произвольное количество разделов private и public. По умолчанию все элементы объекта считаются видимыми извне, то есть являются public ( открытыми ). Этим Паскаль отличается от других, более современных языков программирования.

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

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

    type monster = object
            procedure init(x_, y_, health_, ammo_ : word);
            procedure attack; 
            procedure draw; 
            procedure erase; 
            procedure hit;
            procedure move(x_, y_ : word);
         private
            x, y         : word;
            health, ammo : word;
            color        : word;
         end;

    В этом объекте пять полей данных. Поля x и y представляют собой местоположение объекта на экране, health хранит состояние здоровья, ammo — боезапас, а color — цвет. Поля описаны в разделе private, так как эта информация относится к внутренней структуре объекта.

    Допустим, что нашему герою придется перемещаться по экрану, атаковать другие объекты и терять при этом здоровье и оружие. Каждому действию соответствует свой метод. Методы описаны в разделе public (по умолчанию), потому что они составляют интерфейс объекта.

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

    Описание методов (текст подпрограмм) размещается вне объекта в разделе описания процедур и функций, при этом имени метода предшествует имя объекта, отделенное точкой, например:

    procedure monster.init(x_, y_, health_, ammo_ : word);
    begin
        x := x_; y := y_;
        health := health_;
        ammo   := ammo_;
        color  := yellow;
    end;
    procedure monster.draw;
    begin
        setcolor(color);    outtextXY(x, y, '@');
    end;

    Перед использованием объект требуется инициализировать, то есть присвоить начальные значения его полям и, возможно, выполнить другие подготовительные действия. В документации Borland Pascal процедуре инициализации объекта рекомендуется давать имя init.

    Обратите внимание, что поля объекта используются внутри методов непосредственно, без указания имени объекта.

    Процедура draw предназначена для вывода изображения объекта на экран. Для простоты примем, что изображение монстра представляет собой просто символ "собаки" @.

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

    Как правило, поля объекта объявляют как private, а методы как public, однако это не догма. Если поле объекта представляет собой свойство, которое концептуально входит в интерфейс объекта, и пользователь должен иметь право устанавливать и получать его без каких-либо ограничений, логично поместить его в раздел public. И наоборот: если метод предназначен только для вызова из других методов, его лучше скрыть от посторонних глаз в разделе private.

    Для реализации принципа инкапсуляции, то есть ограничения видимости элементов, объекты обычно описывают в модулях. Тип объекта определяется в интерфейсном разделе модуля, а тексты методов объекта — в разделе реализации.

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

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

    Описание объекта .

    unit monsters;
    interface
    uses Graph;
    type monster  = object
            procedure init(x_, y_, health_, ammo_ : word);
            procedure attack; 
            procedure draw; 
            procedure erase; 
            procedure hit;
            procedure move(x_, y_ : word);
        private
            x, y         : word;
            health, ammo : word;
            color        : word;
    end;
    implementation
    { ----------------------- реализация методов объекта monster  ------------------- }
    procedure monster.init(x_, y_, health_, ammo_ : word);
    begin
        x := x_; y := y_;
        health := health_;
        ammo   := ammo_;
        color  := yellow;
    end;
    procedure monster.attack; { ------------------------------- monster.attack ------ }
    begin
        if ammo = 0 then exit;
        dec(ammo); setcolor(color); outtextXY(x + 15, y, 'ба-бах!');
    end;
    procedure monster.draw;   { ------------------------------- monster.draw -------- }
    begin 
        setcolor(color); outtextXY(x, y, '@'); 
    end;
    procedure monster.erase;  { ------------------------------- monster.erase ------- }
    begin 
        setcolor(black); outtextXY(x, y, '@'); 
    end;
    procedure monster.hit;    { ------------------------------- monster.hit --------- }
    begin
        if health = 0 then exit;
        dec(health);
        if health = 0 then begin color := red; draw; exit; end;
        attack;
    end;
    procedure monster.move(x_, y_ : word); { ------------------ monster.move -------- }
    begin
        if health = 0 then exit;
        erase; x := x_; y := y_; draw;
    end;
    end.

    Для стирания изображения объекта с экрана от выводится поверх старого цветом фона (метод erase ). Перемещение объекта (метод move ) выполняется путем стирания на старом месте и отрисовки на новом.

    Атака (метод attack ) реализована схематично: если имеется ненулевой боезапас, он уменьшается на единицу, после чего выводится диагностическое сообщение. Если объект атакован (метод hit ), он теряет единицу здоровья, но атакует в ответ, а при потере последней единицы здоровья выводится на экран красным цветом. Объект, потерявший в сражении все здоровье, двигаться и атаковать не может.

    Экземпляры объектов

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

    var Vasia : monster;        { описывается статический объект }
        pm    : ^monster;       { описывается указатель на объект }
    ...
    new(pm);                    { создается динамический объект }

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

    uses graph, monsters;

    Доступ к элементам объекта осуществляется так же, как к полям записи: либо с использованием составного имени, либо с помощью оператора with.

    Vasia.erase; 
    with pm^ do begin init(100, 100, 30); draw; end;

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

    pm^.x := 300; { если бы x было public }

    Если объект описан в модуле, получить или изменить значения элементов со спецификатором private в программе можно только через обращение к соответствующим методам.

    При создании каждого объекта выделяется память, достаточная для хранения всех его полей. Методы объекта хранятся в одном экземпляре. Для того чтобы методу было известно, с данными какого экземпляра объекта он работает, при вызове ему в неявном виде передается параметр self, определяющий место расположения данных этого объекта. Фактически внутри метода обращение к полю x объекта имеет вид self.x. При необходимости имя self можно использовать внутри метода явным образом, например @self представляет собой адрес начала области, в которой хранятся поля объекта.

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

    В приведен пример программы, использующей модуль monsters.

    program test_monster;
    uses graph, crt, monsters;
    var Vasia  : monster;                                             { 1 }
        x, y   : word;
        gd, gm : integer;
    begin
        gd := detect; initgraph(gd, gm, '...');
        if graphresult <> grOk then begin
            writeln('ошибка инициализации графики'); exit end;
        Vasia.init(100, 100, 10, 10);                                 { 2 }
        Vasia.draw;                                                   { 3 }
        Vasia.attack;                                                 { 4 }
        readln;
        x := 110;
        while x < 200 do begin
            Vasia.move(x, x); inc(x, 7);                              { 5 }
            Vasia.hit;                                                { 6 }
            delay(200);
        end;
    readln;
    end.

    Для тестирования объекта monster в программе определена переменная Vasia типа monster (оператор 1). Для нее вызываются процедуры инициализации (2), отрисовки (3) и атаки (4), а затем перемещения по экрану (5) с имитацией попадания в объект при каждом перемещении (6). Таким образом проверяется работоспособность всех методов объекта.

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

    <$Mmonster _game>program dinner;
    uses graph, crt, monsters;
    const n = 30;
    var stado  : array [1 .. n] of monster;
        x, y   : array [1 .. n] of integer;
        gd, gm : integer;
        i, j   : word;
    begin
        gd := detect; initgraph(gd, gm, '...');
        if graphresult <> grOk then begin
            writeln('ошибка инициализации графики'); exit end;
        randomize;
        for i := 1 to n do begin
            stado[i].init(random(600), random(440), random(10), random(8));
            stado[i].draw;
        end;
        repeat
            for i := 1 to n do begin
                x[i] := random(600); y[i] := random(440);
                stado[i].move(x[i], y[i]);
            end;
            for i := 1 to n – 1 do
                for j := i + 1 to n do
                    if (abs(x[i] – x[j]) < 15) and (abs(y[i] – y[j]) < 15)
                    then begin
                        stado[i].hit; stado[j].hit;
                    end;
            delay(200);
        until keypressed;
    end.
    Вернуться к учебному плану