Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 3868 / 144 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Лекция 4:

Как найти классы

< Лекция 3 || Лекция 4: 123456 || Лекция 5 >

Сигналы опасности

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

Большое Заблуждение

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

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

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

Мой класс выполняет...

В формальных и неформальных обсуждениях архитектуры проекта часто задается вопрос о роли некоторого класса. И часто можно слышать в ответ: " Этот класс печатает результаты " или " Класс разбирает вход " - варианты общего ответа " Этот класс делает...".

Такой ответ обычно указывает на изъяны в проекте. Класс не должен делать одну вещь, он должен предлагать несколько служб в виде компонентов над объектами некоторого типа. Если же он выполняет одну работу, то, скорее всего, имеет место "Большое Заблуждение".

Вполне вероятно, что ошибка не в самом классе, а способе его описания - использовании операционной фразеологии. Но все-таки в этой ситуации лучше провести проверку класса.

Императивные имена

Предположим, что в процессе проектирования появились классы с такими именами, как PARSE ( РАЗОБРАТЬ ) или PRINT ( ПЕЧАТАТЬ ) - глагол в императивной форме. Это должно насторожить, не делает ли класс одну вещь и, следовательно, не должен быть классом.

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

Правило Имен класса

Имя класса всегда должно быть либо:

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

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

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

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

Единственный случай, который может показаться исключением из правила, задает командные классы, так как они введены в шаблоне проектирования undo-redo, покрывающем абстракции действий. Но даже и здесь можно следовать правилу, задавая имена командных классов текстового редактора в виде: LINE_DELETION и WORD_CHANGE, а не DELETE_LINE и REPLACE_WORD ( Удаление_Строки, а не Удалить_Строку ).

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

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

Однопрограммные классы

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

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

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

Преждевременная классификация

Упомянув таксоманию, следует отметить еще одну общую ошибку новичков - преждевременное построение иерархии классов.

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

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

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

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

Классы без команд

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

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

  • Он может представлять объекты, полученные из внешнего мира, не подлежащие изменениям в ПО. Это могут быть данные датчиков от органов системы управления прибора, пакеты, передаваемые в сети, структуры С, которых ОО-система не должна касаться.
  • Некоторые классы не предназначены для прямого использования - они могут инкапсулировать константы или выступают в качестве родителей других классов. Такое льготное наследование (facility inheritance) будет изучаться при обсуждении методологии наследования (см. "лекцию 6" ).
  • Наконец, класс может быть аппликативным - описывающим объекты, не подлежащие модификации. Это означает, что у класса есть только функции, создающие новые объекты. Например, операция сложения в классах INTEGER, REAL и DOUBLE следует математическим традициям - она не модифицирует значение, но, получив x и y, вырабатывает значение: x + y. В спецификации АТД такие функции характеризуются как командные функции.

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

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

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

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

Подобная история имела место при разработке ISE компилятора. Для внутренних потребностей идентификации классов решено было использовать целые числа, а не специальные объекты. Все это хорошо работало несколько лет. Но потом схема идентификации усложнилась, в частности пришлось перенумеровывать классы при слиянии нескольких систем в одну. Так что пришлось вводить класс CLASS_IDENTIFIER и заменять экземплярами этого класса прежние целые. Это потребовало усилий больше, чем хотелось бы.

Смешение абстракций

Еще один признак несовершенного проектирования состоит в том, что в одном классе смешиваются две или более абстракций.

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

Мэйлир Пейдж Джонс ([Page-Jones 1995]) использует термин соразвитие (connascence), означающий совместное рождение и совместный рост. Для классов соразвитие означает отношение, существующее между двумя тесно связанными компонентами, когда изменение одного влечет одновременное изменение другого. Как он указывает, следует минимизировать соразвитие между классами библиотеки, но компоненты, появляющиеся внутри данного класса, все должны быть связаны одной и той же четко определенной абстракцией.

Этот факт заслуживает отдельного методологического правила, сформулированного в "положительной" форме:

Принцип Согласованности класса

Все компоненты класса должны принадлежать одной, хорошо определенной абстракции.

Идеальный класс

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

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

Этот список описывает множество неформальных целей, не являясь строгим правилом. Легитимный класс может обладать лишь одним из перечисленных свойств. Большинство из примеров, играющих важную роль в этой книге, - начиная от классов LIST и QUEUE до BUFFER, ACCOUNT, COMMAND, STATE, INTEGER, FIGURE, POLYGON и многих других, - обладают всеми этими свойствами.

< Лекция 3 || Лекция 4: 123456 || Лекция 5 >
Александр Качанов
Александр Качанов
Япония, Токио
Янош Орос
Янош Орос
Украина, Киев