Главная » Статьи » Программирование » С/С++

Этапы разработки программного проекта

Этапы разработки программного проекта

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

Основными этапами разработки являются:

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

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

Реализация (кодирование). Результатом выполнения данного этапа являются программы проекта на языках программирования.

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

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

 

1.2. Стили разработки проекта

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

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

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

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

 

1.3. Модульное программирование

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

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

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

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

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

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

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

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

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

  • проверка правильности применения модулей только на основе знания их спецификаций;
  • установление соответствия спецификации и реализации модулей. Модули служат также целям создания проблемно-ориентированного контекста и локализации машинной зависимости.

 

1.4. Структурное программирование

Стиль структурного программирования регламентирует выполнение этапов проектирования, кодирования и тестирования программ. В структурном программировании важны форма представления программы и дисциплина ее разработки. Оно нацелено на повышение продуктивности программирования за счет повышения надежности программ. Одним из главных способов повышения надежности является улучшение структуры программы, что позволяет понимать, сопровождать и модифицировать ее даже без участия автора. Модификация программ в процессе их сопровождения и адаптации неизбежна, а стоимость этой модификации обычно в 3-5 раз превышает стоимость создания программы.

Структурный стиль особенно актуален при разработке крупных программных проектов большими коллективами разработчиков.

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

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

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

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

Вторым важнейшим принципом структурного программирования является структурное кодирование (на языке программирования). Оно имеет два аспекта:

  • структурирование (разбиение) программы на компоненты структурирования;
  • структурность применяемых в программе управляющих конструкций (операторов).

Рассмотрим эти вопросы подробнее.

1.4.1. Нисходящее проектирование

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

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

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

Рассматриваемое здесь понятие модуля правильнее ассоциировать с понятием подпрограммы в языках программирования.

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

1.4.2. Планирование кодирования и тестирования программ

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

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

  • иерархическая модель: сначала кодируются и тестируются все модули одного уровня, затем следующего уровня и т.д.
  • операционная модель: кодируются и тестируются все модули одной ветви иерархии, затем следующей ветви и т.д.

В каждой из этих моделей используется понятие "заглушка".

Заглушкой называется некоторый простой модуль, подменяющий настоящий модуль на время тестирования вызывающего его модуля и имити­рующий в какой-либо мере его исполнение (например, поставляющий некоторые фиксированные данные или просто сообщающий о своем исполнении).

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

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

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

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

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

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

1.4.3. Структурирование программы

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

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

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

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

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

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

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

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

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

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

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

 

1.4.4. Структурность управляющих конструкций

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

 

 

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

 

1.5. Проектирование представления данных

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

На "правильное представление данных" влияют следующие факторы:

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

 

1.5.1. Выбор типа для скалярного данного

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

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

Наиболее ярко такой негативный эффект выполнения операций проявляется в программах, вычисляющих с типом integer разные характеристики в графической системе координат. Несмотря на небольшие значения этих координат (0 <=Х <640, 0 <= Y<350), значения промежуточных результатов (например, расстояние между двумя точками) могут выходить за границы диапазона типа integer (-32768.32767), в результате чего получаются неверные результаты. Другой характерный пример - вычисление произведения элементов последовательности целых чисел.

 

1.5.2. Выбор представления наборов данных

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

 

1.5.3. Представления наборов, данных в памяти

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

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

 

1.5.4. Хранение наборов данных

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

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

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

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

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

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

 

§2. ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ПОДХОД РАЗРАБОТКИ ПРОГРАММНЫХ СИСТЕМ

 

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

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

Накопление новых идей шло в течение примерно десятилетия, начиная с конца 60-х годов. К концу 70-х годов переход на новый уровень абстракции стал свершившимся фактом в академических кругах. Потребовалось еще около десяти лет, чтобы новые идеи проникли в промышленность.

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

Первым на необходимость структуризации систем по уровням абстракции указал Дейкстра. Идея инкапсуляции (скрытия информации) была высказана Парнасом. Позднее был разработан механизм абстрактных типов данных, который был дополнен Хоором в его теории типов и подтипов.

Считается, что первой полной реализацией абстрактных типов, данных в языках программирования является язык Симула, который в свою очередь опирается на языки Модула, CLU, Euclid и др.

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

Затем появились (и продолжают появляться) другие объектно-ориентированные языки, которые определяют современное состояние программирования. Наиболее распространенными из них стали Си++, CLOS, Эйффель, Java.

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

 

2.2. Понятие объекта. Реальные системы как системы взаимодействия объектов

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

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

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

