Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений на С++ (1158635), страница 26
Текст из файла (страница 26)
В каждой проблемной области ключевыеабстракции взаимодействуют многими интересными способами, что мы идолжны отразить в проекте [21].Отношения между классами могут означать одно из двух. Во-первых,у них может быть что-то общее. Например, и маргаритки, и розы - эторазновидности цветов: и те, и другие имеют ярко окрашенные лепестки,испускают аромат и так далее.
Во-вторых, может быть какая-то семантическаясвязь. Например, красные розы больше похожи на желтые розы, чем намаргаритки. Но между розами и маргаритками больше общего, чем междуцветами и лепестками. Также существует сим-биотическая связь междуцветами и божьими коровками: божьи коровки защищают цветы отвредителей, которые, в свою очередь, служат пищей божьим коровкам.Известны три основных типа отношений между классами [22]. Вопервых, это отношение "обобщение/специализация" (общее и частное),известное как "is-a".
Розы суть цветы, что значит: розы являютсяспециализированным частным случаем, подклассом более общего класса"цветы". Во вторых, это отношение "целое/ часть", известное как "part of". Так,лепестки являются частью цветов. В-третьих, это семантические, смысловыеотношения, ассоциации. Например, божьи коровки ассоциируются с цветами -хотя, казалось бы, что у них общего.
Или вот: розы и свечи - и то, и другоеможно использовать для украшения стола.Языки программирования выработали несколько общих подходов квыражению отношений этих трех типов. В частности, большинство объектноориентированных языков непосредственно поддерживают разные комбинацииследующих видов отношений:• ассоциация• наследование• агрегация• использование• инстанцирование• метакласс.Альтернативой наследованию является делегирование, при этомобъекты рассматриваются как прототипы, которые делегируют свое поведениеродственным им объектам.
Таким образом, классы становятся не нужны [23].Из шести перечисленных видов отношений наиболее общим инеопределенным является ассоциация. Как мы увидим в главе 6, обычноаналитик констатирует наличие ассоциации и, постепенно уточняя проект,превращает ее в какую-то более специализированную связь.Наследование, вероятно, следует считать самым интереснымсемантически. Оно выражает отношение общего и частного. Однако, понашему опыту, одного наследования недостаточно, чтобы выразить всемногообразие явлений и отношений жизни. Полезна также агрегация,отражающая отношения целого и части между экземплярами классов.Нелишне добавить отношение использования, означающее наличие связимежду экземплярами классов.
Имея дело с языками Ada, Eiffel и C++, нам необойтись без инстанцирования, которое, подобно наследованию, являетсяспецифической разновидностью обобщения. "Метаклассовые" отношения это нечто совершенно иное, в явном виде встречающееся только в языкахSmalltalkРис. 3-4. Ассоциацияи CLOS. Метакласс - это класс классов, что позволяет нам трактовать классыкак объекты.АссоциацияПример. Желая автоматизировать розничную торговую точку, мыобнаруживаем две абстракции - товары и продажи. На рис.
3-4 показанаассоциация, которую мы при этом усматриваем. Класс Product - это то, чтомы продали в некоторой сделке, а класс Sale - сама сделка, в которой проданонесколько товаров. Надо полагать, ассоциация работает в обе стороны:задавшись товаром, можно выйти на сделку, в которой он был продан, а пойдяот сделки, найти, что было продано.В C++ это можно выразить с помощью того, что Румбах называетпогребенными указателями [24]. Вот две выдержки из объявлениясоответствующих классов:class Product;class Sale;class Product {public:…protected:Sale* lastSale;};class Sale {public:…protected:Product** productSold;};Это ассоциация вида "один-ко-многим": каждый экземпляр товараотносится только к одной последней продаже, в то время как каждыйэкземпляр Sale может указывать на совокупность проданных товаров.Семантические зависимости.
Как показывает этот пример,ассоциация - смысловая связь. По умолчанию, она не имеет направления (еслине оговорено противное, ассоциация, как в данном примере, подразумеваетдвухстороннюю связь) и не объясняет, как классы общаются друг с другом(мы можем только отметить семантическую зависимость, указав, какие роликлассы играют друг для друга). Однако именно это нам требуется на раннейстадии анализа.
Итак, мы фиксируем участников, их роли и (как будет сказанодалее) мощность отношения.Мощность. В предыдущем примере мы имели ассоциацию "один комногим". Тем самым мы обозначили ее мощность (то есть, грубо говоря,количество участников). На практике важно различать три случая мощностиассоциации:• "один-к-одному"• "один-ко-многим"• "многие-ко-многим".Отношение "один-к-одному" обозначает очень узкую ассоциацию.Например, в розничной системе продаж примером могла бы быть связь междуклассом Sale и классом CreditCardTransaction: каждая продажасоответствует ровно одному снятию денег с данной кредитной карточки.Отношение "многие-ко-многим" тоже нередки. Например, каждый объекткласса Customer (покупатель) может инициировать транзакцию снесколькими объектами класса Saleperson (торговый агент), и каждыйторговый агент может взаимодействовать с несколькими покупателями.
Какмы увидим в главе 5, все три вида мощности имеют разного рода вариации.НаследованиеПримеры. Находящиеся в полете космические зонды посылают наназемные станции информацию о состоянии своих основных систем(например, источников энергоснабжения и двигателей) и измерения датчиков(таких как датчики радиации, масс-спектрометры, телекамеры, фиксаторыстолкновений с микрометеоритами и т.
д.). Вся совокупность передаваемойинформации называется телеметрическими данными. Как правило, онипередаются в виде потока данных, состоящего из заголовка (включающеговременные метки и ключи для идентификации последующих данных) инескольких пакетов данных от подсистем и датчиков. Все это выглядит какпростой набор разнотипных данных, поэтому для описания каждого типаданных телеметрии сами собой напрашиваются структуры:class Time...struct ElectricalData {Time timeStamp;int id;float fuelCelllVoltage, fuelCell2Voltage;float fuelCelllAmperes, fuelCell2Amperes;float currentPower;};Однако такое описание имеет ряд недостатков. Во-первых, структуракласса ElectricalData не защищена, то есть клиент может вызватьизменение такой важной информации, как timeStamp или currentPower(мощность, развиваемая обеими электробатареями, которую можно вычислитьиз тока и напряжения).
Во-вторых, эта структура является полностьюоткрытой, то есть ее модификации (добавление новых элементов в структуруили изменение типа существующих элементов) влияют на клиентов. Какминимум, приходится заново компилировать все описания, связанные какимлибо образом с этой структурой. Еще важнее, что внесение в структуруизменений может нарушить логику отношений с клиентами, а следовательно,логику всей программы. Кроме того, приведенное описание структуры оченьтрудно для восприятия.
По отношению к такой структуре можно выполнитьмножество различных действий (пересылка данных, вычисление контрольнойсуммы для определения ошибок и т. д.), но все они не будут связаны сприведенной структурой логически. Наконец, предположим, что анализтребова-Дочерний класс может унаследовать структуру и поведение родительскихклассовний к системе обусловил наличие нескольких сотен разновидностейтелеметрических данных, включающих показанную выше структуру и другиеэлектрические параметры в разных контрольных точках системы. Очевидно,что описание такого количества дополнительных структур будет избыточнымкак из-за повторяемости структур, так и из-за наличия общих функцийобработки.Лучше было бы создать для каждого вида телеметрических данныхотдельный класс, что позволит защитить данные в каждом классе и увязать ихс выполняемыми операциями.
Но этот подход не решает проблемуизбыточности.Значительно лучше построить иерархию классов, в которой от общихклассов с помощью наследования образуются более специализированные;например, следующим образом:class TelemetryData {public:TelemetryData() ;virtual ~TelemetryData();virtual void transmit() ;Time currentTime() const;protected:int id;Time timeStamp;};В этом примере введен класс, имеющий конструктор, деструктор(который иаследники могут переопределить) и функции transmit иcurrentTime, видимые для всех клиентов.
Защищенные элементы id иtimeStamp несколько лучше инкапсулированы - они доступны только классуи его подклассам. Заметьте, что функция currentTlrne сделана открытой,благодаря чему значение timeStamp можно читать (но не изменять).Теперь разберемся с ElectricalData:class ElectricalData : public TelemetryData {public:ElectricalData(float vl, float v2, float al, float a2);virtual ~ElectricalData();virtual void. transmit();float currentPower () const;protected:float fuelCelllVoltage, fuelCell2Voltage;float fuelCelllAmperes, fuelCell2Amperes;};Этот класс - наследник класса TelemetryData, но исходная структурадополнена (четырьмя новыми элементами), а поведение - переопределено(изменена функция transmit).
Кроме того, добавлена функцияcurrentPower.Одиночное наследование. Попросту говоря, наследование - это такоеотношение между классами, когда один класс повторяет структуру иповедение другого класса (одиночное наследование) или других(множественное наследование) классов. Класс, структура и поведениекоторого наследуются, называется суперклассом.
Так, TelemetryData.является суперклассом для ElectricalData. Производный от суперклассакласс называется подклассом. Это означает, что наследование устанавливаетмежду классами иерархию общего и частного. В этом смыслеElectricalData является более специализированным классом более общегоTelemetryData. Мы уже видели, что в подклассе структура и поведениеисходного суперкласса дополняются и переопределяются.