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

 

 

Развитие технологии и языков программирования. История ООП  

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

Теперь программисты могли создавать программы длиной до нескольких тысяч строк длиной. Однако язык программирования, легко понимаемый в простых программах, когда дело касалось больших программ, становился нечитаемым (и неуправляемым). Избавление от таких неструктурированных программ пришло после изобретения в 1960 году языков структурного программирования (Алгол, Паскаль и С). Структурное программирование подразумевает точно обозначенные управляющие структуры, программные блоки отсутствие (или минимальное использование) операторов GOTO, автономные подпрограммы, в которых поддерживается рекурсия и локальные переменные. С появлением структурного программирования появилась возможность разбиения программы на составляющие ее элементы. Теперь уже один программист был в состоянии создать и поддерживать программу в несколько десятков тысяч строк диной.

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

Объект. Свойства и методы объекта

[к началу]

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

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

Объект состоит из следующих трех частей:
- имя объекта;
- состояние (переменные состояния);
- методы (операции).

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

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

Реализация методов (то есть, операций, выполняемых объектом), может быть задана различными способами. Однако это "внутренне дело" объекта.

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

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

По своему смыслу объект является представителем некоторой реальной сущности - реального объекта, процесса, ситуации, которая:

- поддается хранению и обработке;

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

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

 

Понятие класса

[к началу]

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

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

Каждый класс может иметь подклассы - классы, обладающие всеми или частью его свойств, а так же собственными свойствами. Класс, не имеющий ни одного представителя (объекта) обычно называют абстрактным.

Совокупность классов и их подклассов образует иерархию классов.

А теперь, несколько советов из книги Бьёрна Страуструпа (создателя языка C++):

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

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

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

 

Концепции ООП. Инкапсуляция, наследование и полиморфизм

[к началу]

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

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

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

Т.о., комбинирование структуры данных с функциями (действиями или методами), предназначенными для манипулирования данными, называется инкапсуляцией.

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

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

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

Наследование бывает двух видов:

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

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

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

Такой процесс классификации называется таксономией. Это хорошая начальная метафора для понимания механизма наследования в ООП.

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

Когда характеристика определена, все категории ниже этого определения включают эту характеристику. Поэтому, когда Вы идентифицируете насекомое как члена отряда двукрылых (мухи), то Вам не нужно указывать, что муха имеет пару крыльев. Вид "муха" наследует эту характеристику из своего отряда.

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

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

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

Теперь настало время привести еще один совет (в дополнение к двум уже приведенным ранее) из книги Страуструпа:

"... если у двух классов есть некая общая часть, пусть она будет базовым классом. В вашей программе многие классы будут иметь нечто общее; создайте (почти) универсальный базовый класс - к его разработке отнеситесь тщательнее всего".

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

Полиморфизм

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

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

Полиморфизм в языке С++ применим к функциям и к операциям (имеются в виду операции типа +, ==, [ ] и др.).

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

Позднее связывание

Несмотря на то, что три перечисленных принципа называют "тремя китами ООП", сами по себе они не имеют смысла без наличия особого механизма, названного поздним (динамическим) связыванием. Приведем пример.

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

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

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

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

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

Объектно-ориентированное программирование базируется на трех принципах и одном механизме: инкапсуляция, наследование, полиморфизм и позднее связывание.

Объектно-ориентированный подход к программированию подразумевает выделение общих свойств у различных объектов и широчайшее применение абстракций всех уровней.

 

Интерфейсные объекты (компоненты) и событийное программирование в Delphi

История Delphi начинается с 60-х годов, когда профессор Н.Вирт разработал язык высокого уровня Pascal. Это был лучший язык для изучения программирования, и для создания программ для операционной системы MS-DOS. Затем, в 1983 году, А. Хейлсберг совместно с другими программистами, которые только что организовали компанию Borland, разработал компилятор Turbo Pascal, который стал следующим шагом в эволюции Delphi. Затем появился Object Pascal, который уже использовал Объектно-Ориентированный подход к программированию. Когда появилась первая версия Windows – Windows 3.10, Программисты Borland создали Delphi 1. Это уже была объектно-ориентированная среда для визуальной разработки программ, основанная на языке Object Pascal.

С появлением Windows 95 появилась Delphi 2, затем Delphi 3, 4, 5. Язык программирования Object Pascal, который являлся стержнем Delphi, претерпел такие существенные изменения, что с появлением Delphi 6 компания Borland, которая уже превратилась в корпорацию, официально объявила о переименовании Object Pascal в Delphi. Поэтому правы те, кто говорит, что Delphi – это визуальная среда разработки программ. Но также правы и те, кто утверждает, что Delphi – это один из лучших языков программирования.

Основу Delphi составляет не только сам язык, но и RAD (Rapid Application Development)среда быстрой разработки программ. Благодаря визуальному программированию, а также достаточно большой библиотеке визуальных компонентов, Delphi позволяет создавать программы наиболее быстро и эффективно, принимая на себя основную работу, и оставляя программисту творческий процесс.

Визуальные компоненты

Для создания интерфейса приложений система Delphi предлагает обширный набор визуальных компонентов.

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

К оконным элементам относятся, например, командные кнопки, поля редактирования, полосы прокрутки. Для оконных элементов управления базовым классом являе TWinControl — прямой потомок класса TControl.

Для неоконных элементов управления базовым является класс TGraphicControl, производимый непосредственно от класса TControl. Неоконные элементы управления не могут получать фокус ввода и быть родителями других интерфейсных элементов. Достоинством неоконных элементов управления по сравнению с оконными является меньшее расходование ресурсов, т. к. для них не нужен дескриптор окна. Неоконным элементом управления являются, например, компонент Label.

Свойства компонентов

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

События

Визуальные компоненты способны генерировать и обрабатывать достаточно большое число (несколько десятков) событий различных видов, таких как:

Реакцией на событие должно быть какое-либо действие. В Delphi реакция на событие реализуется как процедура обработки события, что и составляет идею событийного программирования. Таким образом, для того, чтобы программа выполняла некоторую работу в ответ на действия пользователя, программист должен написать процедуру обработки соответствующего события. Следует обратить внимание, на то, что значительную часть обработки событий берет на себя компонент. Поэтому программист должен разрабатывать процедуру обработки события только в том случае, если реакция на событие отличается от стандартной или не определена. Например, если по условию задачи ограничений на символы, вводимые в поле Edit, нет, то процедуру обработки события onKeyPress писать не надо, т. к. во время работы программы будет использована стандартная (скрытая от программиста) процедура обработки этого события.