Предположим, что мы должны разработать систему автоматизации банка. Для примера рассмотрим, как могла бы осуществляться операция снятия денег через банкомат (рис. 2.1). В данной операции задействованы три объекта: «клиент Иванов», «банкомат на Тверской» и «счет № 66579801», который открыт в данном банке у г-на Иванова. Подойдя к банкомату и засунув свою карточку, объект «клиент Иванов» посылает банкомату сообщение «Начать работать». Получив такое сообщение, банкомат высвечивает какое-нибудь сообщение на экране и запрашивает код доступа. «Банкомат на Тверской» посылает сообщение «клиенту Иванову»: «Сообщите идентификационный номер». Если идентификация прошла успешно, «клиент Иванов» просит выдать ему 1000 рублей. Он посылает сообщение банкомату, а тот в свою очередь посылает сообщение объекту «счет № 66579801». Приняв это сообщение, объект «счет № 66579801» проверяет, есть ли у него 1000 рублей, и, если есть, пересылает разрешение на снятие денег, одновременно уменьшая свой баланс на соответствующую сумму. Банкомат передает деньги, и на этом процедура заканчивается.

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

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

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

Описание в виде объектов позволяет вычленить компоненты системы. Те же самые объекты - «счет № 66579801» и «клиент Иванов» - будут участвовать и в другой операции, при которой клиент приходит в отделение банка для снятия или зачисления денег на счет.

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

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

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

Хотя из приведенного примера интуитивно ясно, что такое объект, дать его четкое и приемлемое для всех определение достаточно сложно. Объект - это слишком общее понятие. Града Буч дает следующее определение: «что-то, с чем можно оперировать. У объекта есть состояние, поведение и возможность отличить его от других объектов».

Немного другое определение объекта приводит Ивар Якобсон: «Объект - это сущность, способная сохранять свое состояние (информацию) и обеспечивающая набор операций (поведение) для проверки и изменения этого состояния».

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

 

2.3. Состояние объекта

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

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

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

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

Однако не все атрибуты объекта могут изменяться. У объекта «счет» есть номер, по которому он отличается от всех остальных счетов. В отличие от баланса атрибут «номер» не может изменяться. До тех пор, пока счет существует, он сохраняет свой номер. То же самое можно сказать об адресе банкомата, идентификационном коде клиента и т.д.

 

2.4. Идентификация объекта

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

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

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

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

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

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

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

Многие объектные модели предлагают встроенные методы идентификации объектов. При создании любого нового объекта ему присваивается уникальный идентификатор, отличающийся от идентификаторов всех остальных имеющихся в системе объектов. Форма этого идентификатора может быть различной. Это может быть число с достаточно большим диапазоном значений, чтобы хватило на все объекты, или адрес в универсальной системе адре­сации, например, URL для идентификации страниц в Интернете, или специальным образом генерируемое имя. Суть его не меняется. Зная идентификаторы двух объектов, всегда можно сделать вывод об их тождественности или не тождественности.

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

 

2.5. Интерфейс объекта

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

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

В нашем примере работы банкомата у объекта «счет № 66579801» имеются методы «снять деньги со счета» и «положить деньги на счет». Эти два метода и составляют интерфейс объекта. У объекта «клиент Иванов» имеется метод «сообщить свой код». У объекта «банкомат на Тверской» есть методы «начать работу», «принять деньги», «выдать деньги».

У объекта «счет» есть еще и атрибут - «баланс». Является ли атрибут частью интерфейса? Интерфейс - это внешнее описание объекта. При разработке банковской системы и, в частности, объекта «счет» мы решаем вопрос: является ли баланс информацией, необходимой другим объектам? Очевидно, что является. Что именно нужно знать другим объектам? Остаток денег на счете. В таком случае необходимо добавить еще один метод «сообщить остаток денег на счете» к объекту «счет», и его интерфейс теперь будет состоять из трех методов.

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

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

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

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

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

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

 

2.6. Время жизни объекта

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

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

Создание объектов, естественно, выполняется явно. При этом задавать, когда и какие объекты создаются, можно либо на стадии разработки, либо на стадии выполнения.

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

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

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

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

Создание Постоянных объектов принципиально отличается от сохранения и восстановления состояния объектов.

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

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

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

Со временем жизни и идентификацией объектов тесно связано еще одно понятие - понятие объектов первого и второго сорта или равноправия объектов в системе.

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

Если объект «эллипс» описывает фигуру, изображенную на экране монитора, то у него есть атрибут «цвет». Логично предположить, что цвет - это тоже объект, у которого есть свои внутренние атрибуты и методы (например, кодировка в системе «красный, си­ний, зеленый», метод «изменить цвет» и т.д.). Существует ли объект «цвет эллипса» независимо от самого эллипса? Скорее всего нет. При удалении эллипса и его цвет исчезнет из системы. Можно ли обратиться к объекту «цвет эллипса»? Это зависит от реализации, но вполне возможны обе ситуации - у цвета может быть (а может и не быть) уникальный идентификатор объекта. В любом случае «цвет эллипса» является объектом второго сорта.

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

 

2.7. Композиция объектов

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

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

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

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

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

Категория: С/С++ | Добавил: Алексей (15.10.2014)
Просмотров: 8442 | Теги: С/С++, Программа, Проект, программирование, С++ | Рейтинг: 0.0/0
Всего комментариев: 0
ComForm">
avatar