Бьерн Страуструп (947334), страница 70
Текст из файла (страница 70)
Этот пример подтверждает тот вывод, что классы в программе являются
конкретным воплощением понятий, используемых при проектировании,
поэтому нечеткие отношения между классами приводят к нечеткости
основных понятий проектирования.
12.1.4 Гибридный проект
Переход на новые методы работы может быть мучителен для любой
организации. Раскол внутри нее и расхождения между сотрудниками могут
быть значительными. Но резкий решительный переход, способный в одночасье
превратить эффективных и квалифицированных сторонников "старой школы"
в неэффективных новичков "новой школы" обычно неприемлем. В то же
время, нельзя достичь больших высот без изменений, а
значительные изменения обычно связаны с риском.
Язык С++ создавался с целью сократить такой риск за счет
постепенного введения новых методов. Хотя очевидно, что наибольшие
преимущества при использовании С++ достигаются за счет абстракции
данных, объектно-ориентированного программирования и
объектно-ориентированного проектирования, совершенно неочевидно,
что быстрее всего достичь этого можно решительным
разрывом с прошлым. Вряд ли такой явный разрыв будет возможен,
обычно стремление к усовершенствованиям сдерживается или должно
сдерживаться, чтобы переход к ним был управляемым. Нужно учитывать
следующее:
- Разработчикам и программистам требуется время для овладения
новыми методами.
- Новые программы должны взаимодействовать со старыми программами.
- Старые программы нужно сопровождать (часто бесконечно).
- Работа по текущим проектам и программам должна быть
выполнена в срок.
- Средства, рассчитанные на новые методы, нужно адаптировать к
локальному окружению.
Здесь рассматриваются как раз ситуации, связанные с перечисленными
требованиями. Легко недооценить два первых требования.
Поскольку в С++ возможны несколько схем программирования,
язык допускает постепенный переход на него, используя
следующие преимущества такого перехода:
- Изучая С++, программисты могут продолжать работать.
- В окружении, бедном на программные средства, использование С++
может принести значительные выгоды.
- Программы, написанные на С++, могут хорошо взаимодействовать
с программами, написанными на С или других традиционных языках.
- Язык имеет большое подмножество, совместимое с С.
Идея заключается в постепенном переходе программиста с
традиционного языка на С++: вначале он программирует на С++
в традиционном процедурном стиле, затем с помощью методов абстракции
данных, и наконец, когда овладеет языком и связанными с ним средствами,
полностью переходит на объектно-ориентированное программирование.
Заметим, что хорошо спроектированную библиотеку использовать намного
проще, чем проектировать и реализовывать, поэтому даже с первых своих
шагов новичок может получить преимущества, используя более
развитые средства С++.
Идея постепенного, пошагового овладения С++, а также возможность
смешивать программы на С++ с программами, написанными на языках,
не имеющих средств абстракции данных и объектно-ориентированного
программирования, естественно приводит к проекту, имеющему
гибридный стиль. Большинство интерфейсов можно пока оставить
на процедурном уровне, поскольку что-либо более сложное не
принесет немедленного выигрыша. Например, обращение к стандартной
библиотеке math из С определяется на С++ так:
extern "C" {
#include <math.h>
}
и стандартные математические функции из библиотеки можно использовать
так же, как и в С. Для всех основных библиотек такое включение
должно быть сделано теми, кто поставляет библиотеки, так что
программист на С++ даже не будет знать, на каком языке реализована
библиотечная функция. Использование библиотек, написанных на таких
языках как С, является первым и вначале самым важным способом
повторного использования на С++.
На следующем шаге, когда станут необходимы более сложные
приемы, средства, реализованные на таких языках как С или Фортран,
представляются в виде классов за счет инкапсуляции структур данных
и функций в интерфейс классов С++. Простым примером
введения более высокого семантического уровня за счет перехода
от уровня процедур плюс структур данных к уровню абстракции данных
может служить класс строк из $$7.6. Здесь за счет инкапсуляции
символьных строк и стандартных строковых функций С
получается новый строковый тип, который гораздо проще использовать.
Подобным образом можно включить в иерархию классов любой
встроенный или отдельно определенный тип. Например, тип int
можно включить в иерархию классов так:
class Int : public My_object {
int i;
public:
// definition of operations
// see exercises [8]-[11] in section 7.14 for ideas
// определения операций получаются в упражнениях [8]-[11]
// за идеями обратитесь к разделу 7.14
};
Так следует делать, если действительно есть потребность
включить такие типы в иерархию.
Обратно, классы С++ можно представить в программе на С или
Фортране как функции и структуры данных. Например:
class myclass {
// representation
public:
void f();
T1 g(T2);
// ...
};
extern "C" { // map myclass into C callable functions:
void myclass_f(myclass* p) { p->f(); }
T1 myclass_g(myclass* p, T2 a) { return p->g(a); }
// ...
};
В С-программе следует определить эти функции в заголовочном файле
следующим образом:
// in C header file
extern void myclass_f(struct myclass*);
extern T1 myclass_g(struct myclass*, T2);
Такой подход позволяет разработчику на С++, если у него уже есть
запас программ, написанных на языках, в которых отсутствуют понятия
абстракции данных и иерархии классов, постепенно приобщаться к этим
понятиям, даже при том требовании, что окончательную версии программы
можно будет вызывать из традиционных процедурных языков.
12.2 Классы
Основное положение объектно-ориентированного проектирования и
программирования заключается в том, что программа служит моделью
некоторых понятий реальности. Классы в программе представляют
основные понятия области приложения и, в частности, основные
понятия самого процесса моделирования реальности. Объекты классов
представляют предметы реального мира и продукты процесса
реализации.
Мы рассмотрим структуру программы с точки зрения следующих
взаимоотношений между классами:
- отношения наследования,
- отношения принадлежности,
- отношения использования и
- запрограммированные отношения.
При рассмотрении этих отношений неявно предполагается, что их анализ
является узловым моментом в проекте системы. В $$12.4 исследуются
свойства, которые делают класс и его интерфейс полезными для
представления понятий. Вообще говоря, в идеале, зависимость класса
от остального мира должна быть минимальна и четко определена, а
сам класс должен через интерфейс открывать лишь минимальный объем
информации для остального мира.
Подчеркнем, что класс в С++ является типом, поэтому сами классы
и взаимоотношения между ними обеспечены значительной поддержкой
со стороны транслятора и в общем случае поддаются статическому анализу.
12.2.1 Что представляют классы?
По сути в системе бывают классы двух видов:
[1] классы, которые прямо отражают понятия области приложения,
т.е. понятия, которые использует конечный пользователь для
описания своих задач и возможных решений;
и
[2] классы, которые являются продуктом самой реализации, т.е.
отражают понятия, используемые разработчиками и программистами
для описания способов реализации.
Некоторые из классов, являющихся продуктами реализации, могут
представлять и понятия реального мира. Например, программные и
аппаратные ресурсы системы являются хорошими кандидатами
на роль классов, представляющих область приложения. Это отражает
тот факт, что систему можно рассматривать с нескольких точек
зрения, и то, что с одной является деталью реализации, с
другой может быть понятием области приложения. Хорошо
спроектированная система должна содержать классы, которые
дают возможность рассматривать систему с логически
разных точек зрения. Приведем пример:
[1] классы, представляющие пользовательские понятия (например,
легковые машины и грузовики),
[2] классы, представляющие обобщения пользовательских понятий
(движущиеся средства),
[3] классы, представляющие аппаратные ресурсы (например, класс
управления памятью),
[4] классы, представляющие системные ресурсы (например,
выходные потоки),
[5] классы, используемые для реализации других классов (например,
списки, очереди, блокировщики) и
[6] встроенные типы данных и структуры управления.
В больших системах очень трудно сохранять логическое разделение
типов различных классов и поддерживать такое разделение между
различными уровнями абстракции. В приведенном выше перечислении
представлены три уровня абстракции:
[1+2] представляет пользовательское отражение системы,
[3+4] представляет машину, на которой будет работать система,
[5+6] представляет низкоуровневое (со стороны языка программирования)
отражение реализации.
Чем больше система, тем большее число уровней абстракции необходимо
для ее описания, и тем труднее определять и поддерживать эти уровни
абстракции. Отметим, что таким уровням абстракции есть прямое
соответствие в природе и в различных построениях человеческого
интеллекта. Например, можно рассматривать дом как объект,
состоящий из
[1] атомов,
[2] молекул,
[3] досок и кирпичей,
[4] полов, потолков и стен;
[5] комнат.
Пока удается хранить раздельно представления этих уровней абстракции,
можно поддерживать целостное представление о доме. Однако, если
смешать их, возникнет бессмыслица. Например, предложение
"Мой дом состоит из нескольких тысяч фунтов углерода, некоторых
сложных полимеров, из 5000 кирпичей, двух ванных комнат и 13
потолков" - явно абсурдно. Из-за абстрактной природы
программ подобное утверждение о какой-либо сложной программной
системе далеко не всегда воспринимают как бессмыслицу.
В процессе проектирования выделение понятий из области приложения
в класс вовсе не является простой механической операцией. Обычно
эта задача требует большой проницательности. Заметим, что сами
понятия области приложения являются абстракциями. Например, в
природе не существуют "налогоплательщики", "монахи" или "сотрудники".
Эти понятия не что иное, как метки, которыми обозначают бедную
личность, чтобы классифицировать ее по отношению к некоторой
системе. Часто реальный или воображаемый мир (например, литература,
особенно фантастика) служат источником понятий, которые кардинально
преобразуются при переводе их в классы. Так, экран моего компьютера
(Маккинтош) совсем не походит на поверхность моего стола, хотя
компьютер создавался с целью реализовать понятие "настольный" Ь,
а окна на моем дисплее имеют самое отдаленное отношение к
приспособлениям для презентации чертежей в моей комнате.
Ь Я бы не вынес такого беспорядка у себя на экране.
Суть моделирования реальности не в покорном следовании тому,
что мы видим, а в использовании реальности как начала для проектирования,
источника вдохновения и как якоря, который удерживает, когда
стихия программирования грозит лишить нас способности
понимания своей собственной программы.
Здесь полезно предостеречь: новичкам обычно трудно "находить"
классы, но вскоре это преодолевается без каких-либо
неприятностей. Далее обычно приходит этап, когда классы и отношения
наследования между ними бесконтрольно множатся. Здесь уже
возникают проблемы, связанные со сложностью, эффективностью и
ясностью полученной программы. Далеко не каждую отдельную деталь
следует представлять отдельным классом, и далеко не каждое
отношение между классами следует представлять как отношение
наследования. Старайтесь не забывать, что цель проекта - смоделировать
систему с подходящим уровнем детализации и подходящим уровнем
абстракции. Для больших систем найти компромисс между простотой и
общностью далеко не простая задача.
12.2.2 Иерархии классов
Рассмотрим моделирование транспортного потока в городе, цель которого