понедельник, 17 октября 2011 г.

Задача про датчики: проектирование классов. Часть 1

Бертран Мейер, один из идеологов объектно-ориентированного подхода, в своей книге "Объектно-ориентированное конструирование программных систем" пишет:

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

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

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

Мейер, Бертран. Объектно-ориентированное конструирование программных систем / Пер. с англ. – М.: Издательско-торговый дом «Русская редакция», 2005. – стр. 717.

Если подытожить, то Мейер делает два утверждения:

  1. Класс должен отвечать за выполнение нескольких функций.
  2. О классе не нужно думать, как о чём-то, выполняющем какие-то обязанности.

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


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

  1. Классы-сервисы.
  2. Классы-данные.

Классы-сервисы отвечают за выполнение определённых полезных функций. Классы-данные отвечают за временное или постоянное хранение данных и за передачу данных от сервиса к сервису.

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


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

1 функция – 1 класс

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

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

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

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

Классы можно свернуть, если:

  1. Они выполняют одинаковые или похожие функции.
Например, функции преобразования температуры и атмосферного давления из одних величин в другие можно делегировать классу Преобразовальщик.
  1. Они выполняют противоположные функции.
Например, функции сохранения и чтения можно делегировать классу Сохраняльщик/Читальщик.
  1. Один из классов выполняет основную, а другой – вспомогательную функцию.
Например, функции "определение типа сообщения", "нахождение обработчика для сообщения" и "диспетчеризация  сообщения обработчику" можно делегировать одному классу – Распределяльщику (или Диспетчеру).
  1. Они используют одинаковую и сложную реализацию, и гораздо дешевле поддерживать одну реализацию вместо двух.

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

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

1)      класса;
2)      класса со статическими функциями;
3)      функции;
4)      набора функций.

О том, в каком виде лучше реализовать класс проектирования, следует задумываться позже – при продумывании реализации.

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

Табл. 1. Кандидаты в классы

Класс
Обязанность
Ожидальщик
Ожидает наступления определённого события.
Читальщик Сообщений
Читает сообщения с порта.
Отсеиватель/Собиральщик
Отсеивает служебные сообщения, собирает части одного сообщения в единое целое.
Распределяльщик
Распределяет полученные данные между обработчиками.
Извлекальщик
Извлекает температуру, атмосферное давление и время измерения из тела сообщения.
Преобразовальщик
Преобразует температуру, атмосферное давление из одних единиц измерения в другие.
Сохраняльщик/Читальщик
Сохраняет и читает данные измерения в/из БД.
Отображальщик
Отображает информацию на экране.

Для лучшего понимания распределения функций между классами можно составить функционально-физическую матрицу. Это таблица, в которой строками являются функции, а столбцами – кандидаты в классы. На месте пересечения строки и столбца ставиться знак '+', если функция-строка выполняется классом-столбцом.

Табл 2. Функционально-физическая матрица программы для метеостанции


ЛИТЕРАТУРА:

  1. NASA System Engineering Handbook.
  2. System Engineering Fundamentals.