UML2 и унифицированный процесс. Джим арлоуайла нейштадтпрактический объектно ориентированныйанализ и проектированиеu
Скачать 6.08 Mb.
|
Рис. 17.3. Два источника проектных классов: предметная область и область решения 374 Глава 17. Проектные классы 17.4. Анатомия проектного класса С помощью классов анализа делается попытка зафиксировать требуе мое поведение системы без рассмотрения его возможной реализации. В проектных классах необходимо точно определить, как каждый класс будет осуществлять свои обязанности. Для этого нужно сделать следующее: • закончить набор атрибутов и полностью описать их, включая имя, тип, видимость и (необязательно) применяемое по умолчанию зна чение; • закончить набор операций и полностью описать их, включая имя, список параметров и возвращаемый тип. Этот процесс уточнения проиллюстрирован на рис. 17.4. Как было показано в главе 8, операция в классе анализа – это высоко уровневое логическое описание части функциональности, предлагае мой классом. В соответствующих проектных классах каждая опера ция класса анализа уточняется и превращается в одну или более дета лизированных и полностью описанных операций, которые могут быть реализованы как исходный код. Следовательно, одна высокоуровне вая операция этапа анализа на самом деле может распадаться на одну или более проектных операций, которые можно реализовать. Эти дета лизированные операции уровня проектирования иногда называют ме тодами. Для иллюстрации рассмотрим следующий пример. При анализе систе мы регистрации авиапассажиров можно определить высокоуровневую операцию checkIn() (зарегистрировать). Однако если вам когда либо приходилось стоять в очереди на регистрацию на рейс, то вы знаете, что это довольно сложный бизнес процесс, включающий сбор и про верку достоверности определенной информации о пассажире, прием BankAccount name number balance deposit( ) withdraw( ) calculateInterest( ) анализ BankAccount –name : String –number : String –balance : double = 0 +BankAccount( name:String, number:String) +deposit( m:double ) : void +withdraw( m:double ) : boolean +calculateInterest( ) : double +getName( ) : String +setName( n:String ) : void +getAddress( ) : String +setAddress ( a:String ) : voi +getBalance( ) : double проектирование «trace» конструктор Рис. 17.4. Детализация на этапе проектирования 17.5. Правильно сформированные проектные классы 375 багажа и назначение посадочного места в самолете. Поэтому справед ливо предполагать, что при подробном проектировании процесса вы сокоуровневая аналитическая операция checkIn() будет разбита на ряд операций, находящихся на более низком уровне абстракции. Высоко уровневая операция checkIn() может сохраниться, но на этапе проекти рования она будет вызывать ряд «вспомогательных» операций, рас пределяя среди них свои обязанности. Может даже случиться так, что для реализации довольно сложного процесса регистрации необходимо будет ввести несколько новых вспомогательных классов, которые не были обозначены при анализе. 17.5. Правильно сформированные проектные классы Проектная модель будет передана программистам для фактического создания исходного кода. Код также может генерироваться непосред ственно из самой модели, если это поддерживается инструментом мо делирования. Следовательно, проектные классы должны быть доста точно подробно описаны. В процессе описания определяется, является ли класс «правильно сформированным» или нет. Проектный класс всегда должен оцениваться с точки зрения его пользо вателей. При создании важно всегда рассматривать проектный класс с точки зрения его потенциальных клиентов. Каким они увидят этот класс – не слишком ли он сложен? Может быть что то упущено? Как тесно он взаимосвязан с другими классами? Соответствует ли его имя выпол няемым функциям? Все это важно и может быть сведено в следующие четыре основные характеристики правильно сформированного про ектного класса: • полный и достаточный; • простой; • обладает высокой внутренней связностью; • обладает низкой связанностью с другими классами. 17.5.1. Полнота и достаточность Открытые операции класса определяют контракт между классом и его клиентами. Открытые операции класса определяют контракт между классом и его клиентами. Как и для делового контракта, важно, чтобы этот кон тракт был четким, правильно определенным и приемлемым для всех партнеров. 376 Глава 17. Проектные классы Полнота характеризует соответствие предоставляемых классом серви сов тому, что ожидают клиенты. Предположения о наборе доступных операций клиенты будут делать исходя из имени класса. Обратимся к примеру из реальной жизни: при покупке новой машины вполне ра зумно ожидать, что у нее будут колеса! То же самое с классами: делать вывод о том, какие операции должны быть доступны, клиенты будут из имени класса и описания его семантики. Например, от класса Bank Account (банковский счет), предоставляющего операцию withdraw(...), будут ожидать и наличия операции deposit(...). Опять таки, если про ектируется такой класс, как ProductCatalog (каталог продуктов), любой клиент может небезосновательно ожидать, что сможет добавлять, уда лять и вести поиск продуктов ( Product) по каталогу. Такая семантика ясно следует из имени класса. Полнота гарантирует, что классы удов летворяют всем справедливым ожиданиям клиента. Полный и достаточный класс предлагает пользователям такой контракт, какой они ожидают – не больше и не меньше. Достаточность, с другой стороны, гарантирует, что все операции клас са полностью сосредоточены на реализации его предназначения. Класс никогда не должен удивлять клиента. Он должен содержать только ожидаемый набор операций, не более этого. Например, обычная ошиб ка новичков: взять простой достаточный класс, такой как BankAccount, и затем добавить в него операцию по обработке кредитных карт или по управлению политиками страхования и т. д. Достаточность заключа ется в максимально возможном сохранении простоты и узкой специа лизированности проектного класса. Золотое правило обеспечения полноты и достаточности: класс должен делать то, что ожидают от него пользователи, не больше и не меньше. 17.5.2. Простота Операции должны проектироваться таким образом, чтобы предлагать единственный, простой, элементарный сервис. Класс не должен пред лагать множество способов выполнения одного и того же, поскольку это запутывает клиентов и может привести к усложнению техническо го обслуживания и проблемам совместимости. Простота – сервисы должны быть простыми, элементарными и уникаль ными. Например, если в классе BankAccount есть простая операция создания одного депозитного вклада, в нем не должно быть более сложных опе раций, создающих два или более депозитов. Такого же эффекта можно добиться, повторяя простую операцию. Класс всегда должен предла гать самый простой и самый малый набор возможных операций. 17.5. Правильно сформированные проектные классы 377 Хотя придерживаться простоты – хорошее правило, тем не менее воз можны обстоятельства, вынуждающие от него отойти. Самой распро страненной причиной нарушения ограничения простоты является по вышение производительности. Например, если групповое создание де позитных вкладов по сравнению с одиночным существенно повышает производительность, можно ослабить требование к простоте и ввести в класс BankAccount более сложную операцию deposit(…), обрабатываю щую сразу несколько транзакций. Однако отправной точкой в проек тировании всегда должен быть самый простой из возможных набор операций. Усложнение должно происходить, только если для этого есть веские и обоснованные причины. Это важный момент. Многие из так называемых проектных «оптими заций» в большей степени основываются на вере, а не на твердых фак тах. В результате их влияние на фактическую производительность времени выполнения приложения мало или вообще отсутствует. На пример, если приложение будет проводить в данной операции всего 1% своего времени, оптимизация этой операции может ускорить приложе ние не более чем на 1%. Полезное практическое правило: большинство приложений проводит около 90% времени выполнения в 10% своих операций. Вот эти операции и надо находить и оптимизировать, чтобы получить реальное повышение производительности. Такую настройку производительности можно осуществить только с помощью инстру мента профилирования кода, такого как jMechanic для Java (http:// jmechanic.sourceforge.net ), который проводит сбор параметров произво дительности исполняющегося кода. Конечно, это задача реализации, которая может влиять на проектную модель. 17.5.3. Высокая внутренняя связность Каждый класс должен моделировать только одно абстрактное понятие и иметь набор операций, обеспечивающих предназначение класса. Это и есть связность (cohesion). Если необходимо, чтобы у класса было множество разных обязанностей, для реализации некоторых из них можно создать «вспомогательные классы». Тогда основной класс мо жет делегировать обязанности своим «помощникам». Внутренняя связность – одна из самых желательных характеристик класса. Связные классы обычно проще понимать, повторно использо вать и обслуживать. У связного класса небольшой набор тесно взаимо связанных обязанностей. Каждая операция, атрибут и ассоциация класса специально проектируются для реализации этого маленького, узкоспециализированного набора обязанностей. Каждый класс должен отражать единственную четко определенную аб стракцию, используя минимальный набор возможностей. 378 Глава 17. Проектные классы Как то нам попалась на глаза модель системы продаж, приведенная на рис. 17.5, которая несколько смутила нас. В ней есть класс HotelBean (компонент гостиница), класс CarBean (компонент машина) и класс Ho telCarBean (компонент машина гостиница) (компонент (bean) – это кор поративный компонент Java (Enterprise JavaBeans, EJBs)). HotelBean отвечал за сдачу комнат в гостиницах, CarBean – за прокат автомоби лей, а HotelCarBean – за продажу пакета этих услуг (прокат автомобиля при остановке в гостинице). Очевидно, что эта модель неверна по не скольким причинам. • Имена классов подобраны неверно – HotelStay (остановка в гостини це) и CarHire (прокат автомобиля) подошли бы намного лучше. • Суффикс «Bean» не нужен, поскольку он просто указывает на опре деленную деталь реализации. • Класс HotelCarBean имеет очень слабую связность – его две основные обязанности (сдача гостиничных номеров и прокат автомобилей) уже выполняются двумя другими классами. • Это и не аналитическая модель (в ней есть информация проектиро вания – суффиксы «Bean»), и не проектная модель (она недостаточно полная). С точки зрения внутренней связности классы HotelBean и CarBean более или менее приемлемы (при условии, что будут переименованы), но Ho telCarBean просто абсурден. 17.5.4. Низкая связанность с другими классами Конкретный класс должен быть ассоциирован ровно с таким количест вом классов, которого достаточно для того, чтобы он мог реализовы вать свои обязанности. Взаимоотношения должны устанавливаться только в том случае, если между классами существует реальная семан тическая связь – это обеспечивает низкую связанность (coupling). Класс должен быть ассоциирован с минимальным количеством классов, позволяющим ему реализовывать свои обязанности. Одна из распространенных ошибок неопытных ОО проектировщиков – объединение в модели всего со всем практически случайным образом. По сути, связанность – самый злостный ваш враг в объектном модели ровании, поэтому необходимо заранее принимать меры, чтобы ограни HotelCarBean HotelBean CarBean Что не так с этой моделью? Рис. 17.5. Неверная модель системы 17.6. Наследование 379 чить отношения между классами и максимально сократить связан ность. Объектная модель с большим количеством взаимосвязей эквивалент на «спагетти коду» в не ОО мире. Она приведет к созданию малопо нятной и неудобной в эксплуатации системы. Вы увидите, что ОО сис темы с большим количеством взаимосвязей часто возникают в проек тах, в которых нет формального процесса моделирования, а система просто эволюционирует во времени произвольным образом. Неопытные проектировщики должны быть внимательными и не уста навливать связи между классами просто потому, что в одном из них есть код, который может использоваться другим. Это самый плохой вариант повторного использования, когда ради небольшой экономии времени разработки в жертву приносится архитектурная целостность системы. Все ассоциации между классами должны тщательно проду мываться. Многие ассоциации перейдут в проектную модель непосред ственно из аналитической, но целый ряд ассоциаций вводится из за ограничений реализации или желания повторно использовать код. Эти ассоциации необходимо проверять наиболее внимательно. Конечно, некоторая степень связанности хороша и необходима. До пускается высокая связанность в рамках подсистемы, поскольку это указывает на высокую связность компонента. Подрывают архитекту ру только многочисленные связи между подсистемами. Необходимо активно стремиться к сокращению подобной связанности. 17.6. Наследование При проектировании наследование играет намного более важную роль, чем при анализе. В анализе наследование использовалось, толь ко если между классами анализа имело место четкое и явно выражен ное отношение «является». Однако при проектировании наследование может применяться в тактических целях для повторного использова ния кода. Это совсем иная стратегия, поскольку наследование факти чески используется для упрощения реализации дочернего класса, а не для выражения бизнес отношения между родителем и потомком. В следующих нескольких разделах рассматриваются стратегии эф фективного использования наследования в проектировании. 17.6.1. Сравнение агрегации и наследования Наследование – очень мощный метод. Оно является ключевым меха низмом формирования полиморфизма в строго типизированных язы ках программирования, таких как Java, C# и C++. Однако неопытные ОО проектировщики и программисты часто используют его непра вильно. Необходимо осознавать, что наследование имеет определен ные нежелательные характеристики. 380 Глава 17. Проектные классы Наследование – самая строгая форма связанности классов. Это жесткое отношение. • Это самая строгая из возможных форма связанности двух или более классов. • В иерархии классов инкапсуляция низкая. Изменения базового класса передаются вниз по иерархии и приводят к изменениям под классов. Это явление называют проблемой «хрупкости базового класса», когда изменения базового класса имеют огромное влияние на другие классы системы. • Это очень жесткий тип отношения. Во всех широко используемых ОО языках программирования отношения наследования постоянны во время выполнения. Создавая и уничтожая отношения во время выполнения, можно изменять иерархии агрегации и композиции, но иерархии наследования остаются неизменными. Это делает на следование самым жестким типом отношений между классами. Система на рис. 17.6 – типичный пример решения задачи моделирова ния ролей в организации, выполненного неопытным разработчиком. На первый взгляд все вполне приемлемо, однако здесь есть проблемы. Рассмотрим предложенный вариант. Объект john типа Programmer (про граммист) необходимо повысить до типа Manager (менеджер). Вам должно быть понятно, что изменить класс Джона ( john) во время вы полнения нельзя. Поэтому единственный способ повысить Джона – создать новый объект Manager (названный john:Manager), скопировать в него из объекта john:Programmer все существующие данные и затем удалить john:Programmer для сохранения целостности приложения. Ко нечно, это очень сложно и совершенно не соответствует тому, как все происходит на самом деле. В сущности, в модели на рис. 17.6 допущена фундаментальная семанти ческая ошибка. Служащий ( Employee) – это именно должность (Job) или это указывает на то, что служащий занимает некоторую должность? Ответ на этот вопрос приводит к решению проблемы (см. рис. 17.7). Employee Manager Programmer john:Programmer «instantiate» Рис. 17.6. Неверная модель 17.6. Наследование 381 При использовании агрегации получаем верную семантику – у служа щего ( Employee) есть должность (Job). При такой более гибкой модели у служащих при необходимости может быть несколько должностей. Подклассы всегда должны представлять «особую разновидность чего либо», а не «выполняемую роль». Путем замены наследования агрегацией получена более гибкая и се мантически правильная модель. Здесь представлен важный общий принцип: подклассы всегда должны «являться разновидностью чего либо», а не «являться ролью, исполняемой чем либо». Если рассмат ривается бизнес семантика компаний, служащих и должностей, оче видно, что должность – это роль, исполняемая служащим, и она на са мом деле не указывает на разновидность служащего. Таким образом, наследование – безусловно, неверный выбор для моделирования тако го рода бизнес отношения. С другой стороны, в компании много раз ных должностей. Это указывает на то, что иерархия наследования должностей (корнем которой является абстрактный базовый класс Job), вероятно, является хорошей моделью. 17.6.2. Множественное наследование При множественном наследовании у класса может быть более одного родителя. Иногда возникает потребность наследования от нескольких родите лей. Такое наследование называют множественным. Его поддержива ют не все ОО языки программирования, например в Java и C# допус кается только единичное наследование. На практике отсутствие под держки множественного наследования не является проблемой, по 0..* Employee john:Employee Job Manager Programmer :Manager :Programmer 0..* чтобы повысить Джона, просто изменяем эту связь во время выполнения «instantiate» «instantiate» «instantiate» |