Документирование классов - задача более трудная, чем это было в случае процедур и
модулей. Поскольку любой метод может быть переопределен, в документации должно
говориться не только о том, что делает данный метод, но также и о том, в каком контексте
он вызывается. Ведь переопределенные методы обычно вызываются не клиентом, а самим
каркасом. Таким образом, программист должен знать, какие условия выполняются, когда
вызывается данный метод. Для абстрактных методов, которые пусты, в документации
должно даже говориться о том, для каких целей предполагается использовать
переопределяемый метод.
В сложных иерархиях классов поля и методы обычно наследуются с разных уровней. И
не всегда легко определить, какие поля и методы фактически относятся к данному классу.
Для получения такой информации нужны специальные инструменты вроде навигаторов
классов. Если конкретный класс расширяется, то каждый метод обычно сокращают перед
передачей сообщения базовому классу. Реализация операции, таким образом,
рассредоточивается по нескольким классам, и чтобы понять, как она работает, нам
приходится внимательно просматривать весь код.
Методы, как правило, короче процедур, поскольку они осуществляют только одну операцию
над данными. Зато количество методов намного выше. Короткие методы обладают тем
преимуществом, что в них легче разбираться, неудобство же их связано с тем, что код для
обработки сообщения иногда "размазан" по многим маленьким методам.
Абстракцией данных не следует злоупотреблять. Чем больше данных скрыто в недрах
класса, тем сложнее его расширять. Отправной точкой здесь должно быть не то, что
клиентам не разрешается знать о тех или иных данных, а то, что клиентам для работы с
классом этих данных знать не требуется.
Часто можно слышать, что ООП является неэффективным. Как же дело обстоит в
действительности? Мы должны четко проводить грань между неэффективностью на этапе
выполнения, неэффективностью в смысле распределения памяти и неэффективностью,
связанной с излишней универсализацией.
1. Неэффективность на этапе выполнения. В языках типа Smalltalk сообщения
интерпретируются во время выполнения программы путем осуществления поиска их
в одной или нескольких таблицах и за счет выбора подходящего метода. Конечно, это
медленный процесс. И даже при использовании наилучших методов оптимизации
Smalltalk-программы в десять раз медленнее оптимизированных C-программ.
В гибридных языках типа Oberon-2, Object Pascal и C++ посылка сообщения приводит
лишь к вызову через указатель процедурной переменной. На некоторых машинах
сообщения выполняются лишь на 10% медленнее, чем обычные процедурные вызовы.
И поскольку сообщения встречаются в программе гораздо реже других операций, их
воздействие на время выполнения влияния практически не оказывает.
Однако существует другой фактор, который влияет на время выполнения: это
инкапсуляция данных. Рекомендуется не предоставлять прямой доступ к полям класса,
а выполнять каждую операцию над данными через методы. Такая схема приводит к
необходимости выполнения процедурного вызова при каждом доступе к данным.
Однако, когда инкапсуляция используется только там, где она необходима (т.е. в
случаях, где это становится преимуществом), то замедление вполне приемлемое.
2. Неэффективность в смысле распределения памяти. Динамическое связывание и
проверка типа на этапе выполнения требуют по ходу работы информации о типе
объекта. Такая информация хранится в дескрипторе типа, и он выделяется один на
класс. Каждый объект имеет невидимый указатель на дескриптор типа для своего
класса. Таким образом, в объектно-ориентированных программах требуемая
дополнительная память выражается в одном указателе для объекта и в одном
дескрипторе типа для класса.
3. Излишняя универсальность. Неэффективность может также означать, что программа
имеет ненужные возможности. В библиотечном классе часто содержится больше
методов, чем это реально необходимо. А поскольку лишние методы не могут быть
удалены, то они становятся мертвым грузом. Это не воздействует на время выполнения,
но влияет на возрастание размера кода.
Одно из возможных решений - строить базовый класс с минимальным числом методов,
а затем уже реализовывать различные расширения этого класса, которые позволят
нарастить функциональность.
Другой подход - дать возможность компоновщику удалять лишние методы. Такие
интеллектуальные компоновщики уже доступны для различных языков и операционных
систем.
Но нельзя утверждать, что ООП неэффективно. Если классы используются лишь там, где
это действительно необходимо, то потеря эффективности из-за повышенного расхода
памяти и меньшей производительности незначительна. Кроме того, часто более важной
является надежность программного обеспечения и небольшое время его написания, а не
производительность.
Авторы:
Николай Вязовик (Центр Sun технологий МФТИ) <vyazovick@itc.mipt.ru>
Евгений Жилин (Центр Sun технологий МФТИ) <
gene@itc.mipt.ru >