Парадигмы программирования

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

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

Императивная (imperative paradigm), или процедурная, парадигма (procedural paradigm), представляет собой традиционный подход к процессу программирования. К этой парадигме относится наш псевдокод (см. главу 4), а также машинный язык (см. главу 2). Как можно понять из названия, императивная парадигма определяет процесс программирования как построение последовательности команд, которые манипулируют входными данными для порождения необходимого результата. То есть согласно этой парадигме сначала следует найти алгоритм решения задачи, а затем представить его в виде последовательности команд.

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

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

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

Функциональная парадигма (functional paradigm) рассматривает процесс разработки программы как соединение «черных ящиков», каждый из которых получает входные данные и порождает выходные данные так, чтобы создать необходимую зависимость между ними. Математики называют эти «ящики» функциями, именно поэтому подход и называется функциональным. Примитивы функционального языка программирования являются элементарными функциями, из которых можно построить более сложные функции, необходимые для решения некоторой задачи. Таким образом, программист, придерживающийся функциональной парадигмы, создает программное обеспечение, объединяя элементарные функции в систему, которая порождает необходимый результат. Проще говоря, процесс программирования сводится к построению сложных функций из более п;

Рассмотрим в качестве примера функцию, которая находит среднее арифметическое значение нескольких чисел (рис. 5.3). Одна элементарная функция, Sum, получает на входе список чисел и вычисляет их сумму. Другая функция, Count, получает список чисел и порождает значение, равное количеству элементов в этом списке. И третья функция, Divide, получает два значения и вычисляет их частное. Процесс создания функции можно записать на языке LISP следующим образом: (Divide (Sum Numbers) (Count Numbers)).

Гнездовая структура этого выражения отражает тот факт, что входные данные функции Divide являются выходными данными функций Sum и Count. Вот другой пример. Предположим, у нас есть функция Sort, которая сортирует список чисел

List, и функция First, которая извлекает из списка первый элемент. Тогда выражение

(First (Sort List))

извлекает наименьший элемент списка. Здесь гнездовая структура показывает, что выходные данные функции Sort являются входными данными функции Fi rst. То есть сначала список сортируется, а затем уже из упорядоченного списка извлекается первый элемент.

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

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

СкладА <— результат применения процедуры СталелитейныйЗавод к переменным Известняк.

МелезнаяРуда и Уголь:

СкладВ «- результат применения процедуры ЗаводДеталей к содежимому СкладаА:

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

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

Объектно-ориентированная парадигма (object-oriented paradigm) и соответствующее ей объектно-ориентированное программирование (ООП) представляют собой другой подход к процессу разработки программного обеспечения. Данные при этом подходе рассматриваются как активные «объекты», а не как пассивные единицы, представленные в обычной императивной парадигме. Рассмотрим список имен. В императивной парадигме этот список рассматривается просто как набор данных. Любая программа, пытающаяся получить доступ к списку, должна содержать алгоритм, выполняющий необходимые действия. Таким образом, список является пассивным, в том смысле, что он обслуживается управляющей программой, а не обслуживает себя сам. Однако при объектно-ориентированном подходе список рассматривается как объект, состоящий из самого списка и программ для манипуляции им. Это могут быть программы для помещения в список нового элемента, удаления элемента из списка, проверки наличия элемента в списке и сортировки списка. В свою очередь, программе, пытающейся получить доступ к списку, совсем не обязательно содержать алгоритмы для выполнения этих задач. Вместо этого она использует процедуры объекта. Можно сказать, что программа просит список отсортировать себя, а не сама сортирует его, как в императивной парадигме.

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

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

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

разработанного в рамках объектно-ориентированной парадигмы, часто лежит модель «клиент-сервер» (см. раздел 3.3). В данном случае сервер — это объект, который отвечает на сообщения другого объекта, являющегося клиентом.

Объектно-ориентированная парадигма занимает важное место в вычислительной технике, поэтому мы рассмотрим ее более подробно в разделе 5.5. Кроме того, мы еще встретимся с этой парадигмой в других главах книги. В частности, мы покажем влияние объектно-ориентированной парадигмы на разработку программного обеспечения (см. главу 6) и создание баз данных (см. главу 9), а в главе 7 мы увидим, что объектно-ориентированный подход к разработке программного обеспечения является естественным результатом изучения структур данных.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: