Начинайте заполнять карточки по самым важным из определенных вами классов. С обратной стороны дайте небольшое описание в одно или два предложения. Можно также указать, уточнением (производным) какого класса является данный класс, если это очевидно к моменту работы с карточкой. Просто под именем класса напишите Надкласс: и впишите имя класса, от которого данный класс производится.
Сфокусируемся на распределении ответственности
Основным пунктом повестки дня заседания является определение ответственности каждого класса. Не обращайте много внимания на атрибуты, фиксируйте по мере продвижения только самые существенные и очевидные из них. Если для выполнения задачи класс должен делегировать часть работы другому классу, то эта информация указывается в столбце Сотрудничество.
В ходе работы обращайте внимание, сколько пунктов появилось на карточке класса в столбце Ответственность. Если на карточке не хватает места, то это повод задуматься, не следует ли разделить данный класс на два. Помните, каждый класс должен отвечать за выполнение одной задачи, поэтому все пункты в столбце Ответственность должны быть логически и функционально взаимосвязанными.
На данном этапе проектирования не стоит задумываться над тем, каким образом будет объявлен класс в программе и сколько открытых и закрытых методов он будет содержать. Обращайте внимание только на то, за что этот класс отвечает.
Как сделать класс живым
Главным свойством карточек CRC является то, что их можно сделать антропоморфными, т.е. каждый класс наделяется свойствами человека. Посмотрим, как это работает. После определения первоначального набора классов разложите по кругу на столе карточки CRC в произвольном порядке и вместе пройдитесь по сценарию. Например, вернемся к предложенному ранее сценарию.
Клиент выбирает операцию снятия наличных с расчетного счета. На счете в банке имеется достаточная сумма, в ATM достаточно наличных и заправлена лента для квитанций, а сеть включена и работает. Кассовый аппарат ATM просит указать сумму, которая не должна превышать $300. Машина выдает указанную сумму и печатает квитанцию для клиента.
Предположим, в заседании участвуют пять человек: Эмма — ваш помощник, сведущая в объектно-ориентированном программировании; Борис — ведущий программист; Сергей — будущий клиент вашей системы; Олег — эксперт по домену; а также Эдик — программист.
Эмма держит карточку CRC класса Расчетный счет и говорит: "Я сообщаю клиенту, сколько можно получить денег. Он просит меня дать $300. Я посылаю сообщение на устройство выдачи, чтобы было выдано $300 наличными". Борис держит свою карточку и говорит: "Я устройство выдачи; я выдаю $300 и посылаю Эмме сообщение, чтобы она уменьшила остаток на счете на $300. Кому я должен сообщить, что в машине стало на $300 меньше? Должен ли я это отслеживать?" Сергей: "Думаю, нужен объект для слежения за наличностью в машине". Эдик: "Нет. Кассовый аппарат сам должен знать, сколько у него осталось денег; это не должно нас волновать". Эмма возражает: "Нет. Выдачу денег кто-то должен контролировать. Программа должна знать, доступна ли наличность и достаточно ли у клиента денег на счете. Кроме того, программа должна проследить, было ли выдано аппаратом именно столько денег, сколько было заказано. Учет денег в кассовом аппарате следует делегировать некоему внутреннему счету. Необходимо также, чтобы система оповещала технический персонал банка о том, что в кассовом аппарате закончились деньги".
Спор продолжается. Каждый класс реально стал человеком, который держит соответствующую карточку в руках и заполняет столбцы ответственности и сотрудничества.
Ограничения карточек CRC
Хотя карточки CRC могут быть мощным средством в начале проектирования, их возможности ограничены. Первая проблема в том, что большой проект может состоять из большого числа классов. Тогда в карточки можно будет зарыться с головой. Взаимоотношения между классами прорабатываются недостаточно. Правда, фиксируется сотрудничество, но его природа и механизмы не моделируются. По карточкам не видно, какие классы вкладываются в другие классы, какие наследуются, а какие ассоциируются. В карточках не отмечаются атрибуты класса, определяющие его внутреннюю структуру, поэтому трудно перейти от карточек прямо к коду. Нужно помнить, что карточки статичны по своей природе и, хотя в них и записываются в общем виде отношения между классами, они не годятся для построения динамической модели.
Короче говоря, карточки CRC являются хорошим началом, но для построения более полной модели проекта нужно перейти к UML. После создания модели UML карточки можно будет отложить в сторону. Они вам больше не потребуются.
Создание модели UML no картонкам CRC
Каждой карточке будет соответствовать класс диаграммы UML. Пункты из столбца Ответственность становятся методами класса. Также в диаграмму переносятся все зафиксированные атрибуты класса. Определение класса с обратной стороны карточки помещается в документацию класса. На рис. 18.13 показана диаграмма отношения между классами Счет и Расчетный счет, атрибуты класса Расчетный счет взяты с соответствующей карточки CRC, показанной ниже.
Рис. 18.13. Отображение данных карточки CRC на диаграмме
Класс: Расчетный счет
Надкласс: Счет
Ответственность:
Отслеживать текущий остаток
Принимать и переводить депозиты
Выдавать чеки
Переводить деньги при снятии со счета
Сохранять баланс выдачи кассового аппарата за текущий день
Сотрудничество:
Другие счета
Компьютерная система банка
Устройство выдачи наличных
Отношения между классами
После того как классы будут отображены средствами UML, можно заняться отношениями между ними. Рассматриваются четыре основных вида отношений.
• Обобщение.
• Ассоциация.
• Агрегирование.
• Композиция.
Обобщение реализуется в C++ с помощью открытого наследования. Но с точки зрения проектирования больше внимания следует уделять не механизму, а семантике: что именно подразумевает это отношение. Мы уже говорили об обобщении на этапе анализа, но теперь рассмотрим этот вид отношений применительно к классам проекта. Нашей задачей будет вынести общие действия за границы взаимосвязанных классов в общий базовый класс, который инкапсулирует общую ответственность.
Таким образом, если обнаружено, что расчетный и депозитный счета используют одни и те же методы для перевода денег, то в базовый класс Счет можно перенести метод TransferFunds(). Чем больше методов будет сосредоточено в базовых классах, тем более полиморфным становится проект.
Одним из средств программирования, доступных в C++, но не в Java, является множественное наследование (однако Java имеет похожее, хотя и ограниченное средство, позволяющее создавать множественные интерфейсы). Это средство позволяет производить класс более чем от одного базового класса, добавляя переменные-члены и методы двух и более классов.
Опыт показывает, что множественное наследование надо использовать умеренно, так как оно может усложнить программный код проекта. Многие проблемы, вначале решаемые с помощью множественного наследования, теперь решаются путем агрегирования (вложения) классов. Тем не менее множественное наследование остается мощным средством программирования, от которого не следует огульно отказываться при разработке проектов.
Множественное наследование против вложения
Является ли объект суммой его частей? Имеет ли смысл классы деталей автомобиля, такие Руль, Двери и Колеса, производить от общего класса Автомобиль, как показано на рис. 18.14?
Рис. 18.14. Возможно, но не умно
Важно вернуться к основам: открытое наследование должно всегда моделировать обобщение, т.е. производный класс должен быть уточнением базового класса, чего не скажешь о приведенном выше примере. Если требуется смоделировать отношение "иметь" (например, автомобиль имеет руль), то это делается с помощью агрегирования (рис. 18.15).
Диаграмма на рис. 18.15 показывает, что автомобиль имеет руль, четыре колеса и от двух до пяти дверей. Это более точная модель отношения автомобиля и его частей. Обратите внимание, что ромбик на диаграмме не закрашен. Это потому, что отношение моделируется с помощью агрегирования, а не композиции. Композиция подразумевает контроль за временем жизни объекта вложенного класса. Хотя автомобиль имеет шины и дверь, но они могут существовать и до того, как станут частями автомобиля, и после того, как перестанут ими быть.