«Ни один ремесленник, который стремится к вершинам своей профессии, не примет негодных инструментов; и ни один производитель, который ценит качество работы, не будет упрашивать ремесленника принять их»
40. Краткий обзор концепций программирования
История программирования насчитывает уже полвека, если начинать отсчет с середины 50-х годов, когда появился первый язык программирования высокого уровня Fortran. Этот язык получил широкое распространение и используется до сих пор. За эти полвека технология программирования обогатилась целым рядом не только практических методов, но и продуктивных концепций, которые и будут предметом краткого анализа в данном разделе. Хочется думать, что этот обзор поможет лучше понять главные тенденции развития программирования.
К настоящему времени я бы выделил такие основные концепции программирования:
· процедурное программирование;
· модульное программирование;
· объектно-ориентированное программирование;
· модель компонентных объектов COM.
Порядок перечисления этих концепций соответствует и хронологии их зарождения. Этапу процедурного программирования предшествовала та далекая пора, когда еще не было языков программирования, имеющих подпрограммы. Та технология, которая использовалась до появления подпрограмм, могла бы быть названа непроцедурным программированием. В настоящее время трудно даже представить себе программу, написанную без использования подпрограмм. В качестве эксперимента попробуйте написать на каком-нибудь языке программу, которая вводит два числа с клавиатуры и выводит на монитор их сумму. Скажете, это просто? Но ведь использовать какие-либо стандартные подпрограммы нельзя, если придерживаться технологии непроцедурного программирования…
Отметим, что методологии разработки программных продуктов ранее и, тем более, в настоящее время уделяется очень большое внимание, но даже краткое освещение ее основных результатов выходит за рамки не только этого раздела, но и данной работы в целом. По этой причине мы не будем здесь обсуждать, например, CASE-технологии, а рассмотрим только те парадигмы программирования, которые лежат в основе всех современных средств проектирования ПП и относятся преимущественно к языковым средствам. При этом рассматриваемые концепции программирования присущи и реализуемы в разных языковых средах, в том числе в С++ и Object Pascal. Надо отметить, что в языке С++ присутствует более широкий спектр средств, предназначенных, в первую очередь, для целей структурирования ПП.
При процедурном программировании мы размышляем в первую очередь о действиях, которые необходимо выполнить, а затем о данных. При этом подходе предметная область решаемой задачи отображается на множество операторов (процедур) и операндов (данных). Например, если нам надо использовать в программе (для MS DOS) окно, то мы будем проектировать процедуры прорисовки и прятанья окна, вывода информации в окно и др., а затем начнем думать о том, где и как хранить данные, относящиеся к окну. По всей видимости, мы достаточно быстро придем к мысли о том, что данные целесообразно объединить в записи. Вследствие такого подхода нам удастся избежать того хаоса, который был присущ программе, не имеющей процедур. Достаточно представить себе программу, содержащую хотя бы 1000 строк (а это совсем не большая программа), чтобы осознать тот факт, что даже если такую программу и удастся когда-нибудь отладить, то уж модифицировать ее и устранять неизбежные ошибки на этапе сопровождения простым смертным не под силу.
Кстати, чтобы по достоинству оценить роль и значение процедур, возьмите любую программу и посмотрите, сколько в ней используется процедур и функций: стандартных и собственных. Код хорошо написанной программы будет на 90% состоять из вызовов подпрограмм.
Таким образом, процедурное программирование, как и следует из его названия, позволяет хорошо реализовать алгоритмы обработки данных, расчленив их на отдельные подпрограммы, каждая из которых выполняет свою четко определенную функцию. С другой стороны, все обрабатываемые данные нам придется описать в глобальной области видимости и позаботиться о том, чтобы каждая подпрограмма получила именно те данные, которые ей необходимы. Проблема оптимальной организация и структурирования данных в большой программе является не менее актуальной, чем оптимальная декомпозиция программы на процедуры.
Наличие большого объема глобальных данных, характерное для процедурного подхода, устраняется в модульном программировании, концепция которого как раз и появилась как средство защиты от постоянно возрастающих размеров программ, и, соответственно, количества, объема и многообразия подпрограмм и обрабатываемых ими данных. Модуль является такой структурной единицей программы, которая объединяет (инкапсулирует) в единое целое данные и процедуры их обработки.
Такие языковые средства как модули (unit) в Object Pascal или пространства имен (namespaces) в С++ не только позволяют воплотить в реальных ПП концепцию модульного программирования, но и позволяют решить проблему конфликта имен, присущую большим программам.
В том же примере с таким важным элементом интерфейса программы, как окно, было бы целесообразно поместить в один модуль все процедуры работы с окном и данные, которые к нему относятся. Такой – модульный – подход позволяет добиться по крайней мере двух основных преимуществ:
· возможность простого использования модуля в других программах, причем пользователю модуля нет необходимости передавать исходный код модуля: ему достаточно откомпилированного кода;
· сокращение объема глобальных данных программы и сокрытие данных в модулях, что повышает надежность программы. В программе на Object Pascal важные данные можно легко скрыть от пользователя модуля, описав их в секции реализации.
Наглядным примером разумного использования модульного подхода являются целые библиотеки модулей, поставляемые вместе с Delphi. Как известно, эти модули содержат не только процедуры и функции, но и описания классов, констант, типов и, иногда, небольшого числа переменных.
Концепция объектно-ориентированного программирования (ООП) базируется на понятии объекта (класса) как типа данных языка программирования. В «Словаре по кибернетике» ООП называется «программированием, основанным на декомпозиции программируемой задачи на объекты. Система программ, осуществляющая решение задачи, рассматривается как совокупность активных объектов, взаимодействующих путем посылки сообщений».
Замечание о терминологии. Объект как тип данных в языке Object Pascal принято называть, как и в С++, классом, а экземпляр объекта (переменную) – собственно объектом. |
Класс – определяемый программистом структурный тип, объединяющий данные и процедуры их обработки в единое целое. Упрощая понятие класса можно сказать, что он представляет собой такой структурный тип данных (тип record в Object Pascal и struct в С++), в который добавлены процедуры и функции. Данные класса часто называют его свойствами, а процедуры и функции – методами (обработки этих данных).
Следование концепции ООП предполагает выполнение такого анализа решаемой задачи, при котором, в первую очередь, идентифицируются объекты и их поведение. Для объектов необходимо выявить:
· данные, описывающие их свойства и состояние;
· функции, которые должны выполнять объекты;
· сообщения, которые могут получать объекты.
Таким образом, объект представляет собой структурный элемент программы, в котором локализована определенная часть знаний предметной области. Объект выполняет свои функции только в ответ на получение сообщений, благодаря чему ООП наиболее приспособлено для моделирования технических систем и систем с параллельными процессами.
В развитие предыдущих концепций ООП предлагает, в первую очередь, думать о классе как абстрактной модели реального или физического объекта. В отличие от концепции модуля как главного элемента модульного программирования, класс является, в первую очередь, типом, объединяющим данные и методы их обработки. Если вернуться к примеру с окном и сделать его классом, то данными класса будут размеры окна, его координаты, цветовые атрибуты и т.д., а методами – процедуры и функции, которые обеспечивают прорисовку окна, вывод в него данных и т.д.
Коль скоро класс является типом (данных), то для его использования в программе необходимо описать переменные этого типа, которые принято называть объектами или экземплярами класса. Отсюда следует, что разработка класса окно должна выполняться так, чтобы при необходимости использования в программе нескольких окон достаточно было просто объявить нужное число экземпляров класса, т.е. переменных. Каждый объект имеет свой собственный набор данных, а функции объекта всегда присутствуют в программе в одном экземпляре.
Таким образом, если модуль инкапсулирует все данные, например, данные о всех окнах программы, то каждый объект содержит данные только об одном – «своем» – окне и не имеет доступа к данным других объектов. Это обстоятельство является одним из важных преимуществ ООП по сравнению с модульным программированием.
Если инкапсуляция является первым принципом ООП, то его вторым важнейшим принципом является наследование. Наследование есть возможность порождать новые классы на основе уже существующих, причем порожденный класс (наследник) наследует все данные и методы родительского класса и может иметь (и обычно имеет) свои собственные.
Принцип наследования позволяет достичь двух важнейших целей.
Во-первых, это простой и удобный способ повторного использования кода. Достаточно часто разработка нового класса начинается не «с нуля», а базируется на расширении возможностей уже существующего класса. В этом случае разработчику нового класса нет необходимости разбирать «чужой» программный код – обычно он даже не имеет исходного кода, а располагает только спецификацией класса – ему необходимо реализовать в своем классе только недостающие методы или перекрыть те методы, функциональная возможность которых не отвечает поставленным требованиям.
Во-вторых, следование принципу наследования позволяет построить иерархию классов, которая отражает классификацию реальных или виртуальных объектов реального мира. Иерархия классов позволяет иметь методы и данные, совместно и единообразно используемые объектами всей иерархии. Использование третьего принципа ООП – полиморфизма – позволяет создавать, в пределах одной иерархии классов, полиморфные объекты.
Концепция, лежащая в основе модели компонентных объектов (COM – Component Object Model), будет рассмотрена в следующем разделе.
41. Предпосылки возникновения COM