Введение в теорию программирования. Объектно-ориентированный подход

         

Объектно-ориентированный подход к программированию


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

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

При объектно-ориентированном подходе программа представляет собой описание объектов, их свойств (или атрибутов), совокупностей (или классов), отношений между ними, способов их взаимодействия и операций над объектами (или методов).

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

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

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

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

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

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

Наиболее известным примером объектно-ориентированного языка программирования является язык C++, развившийся из императивного языка С. Его прямым потомком и логическим продолжением является язык С#, который изучается в данном курсе. Другие примеры объектно-ориентированных языков программирования: Visual Basic, Java, Eiffel, Oberon.

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

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

Зачастую в практических и учебных курсах по программированию слушатели не имеют четкого математического основания для формирования достаточно полного и ясного представления об основах ООП. Преимущество предлагаемого курса заключается в том, что уже изученные в первой части курса разделы computer science (например, ламбда-исчисление и комбинаторная логика) позволяют сформировать глубокое и точное понимание фундаментальных понятий объектно-ориентированного программирования. В частности, понятие абстракции – основной операции ламбда-исчисления – для нас является уже хорошо знакомым.

Поясним качественно фундаментальные принципы ООП. Наследование конкретных атрибутов объектов и функций оперирования объектами основано на иерархии.


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

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

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

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

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

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



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

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

  1. фреймовой нотации Руссопулоса (названной так по имени своего создателя, N.D. Roussopulos);
  2. диаграмм Хассе (получивших название по имени ученого, который впервые предложил этот способ наглядного представления наследования, H. Hasse).


Рассмотрим пример программы на языке программирования C#, иллюстрирующий концепцию наследования:

class A { // базовый класс int a; public A() {...} public void F() {...} }

// подкласс (наследует свойства //класса A, расширяет класс A) class B:A { int b; public B() {...} public void G() {...} }

Пример представляет собой описание базового класса A и производного от него класса B.

Класс A содержит целочисленный атрибут (т.е. переменную) a, а также два метода (т.е. функции), A() и F(). Класс B содержит целочисленный атрибут (т.е. переменную) b, а также два метода (т.е. функции) B() и G().

Двоеточие B:A в описании класса B означает наследование.

Отметим, что выше был рассмотрен простейший случай наследования, а именно, единичное наследование. Язык программирования C# позволяет реализовать механизмы, поддерживающие и более сложный случай наследования – множественное наследование.

Рассмотрим более подробно особенности наследования, которые реализует данный пример программы на языке C#.

Производный класс B наследует от базового класса А свойство a и метод F(). При этом к классу В добавляются собственные свойство b и метод G().

Заметим, что в отношении операции наследования справедливы следующие ограничения:



  1. конструкторы (т.е. функции создания и инициализации классов) не наследуются;
  2. в языке C# существует возможность замещения наследуемых методов (этот языковой аспект будет рассмотрен более подробно в ходе дальнейших лекций).


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

Один класс языка программирования C# может наследовать лишь свойства другого класса (но не структуры – типа данных, аналогичного кортежу языка программирования SML).

Структура не может наследовать свойства другого типа данных, однако может при этом реализовывать как один, так и несколько интерфейсов.

Подкласс с неявным базовым классом наследует свойства наиболее абстрактного класса, известного под названием "объект" (object).

Еще одним фундаментальным компонентом концепции объектно-ориентированного программирования является понятие инкапсуляции.

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

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

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

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




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

К основным свойствам инкапсуляции относятся следующие возможности:

  1. совместное хранение данных и функций (т.е. свойств и методов) внутри объекта;
  2. сокрытие внутренней информации от пользователя (что обеспечивает большую безопасность приложения);
  3. изоляция пользователя от особенностей реализации (что обеспечивает независимость от машины и потенциально дружественный интерфейс приложений).


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

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

Рассмотрим пример простейшей полиморфной функции:

void Poly(object o) { Console.WriteLine(o.ToString()); }

Данная функция реализует отображение на экране объекта (метод Console.WriteLine) с предварительным преобразованием его к строковому типу (метод ToString()) .

Все приведенные ниже варианты вызова функции:

Poly(25); Poly("John Smith"); Poly(3.141592536m); Poly(new Point(12,45));

успешно пройдут компиляцию и завершатся выдачей корректного результата.

Для более подробного самостоятельного ознакомления с тематикой лекции рекомендуется следующий список источников: [22, 38, 51, 53, 61, 63, 75].


Содержание раздела