Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений на С++ (1158635), страница 24
Текст из файла (страница 24)
Началом времени существования любогообъекта является момент его создания (отведение участка памяти), аокончанием - возвращение отведенного участка памяти системе.Объекты создаются явно или неявно. Есть два способа создать их явно.Во-первых, это можно сделать при объявлении (как это было с item1): тогдаобъект размещается в стеке. Во-вторых, как в случае item3, можно разместитьобъект, то есть выделить ему память из "кучи". В C++ в любом случае приэтом вызывается конструктор, который выделяет известное ему количествоправильно инициализированной памяти под объект.
В Smalltalk этимзанимаются метаклассы, о семантике которых мы поговорим позже.Часто объекты создаются неявно. Так, передача параметра позначению в C++ создает в стеке временную копию объекта. Более того,создание объектов транзи-тивно: создание объекта тянет за собой созданиедругих объектов, входящих в него. Переопределение семантики копирующегоконструктора и оператора присваивания в C++ разрешает явное управлениетем, когда части объекта создаются и уничтожаются. К тому же в C++ можнопереопределять и оператор new, тем самым изменяя политику управленияпамятью в "куче" для отдельных классов.В Smalltalk и некоторых других языках при потере последней ссылкина объект его забирает сборщик мусора. В языках без сборки мусора, типаC++, объекты, созданные в стеке, уничтожаются при выходе из блока, вкотором они были определены, но объекты, созданные в "куче" операторомnew, продолжают существовать и занимать место в памяти: их необходимоявно уничтожать оператором delete.
Если объект "забыть", не уничтожить,это вызовет, как уже было сказано выше, утечку памяти. Если же объектпопробуют уничтожить повторно (например, через другой указатель),последствием будет сообщение о нарушении памяти или полный крахсистемы.При явном или неявном уничтожении объекта в C++ вызываетсясоответствующий деструктор. Его задача не только освободить память, но ирешить, что делать с другими ресурсами, например, с открытыми файлами.7Уничтожение долгоживущих объектов имеет несколько другуюсемантику. Как говорилось в предыдущей главе, некоторые объекты могутбыть долгоживу-щими; под этим понимается, что их время жизни можетвыходить за время жизни породивших их программ.
Обычно такие объектыявляются частью некой долговременной объектной структуры, поэтомувопросы их жизни и смерти относятся скорее к политике соответствующейобъектно-ориентированной базы данных. В таких системах для обеспечениядолгой жизни наиболее принят подход на основе постоянных"подмешиваемых классов".
Все объекты, которым мы хотим обеспечитьдолгую жизнь, должны наследовать от этих классов.3.2. Отношения между объектамиТипы отношенийСами по себе объекты не представляют никакого интереса: только впроцессе взаимодействия объектов реализуется система. По выражениюИнгалса: "Вместо процессора, беззастенчиво перемалывающего структурыданных, мы получаем сообщество хорошо воспитанных объектов, которыевежливо просят друг друга об услугах" [13]. Самолет, по определению,"совокупность элементов, каждый из которых по своей природе стремитсяупасть на землю, но за счет совместных непрерывных усилийпреодолевающих эту тенденцию" [14]. Он летит только благодарясогласованным усилиям своих компонентов.Отношения двух любых объектов основываются на предположениях,которыми один обладает относительно другого: об операциях, которые можновыполнять, и об ожидаемом поведении.
Особый интерес для объектноориентированного анализа и проектирования представляют два типаиерархических соотношений объектов:• связи• агрегация.Зайдевиц и Старк назвали эти два типа отношений отношениямистаршинства и "родитель/потомок" соответственно [15].Связи7Деструкторы не освобождают автоматически память, размещенную оператором new,программисты должны явно освободить ее.Семантика. Мы заимствуем понятие связи у Румбаха, которыйопределяет его как "физическое или концептуальное соединение междуобъектами" [16]. Объект сотрудничает с другими объектами через связи,соединяющие его с ними.
Другими словами, связь - это специфическоесопоставление, через которое клиент запрашивает услугу у объекта-сервераили через которое один объект находит путь к другому.На рис. 3-2 показано несколько разных связей. Они отмечены линиямии означают как бы пути прохождения сообщений. Сами сообщения показаныстрелками (соответственно их направлению) и помечены именем операции. Нарисунке объект aController связан с двумя объектами класса Displayltem(объекты а и ь). В свою очередь, оба, вероятно, связаны с aView, но нам былаинтересна только одна из этих связей.
Только вдоль связи один объект можетпослать сообщение другому.Связь между объектами и передача сообщений обычно односторонняя(как на рисунке; хотя технически она может быть и взаимной). Как мы увидимв главе 5, подобное разделение прав и обязанностей типично для хорошоструктурированных объектных систем.8 Заметьте также, что хотяпередаваемое сообщение инициализировано клиентом (в данном случаеaController), данные передаются в обоих направлениях.
Например, когдаaController вызывает операцию move для пересылки данных объекту а,данные передаются от клиента серверу, но при выполнении операцииisUnder над объектом b, результат передается от сервера клиенту.Участвуя в связи, объект может выполнять одну из следующих трехролей:• Актер9 Объект может воздействовать на другие объекты, но самникогда не подвергается воздействию других объектов; в определенномсмысле это соответствует понятию активный объектРис. 3-2. Связи• Сервер Объект может только подвергаться воздействию со стороныдругих объектов, но он никогда не выступает в роли воздействующего объекта• Агент Такой объект может выступать как в активной, так и впассивной роли; как правило, объект-агент создается для выполненияопераций в интересах какого-либо объекта-актера или агента.На рис.
3-2 объект aController выступает как актер, объект а - какагент и объект aView - как сервер.8На самом деле организация объектов, показанная на рис. 3-2, встречается настолькочасто, что ее можно считать типовым проектным решением. В Smalltalk аналогичныймеханизм известен как MVC, model/view/controller(модель/представление/контроллер).
Как мы увидим в следующей главе, хорошоструктурированные системы имеют много таких опознаваемых типовых решений.9Actor - это деятель, исполнитель. А исполнитель ролей, это и есть актер. Примеч.ред.Пример. Во многих промышленных процессах требуется непрерывноеизменение температуры. Необходимо поднять температуру до заданногозначения, выдержать заданное время и понизить до нормы. Профильизменения температуры у разных процессов разный; зеркало телескопа надоохлаждать очень медленно, а закаляемую сталь очень быстро.Абстракция нагрева имеет достаточно четкое поведение, что дает намправо на описание такого класса.
Сначала определим тип, значение которогозадает прошедшее время в минутах.// Число прошедших минутtypedef unsigned int Minute;Теперь опишем сам класс TemperatureRamp, который по смыслузадает функцию времени от температуры:class TemperatureRamp {public:TemperatureRamp() ;virtual ~TenperatureRamp() ;virtual void clear();virtual void bind (Temperature, Minute);Temperature TemperatureAt(Minute);protected:…};Выдерживая наш стиль, мы описали некоторые из операцийвиртуальными, гак как ожидаем, что этот класс будет иметь подклассы.На самом деле в смысле поведения нам надо нечто большее, чемпросто зависимость температуры от времени.
Пусть, например, известно, чтона 60-й минуте должна быть достигнута температура 250°F, а на 180-й - 150°F.Спрашивается, какой она должна быть на 120-й минуте? Это требуетлинейной интерполяции, так что требуемое от абстракции поведениеусложняется.Вместе с тем, управления нагревателем, поддерживающего требуемыйпрофиль, мы от этой абстракции не требуем. Мы предпочитаем разделениепонятий, при котором нужное поведение достигается взаимодействием трехобъектов: экземпляра TemperatureRamp, нагревателя и контроллера. КлассTemperatureController можно определить так:class TemperatureController {public:TemperatureController(Location);~TemperatureController();void process(const TemperatureRamp&) ;Minute schedule(const TemperatureRamp&) const;private:…};Тип Location был определен в главе 2.
Заметьте, что мы не ожидаемнаследования от этого класса и поэтому не объявляем в нем никакихвиртуальных функций.Операция process обеспечивает основное поведение этой абстракции;ее назначение - передать график изменения температуры нагревателю,установленному в конкретном месте. Например, объявим:TemperatureRamp growingRamp;TemperatureController rampController(7) ;Теперь зададим пару точек и загрузим план в контроллер:growingRamp.
bind (250, 60);growingRamp.bind(150, 180);rampController.process(growingRamp) ;В этом примере rampController - агент, ответственный завыполнение температурного плана, он использует объект growingRamp каксервер. Эта связь проявляется хотя бы в том, что rampController явнополучает growingRamp в качестве параметра одной из своих операций.Одно замечание по поводу нашего стиля. На первый взгляд можетпоказаться, что наши абстракции - лишь объектные оболочки для элементов,полученных в результате обычной функциональной декомпозиции.
Примероперации schedule показывает, что это не так. Объекты классаTemperatureController имеют достаточно интеллекта, чтобы определятьрасписание для конкретных профилей, и мы включаем эту операцию какдополнительное поведение нашей абстракции. В некоторых энергоемкихтехнологиях (например, плавка металлов) можно существенно выиграть, еслиучитывать остывание установки и тепло, остающееся после предыдущейплавки. Поскольку существует операция schedule, клиент может запроситьобъект TemperatureController, чтобы тот рекомендовал оптимальныймомент запуска следующего нагрева.Видимость.
Пусть есть два объекта А и в и связь между ними. Чтобы Амог послать сообщение в, надо, чтобы в был в каком-то смысле видим для А.Мы можем не заботиться об этом на стадии анализа, но когда дело доходит дореализации системы, мы должны обеспечить видимость связанных объектов.В предыдущем примере объект rampController видит объектgrowingRamp, поскольку оба они объявлены в одной области видимости ипотому, что growingRamp передается объекту rampController в качествепараметра. В принципе есть следующие четыре способа обеспечитьвидимость.• Сервер глобален по отношению к клиенту.• Сервер (или указатель на него) передан клиенту в качестве параметраоперации.• Сервер является частью клиента.• Сервер локально порождается клиентом в ходе выполнения какойлибо операции.Какой именно из этих способов выбрать - зависит от тактикипроектирования.Синхронизация.
Когда один объект посылает по связи сообщениедругому, связанному с ним, они, как говорят, синхронизируются. В строгопоследовательном приложении синхронизация объектов и состоит в запускеметода (см. врезку ниже). Однако в многопоточной системе объекты требуютболее изощренной схемы передачи сообщений, чтобы разрешить проблемывзаимного исключения, типичные для параллельных систем. Активныеобъекты сами по себе выполняются как потоки, поэтому присутствие другихактивных объектов на них обычно не влияет.