Парадигмы программирования.
В основе разделения языков программирования на поколения лежит линейная шкала. Позиция языка на этой шкале определяется тем, насколько пользователь свободен от ненужной информации и в какой степени данный язык позволяет программисту мыслить в понятиях, связанных с решаемой задачей. В действительности развитие языков программирования происходит не только в этом направлении, существуют и другие подходы к процессу программирования - парадигмы программирования. Поэтому историческое развитие языков программирования лучше изображать с помощью диаграммы (рис. 2.6). На этой диаграмме показано, что разные направления развития языков являются результатом разных парадигм (подходов), развивающихся независимо друг от друга. В частности, на рисунке изображены четыре направления, представляющие функциональную, объектно-ориентированную, императивную и декларативную парадигмы. Языки, относящиеся к каждой парадигме, расположены на временной шкале, показанной внизу (но из этого не следует, что один язык развивался из другого).[1]
Рис. 2.6. Эволюция парадигм программирования.
Следует заметить, что хотя парадигмы, изображенные на рисунке, и называются парадигмами программирования, их влияние выходит за рамки процесса программирования. Они представляют собой совершенно разные подходы к решению задач и, следовательно, ко всему процессу разработки программного обеспечения. В этом смысле термин «парадигмы программирования» употребляется неправильно. Здесь больше подходит термин «парадигмы разработки программного обеспечения».
Императивная, или процедурная парадигма, представляет собой традиционный подход к процессу программирования. К этой парадигме относится вышерассмотренный псевдокод, а также машинный язык. Императивная парадигма определяет процесс программирования как построение последовательности команд, которые манипулируют входными данными для порождения необходимого результата. Согласно этой парадигме сначала следует найти алгоритм решения задачи, а затем представить его в виде последовательности команд.
Декларативная парадигма позволяет программисту описывать задачу. Суть заключается в том, чтобы найти и выполнить алгоритм, решающий общую задачу. Как только этот общий алгоритм найден, задачи можно решать, просто формулируя их условия так, чтобы они были совместимы с этим алгоритмом. В такой среде программист должен точно сформулировать задачу, а не найти алгоритма решения.
Главной сложностью при разработке программного обеспечения на основе декларативной парадигмы является обнаружение лежащего в основе алгоритма. Поэтому первые декларативные языки были по своей сути специализированными и создавались для использования в определенных прикладных задачах. Например, декларативный подход многие годы применялся для имитации систем (экономических, физических, политических и т. д.) с целью проверки гипотез. В таком случае лежащий в основе алгоритм — это процесс воспроизведения хода времени с помощью повторяющегося вычисления значений параметров (валовой внутренний продукт, внешнеторговый дефицит и т. д.) из предыдущих значений. Таким образом, использование в таких моделях декларативного языка требует применения алгоритма, который выполняет эту повторяющуюся процедуру. Следовательно, перед программистом стоит единственная задача: описать зависимости между параметрами. Затем алгоритм просто имитирует ход времени, используя эти зависимости для необходимых вычислений.
Функциональная парадигма рассматривает процесс разработки программы как соединение «черных ящиков», каждый из которых получает входные данные и порождает выходные данные так, чтобы создать необходимую зависимость между ними. Математики называют эти «ящики» функциями, именно поэтому подход и называется функциональным. Примитивы функционального языка программирования являются элементарными функциями, из которых можно построить более сложные функции, необходимые для решения некоторой задачи. Таким образом, программист, придерживающийся функциональной парадигмы, создает программное обеспечение, объединяя элементарные функции в систему, которая порождает необходимый результат. Проще говоря, процесс программирования сводится к построению сложных функций из более простых (например, в Паскале sin(sqr(x)) ).
Функциональная парадигма представляет среду, в которой существует иерархия абстракций, и это позволяет создавать новое программное обеспечение из больших заранее заданных компонентов. Создание таких сред для разработки программного обеспечения является одной из главных задач в вычислительной технике.
Ниже приведены примеры запись команд на LISP, который относится к функциональным языкам:
1) (MAX_число1_число2_ ... числоN) - максимальное из чисел;
2) (+_число1_число2_ ... числоN) – сложение;
3) (SETQ_символ1_S-exp1_ .... символN_S-expN) - связывает имя со значением выражения.;
4) (EVAL_(/_(-_(*_ 2_7)_5)_2)) - вычисление значения выражения (2*7-5)/2;
5) (SETQ_f_1) (WHILE_(<_f_10)_(SETQ_f_(+_f_3))) – присваиваем переменной f значение 1 и увеличиваем переменную f на три, до тех пор, пока f меньше 10.
Объектно-ориентированная парадигма и соответствующее ей объектно-ориентированное программирование (ООП) представляют собой еще один подход к процессу разработки программного обеспечения. Данные при этом подходе рассматриваются как активные «объекты», а не как пассивные единицы, представленные в обычной императивной парадигме. Например, рассмотрим список имен. В императивной парадигме этот список рассматривается просто как набор данных. Любая программа, пытающаяся получить доступ к списку, должна содержать алгоритм, выполняющий необходимые действия (чтение списка и т.п.). Таким образом, список обслуживается управляющей программой. При объектно-ориентированном подходе список рассматривается как объект, состоящий из самого списка и процедур для манипуляции им. Это могут быть программы для помещения в список нового элемента, удаления элемента из списка, проверки наличия элемента в списке и сортировки списка. В свою очередь, программе, пытающейся получить доступ к списку, совсем не обязательно содержать алгоритмы для выполнения этих задач. Вместо этого она использует процедуры объекта. Можно сказать, что программа просит список отсортировать себя, а не сама сортирует его.
В качестве другого примера объектно-ориентированного подхода рассмотрим разработку графического пользовательского интерфейса. Здесь значки, которые появляются на экране, представляют собой объекты. Каждый из этих объектов включает в себя набор процедур, которые описывают, как этот объект должен отвечать на появление различных событий, например, таких как щелчок мыши на нем. Таким образом, вся система является набором объектов, каждый из которых отвечает на определенные события.
Преимущества объектно-ориентированного программирования заключаются в модульной структуре программы, которая является естественным следствием объектно-ориентированной философии. Каждый объект представляет собой отдельную, строго определенную единицу. После задания свойств объекта его можно использовать каждый раз, когда требуется такой объект. Сторонники объектно-ориентированного программирования также утверждают, что объектно-ориентированная парадигма предоставляет естественную среду для разработки программного обеспечения с помощью стандартных блоков. Они представляют собой библиотеки определений объектов, из которых можно создавать новое программное обеспечение точно так же, как сложные изделия собираются из готовых компонентов.
Другое преимущество модульной структуры состоит в том, что связь между модулями осуществляется строго определенным способом (обмен сообщениями между объектами) - этот же метод используется для организации связи по сети. На самом деле передача сообщений между объектами представляет собой естественный подход к разработке систем программного обеспечения, которые распределены по сети. Поэтому неудивительно, что в основе программного обеспечения, разработанного в рамках объектно-ориентированной парадигмы, часто лежит модель «клиент-сервер». В данном случае сервер – это объект, который отвечает на сообщения другого объекта, являющегося клиентом. Следует отметить, что процедуры объекта, описывающие как объект должен отвечать на различные сообщения, в сущности, представляют собой небольшие императивные программные единицы.
В объектно-ориентированном программировании данные вместе с процедурами хранятся в классе. Класс определяет общие для всех его объектов методы и свойства. Свойства представляют собой характеристики объекта (цвет, размер шрифта, название, положение на экране и т.п.). Методы – это программные процедуры, реализующие некоторый алгоритм, который определяет взаимодействие объектов класса с внешней средой. Объект, с одной стороны обладает определенными свойствами, а, с другой стороны, над ним возможны операции (методы), которые приводят к изменению этих свойств. Это свойство объединения в объекте его свойств и методов называется инкапсуляцией.
Понятие ООП включает также возможность наследования. Наследование - это возможность сопоставления с создаваемым классом одного или даже нескольких уже созданных классов в качестве родительских. Все члены родительских классов будут при этом являться и членами создаваемого класса, в котором они, обычно, переопределяются в соответствии с его характеристиками.
Наследование обеспечивает один из способов реализации третьего принципа ООП – полиморфизма, т.е. возможности использовать одноименные методы для выполнения однотипных действий у объектов разных классов (например, существует команда нарисовать объект, но для выведения объектов разной формы используются различные процедуры).
Объектно-ориентированные языки программирования позволяют сделать простым и понятным процесс создания интерфейса разрабатываемых приложений, поскольку при задании свойств графических объектов используются диалоговые окна. Взаимодействия программных объектом между собой и их изменение описывается с помощью программного кода (программы). [1, 8]