М. Бен-Ари - Языки программирования. Практический сравнительный анализ (2000) (1160781), страница 39
Текст из файла (страница 39)
(см. раздел 15.1).
Где находятся подпрограммы реализованного класса? Ответ состоит в том, что они могут быть реализованы где угодно, в частности в отдельном файле, который обращается к определению класса через включаемый файл. Операция разрешения контекста «::» идентифицирует каждую подпрограмму как принадлежащую конкретному классу:
// Некоторый файл
#include "Airplanes.h" // Содержит объявление класса
void Airplanes::new_airplane(const Airplane_Data & a, int & i)
{
…
}
void Airplanes::get_airplane(int i, Airplane_Data & a) const
{
….
}
int Airplanes::find_empty_entry()
{
…
}
Обратите внимание, что внутренняя подпрограмма find_empty_entry должна быть объявлена внутри (в закрытой части) класса так, чтобы она могла обращаться к приватным данным.
Пространство имен
Одним из последних добавлений к определению языка C++ была конструкция namespace (пространство имен), которая дает возможность программисту ограничить область действия других глобальных объектов так же, как это делается с помощью пакета в языке Ada. Конструкция, аналогичная use-предложению в Ada, открывает пространство имен:
namespace N1 {
void proc(); //Процедура в пространстве имен
};
namespace N2 {
void proc(); // Другая процедура
};
N1:: proc(), //Операция разрешения контекста для доступа
using namespace N1 ;
proc(); // Правильно
using namespace N2;
proc(); //Теперь неоднозначно
К сожалению, в языке C++ не определен библиотечный механизм: объявления класса могут использоваться совместно только через включаемые файлы. Группа разработчиков должна организовать процедуры для обновления включаемых файлов, отдавая предпочтение программным инструментальным средствам, чтобы оповещать членов группы о том, что две компиляции не используют одну и ту же версию включаемого файла.
13.6. Упражнения
1. Напишите главную программу на языке С, которая вызывает внешнюю функцию f с целочисленным параметром; в другом файле напишите функцию f с параметром с плавающей точкой, который она печатает. Откомпилируйте, скомпонуйте и выполните программу. Что она печатает? Попытайтесь откомпилировать, скомпоновать и выполнить ту же самую программу на языке C++ .
2. Напишите программу, реализующую абстрактный тип данных для очереди, и главную программу, которая объявляет и использует несколько очередей. Очередь должна быть реализована как массив, который объявлен в закрытой части пакета языка Ada или класса C++. Затем измените реализацию на связанный список; главная программа должна выполняться без изменений.
3. Что происходит, если вы пытаетесь присвоить одну очередь другой? Решите проблему, используя ограниченный приватный тип в языке Ada или конструктор копий (copy-constructor) в C++.
4. В языках С и C++ в объявлении подпрограммы имена параметров не обязательны:
C |
int func(int, float, char*);
Почему это так? Будут ли так или иначе использоваться имена параметров? Почему в языке Ada требуется, чтобы в спецификации пакета присутствовали имена параметров?
5. В языке Ada есть конструкция для раздельной компиляции, которая не зависит от конструкции пакета:
Ada |
procedure Main is
Global: Integer;
procedure R is separate; -- Раздельно компилируемая процедура
end Main;
separate(Main) --Другой файл
procedure R is
begin
Global := 4; -- Обычные правила области действия
end R:
Факт раздельной компиляции локального пакета или тела процедуры не влияет на область действия и видимость. Как это может быть реализовано? Требуют ли изменения в раздельно компилируемой единице перекомпиляции родительской единицы? Почему? Обратный вопрос: как изменения в родителе воздействуют на раздельно компилируемую единицу?
6. Раздельно компилируемая единица может содержать конструкцию, задающую контекст:
with Text_IO;
Ada |
procedure R is
…
end R;
Как это можно использовать?
7. Следующая программа на языке Ada не компилируется; почему?
package P is
type T is (А, В, С, D);
end Р;
Ada |
with P;
procedure Main is
X: Р.Т;
begin
if X = P. A then ...end if;
end Main;
Существуют четыре способа решить проблему; каковы преимущества и недостатки каждого из них: а) use-конструкция, б) префиксная запись, в) renames (переименование), г) конструкция use type в языке Ada 95?
Глава 14
Объектно-ориентированное программирование
14.1. Объектно-ориентированное проектирование
В предыдущей главе обсуждалась языковая поддержка структурирования программ, но мы не пытались ответить на вопрос: как следует разбивать программы на модули? Обычно этот предмет изучается в курсе по разработке программного обеспечения, но один метод декомпозиции программ, называемый объектно-ориентированным программированием (ООП), настолько важен, что современные языки программирования непосредственно поддерживают этот метод. Следующие две главы будут посвящены теме языковой поддержки ООП.
При проектировании программы естественный подход должен состоять в том, чтобы исследовать требования в терминах функций или операций, то есть задать вопрос: что должна делать программа? Например, программное обеспечение для предварительной продажи билетов в авиакомпании должно выполнять такие функции:
1. Принять от кассира место назначения заказчика и дату отправления.
2. Отобразить на терминале кассира список доступных рейсов.
3. Принять от кассира предварительный заказ на конкретный рейс.
4. Подтвердить предварительный заказ и напечатать билет.
Эти требования, естественно, находят отражение в проекте, показанном на рис. 14.1, с модулем для каждой функции и «главным» модулем, который вызывает другие.
К сожалению, этот проект не будет надежным в эксплуатации; даже для небольших изменений в требованиях могут понадобиться значительные изменения программного обеспечения. Для примера предположим, что авиакомпания улучшает условия труда, заменяя устаревшие дисплейные терминалы. Вполне правдоподобно, что для новых терминалов потребуется изменить все четыре модуля; точно так же придется вносить много исправлений, если изменятся соглашения о форматах информации, используемой совместно с другими компаниями.
Но все мы знаем, что изменение программного обеспечения чревато внесением ошибок; не устойчивый к ошибкам проект приведет к тому, что поставленная программная система будет ненадежной и неустойчивой. Вы могли бы возразить, что персонал должен воздержаться от изменения программного обеспечения, но весь смысл программного обеспечения состоит в том, что это именно программное обеспечение, а значит, его можно перепрограммировать, изменить; иначе все прикладные программы было бы эффективнее «зашить» подобно программе карманного калькулятора.
Программное обеспечение можно сделать намного устойчивее к ошибкам и надежнее, если изменить основные критерии, которыми мы руководствуемся при проектировании. Правильнее задать вопрос: над чем работает программное обеспечение? Акцент делается не на функциональных возможностях, а на внешних устройствах, внутренних структурах данных и моделях реального мира, т. е. на том, что принято называть объектами (objects). Модуль должен быть создан для каждого «объекта» и содержать все данные и операции, необходимые для реализации объекта. В нашем примере мы можем выделить несколько объектов, как показано на рис. 14.2.
Такие внешние устройства, как дисплейный терминал и принтер, идентифицированы как объекты, так же как и базы данных с информацией о рейсах и предварительных заказах. Кроме того, мы выделили объект Заказчик, назначение которого — моделировать воображаемую форму, в которую кассир вводит данные до того, как подтвержден рейс и выдан билет. Этот проект устойчив к ошибкам при внесении изменений:
• Изменения, которые вносят для того, чтобы использовать разные терминалы, могут быть ограничены объектом Терминал. Программы этого объекта отображают данные заказчика на реальный дисплей и команды клавиатуры, так что объект Заказчик не должен изменяться, а только отображаться на новые аппаратные средства.
• Перераспределение кодов авиакомпаний может, конечно, потребовать общей реорганизации базы данных, но что касается остальных частей программы, то для них один двухсимвольный код авиакомпании ничем не отличается от другого.
Объектно-ориентированное проектирование можно использовать не только для моделирования реальных объектов, но и для создания многократно используемых программных компонентов. Это непосредственно связано с одной из концепций языков программирования, которую мы подчеркивали, — абстрагированием. Модули, реализующие структуры данных, могут быть разработаны и запрограммированы как объекты, которые являются экземплярами абстрактного типа данных вместе с операциями для обработки данных. Абстрагирование достигается за счет того, что представление типа данных скрывается внутри объекта.
Фактически, основное различие между объектно-ориентированным и «обычным» программированием состоит в том, что в обычном программировании мы ограничены встроенными абстракциями, в то время как в объектно-ориентированном мы можем определять свои собственные абстракции. Например, числа с плавающей точкой (см. гл. 9) — это ничто иное, как удобная абстракция сложной обработки данных на компьютере. Хорошо было бы, если бы все языки программирования содержали встроенные абстракции для каждого объекта, который нам когда-нибудь понадобится (комплексные числа, рациональные числа, векторы, матрицы и т. д. и т. п.), но полезным абстракциям нет предела. В конечном счете, язык программирования нужно чем-то ограничить и оставить работу для программиста.
Как программист может создавать новые абстракции? Один из способов состоит в том, чтобы использовать соглашения кодирования и документирование («первый элемент массива — вещественная часть, а второй — мнимая часть»). С другой стороны, язык может обеспечивать такую конструкцию, как приватные типы в языке Ada, которая дает возможность программисту явно определить новые абстракции; эти абстракции будут компилироваться и проверяться точно так же, как и встроенные абстракции. ООП можно (и полезно) применять и в рамках обычных языков, но, аналогично другим идеям в про- граммировании, оно работает лучше всего, когда используются языки, которые непосредственно поддерживают это понятие. Основная конструкция для поддержки ООП — абстрактный тип данных, который обсуждался в предыдущей главе, но важно понять, что объектно-ориентированное проектирование является более общим и простирается до абстрагирования внешних устройств, моделей реального мира и т. д.
Объектно-ориентированное проектирование — дело чрезвычайно слож-ное. Нужны большой опыт и здравый смысл, чтобы решить, что же заслуживает того, чтобы стать объектом. Новички в объектно-ориентированном проектировании склонны впадать в излишний энтузиазм и делать объектами буквально все; а это приводит к таким перегруженным и длинным утомительным программам, что теряются все преимущества метода. Наилучшее интуитивное правило, на которое стоит опираться, — это правило упрятывания информации:
В каждом объекте должно скрываться одно важное проектное решение.
Очень полезно бывает задать себе вопрос: «возможно ли, что это решение изменится за время жизни программы?»
Конкретные дисплейные терминалы и принтеры, выбранные для системы предварительных заказов, явно подлежат обновлению. Точно так же решения по организации базы данных, вероятно, будут изменяться, чтобы улучшить эффективность, поскольку система растет. С другой стороны, можно было бы привести доводы, что изменение формы данных заказчика маловероятно и что отдельный объект здесь не нужен. Даже если вы не согласны с нашим проектным решением создать объект Заказчик, вы должны согласиться, что объектно-ориентированное проектирование — хороший общий подход для обсуждения проблем разработки и достоинств одного проекта перед другим.
В следующих разделах языковая поддержка ООП будет обсуждаться на примере двух языков: C++ и Ada 95. Сначала мы рассмотрим язык C++, который был разработан как добавление одной интегрированной конструкции для ООП к языку С, в котором нет поддержки даже для модулей. Затем мы увидим, как полное объектно-ориентированное программирование определено в языке Ada 95 путем добавления нескольких небольших конструкций к языку Ada 83, который уже имел много свойств, частично поддерживающих ООП.
14.2. Объектно-ориентированное программирование на языке C++