Гради Буч - Объектно-ориентированный анализ и проектирование с примерами приложений на С++ (1158635), страница 33
Текст из файла (страница 33)
По наблюдению Мейера"хороший проектировщик умеет найти компромисс между большим числомсвязей (дробление системы на фрагменты) и большим размером модулей (чтоможет привести к потере управляемости)" [54].В объектно-ориентированном проектировании принято рассматриватьметоды класса как единое целое, поскольку все они взаимодействуют друг сдругом для реализации протокола абстракции. Таким образом, определивповедение, нужно решить, в каком из классов это поведение реализуется.Халберт и 0'Брайен предложили следующие критерии для принятия такогорешения:• Повторная используемость Будет ли это поведение полезно болеечем в одном контексте?• СложностьНасколько трудно реализовать такоеповедение?• ПрименимостьНасколько данное поведение характернодля класса, в который мы хотим включить поведение?• Знание реализацииНадо ли для реализации данногоповедения знать секреты класса?Обычно операции объявляются, как методы класса, к объектамкоторого относятся данные действия.
Однако в языках Object Pascal, C++,CLOS и Ada допускается описание операций в виде свободных подпрограмм(утилит класса). Свободная подпрограмма, в терминологии C++, - этофункция, не являющаяся элементом класса. Свободные подпрограммы немогут переопределяться подобно обычным методам, в них нет такойобщности. Наличие утилит позволяет выполнить требование примитивности иуменьшить зацепление между классами, особенно если эти операции высокогоуровня задействуют объекты многих различных классов.Аспекты расхода памяти и времени. После того, как мы принялирешение о необходимости конкретной функции и определили ее семантику,следует принять решение об использовании ею времени и памяти.
Длявыражения таких решений принято использовать понятие лучшего, среднего ихудшего вариантов, где худший - это верхний допустимый предел расходов.Раньше мы уже отмечали, что поскольку один объект посылаетдругому сообщение, эти два объекта должны быть каким-то образомсинхронизированы. В случае многих потоков управления это означает, чтопередача сообщений сложнее, чем управление вызовами подпрограмм.
Длябольшинства языков программирования синхронизация просто не нужна,поскольку в них программы однопотоковые, и все объекты действуютпоследовательно. Мы говорим в таких случаях о простой передаче сообщений,так как ее семантика больше похожа на простой вызов подпрограмм. Однако вязыках, поддерживающих параллелизм,21 нужно побеспокоиться о болееизощренных системах передачи сообщений, чтобы избежать случаев, когдадва потока работают одновременно и несогласованно с одним и тем жеобъектом.
Объекты, семантика которых сохраняется при многопоточности,являются или синхронизированными, или защищенными.В некоторых обстоятельствах полезно отмечать параллельность какдля отдельных операций, так и для объекта в целом, так как разные операциимогут потребовать разных форм синхронизации. Выделяют следующие формыпередачи сообщений:21Ada и Smalltalk имеют прямую поддержку параллельности. Языки типа C++ такойподдержкой не обладают, но в них часто можно обеспечить семантикупараллельности за счет расширения классами (зависящими от платформы): примеромслужит библиотека AT&T для C++.• СинхроннаяОперация активизируется толькопри готовности передающего и принимающего сообщения объектов;ожидание взаимной готовности может быть неопределенно долгим• С учетом задержкиТо же, что и синхронная, однако, вслучае, если принимающий не готов, передающий не выполняет операцию• С ограничением времени То же, что и синхронная, однако,посылающий будет ждать готовности принимающего не дольше некотороговремени• АсинхроннаяОперация выполняется вне зависимостиот готовности принимающего.Нужная форма выбирается для каждой операции отдельно, но толькопосле того, как ее функциональная семантика определена.Как выбирать отношенияСотрудничество.
Отношения между классами и объектами связаны сконкретными действиями. Если мы хотим, чтобы объект X послал объекту Yсообщение M, то прямо или косвенно класс х должен иметь доступ к классу Y,иначе невозможно вызвать в классе х операцию м. Под доступностью мыпонимаем способность одной абстракции видеть другую и обращаться к ееоткрытым ресурсам. Абстракции доступны одна другой только тогда, когдаперекрываются их области видимости и даны необходимые права доступа(так, закрытая часть класса доступна только ему самому и его друзьям).
Такимобразом, зацепление связано с видимостью.Одним из полезных правил является закон Деметера, которыйутверждает, что "методы любого класса не должны зависеть от структурыдругих классов, а только от структуры (верхнего уровня) самого класса. Вкаждом методе посылаются сообщения только объектам из предельноограниченного множества классов" [56]. Следование этому закону позволяетсоздавать слабо зацепленные классы, реализация которых скрыта.
Такиеклассы достаточно автономны и для понимания их логики нет необходимостизнать строение других классов.При анализе структуры классов системы в целом можно обнаружить,что иерархия наследования либо широкая и мелкая, либо узкая и глубокая,либо сбалансированная. В первом случае структура классов выглядит как лесиз свободно стоящих деревьев. Классы могут свободно смешиваться ивступать во взаимоотношения [57].
Во втором случае структура классовнапоминает одно дерево с ветвями классов, имеющих общего предка [58].Каждый из вариантов имеет свои достоинства и недостатки. Классы,составляющие лес, независимы друг от друга, но, вероятно, не лучшимобразом используют возможности специализации и обобществления кода. Вслучае дерева классов эта "коммунальность" используется максимально,поэтому каждый из классов имеет меньший размер.
Однако в последнемслучае классы невозможно понять без контекста всех их предков.Иногда требуется выбирать между отношениями наследования,агрегации и использования. Например, должен ли класс Car (автомобиль)наследовать, содержать или использовать классы Engine (двигатель) и wheel(колесо)? В данном случае более целесообразны отношения использования.По мнению Мейера, между классами А И В "отношения наследования болеепригодны тогда, когда любой объект класса в может одновременнорассматриваться и как объект А" [59]. С другой стороны, если объект являетсячем-то большим, чем сумма его частей, то отношение агрегации не совсемуместно.Механизмы и видимость. Отношения между объектами определяетсяв основном механизмами их взаимодействия.
Вопрос состоит только в том,кто о чем должен знать. Например, на ткацкой фабрике материалы (партии)поступают на участки для обработки. Как только они попадают на участок, обэтом надо известить управляющего. Является ли поступление материала научасток операцией над участком, над материалом, или тем и другим сразу?Если это операция над участком, то класс участка должен быть видим дляматериала.
Если это операция над материалом, то класс материала долженбыть видим для участка, так как партия материала должна различать участки.В случае операции над помещением и участком нужно обеспечить взаимнуювидимость. Аналогично следует определить отношение между управляющими участком (но не материалом и управляющим): либо управляющий должензнать об участке, либо участок об управляющем.Иногда в процессе проектирования полезно явно определитьвидимость объектов. Существуют четыре основных способа сделать так,чтобы объект X (клиент) видел объект Y (сервер):• сервер является глобальным;• сервер передается клиенту в качестве параметра операции;• сервер является частью клиента в смысле классов;• сервер локально объявляется в области видимости клиента.Эти варианты можно комбинировать.
Y может быть частью х и приэтом быть видимым другим объектам. В языке Smalltalk такой способ обычноозначает зависимость между двумя объектами. Общая зона видимостиприводит к структурной зависимости, то есть один объект не имеетисключительных прав доступа к другому: состояние этого другого объектаможет быть изменено несколькими способами.Выбор реализацииВнутреннее строение (реализация) классов и объектов разрабатываетсятолько после завершения проектирования их внешнего поведения. При этомнеобходимо принять два проектных решения: выбрать способ представлениякласса или объекта и способ размещения их в модуле.Представление. Представление классов и объектов почти всегдадолжно быть инкапсулировано (скрыто).
Это позволяет вносить изменения(например, перераспределение памяти и временных ресурсов) без нарушенияфункциональных связей с другими классами и объектами. Как мудро отметилВирт: "выбор способа представления является нелегкой задачей и неопределяется одними лишь техническими средствами. Он всегда долженрассматриваться с точки зрения операций над данными" [60]. Рассмотрим,например, класс, соответствующий расписаниям полетов самолетов. Как егонужно оптимизировать - по эффективности поиска или по скоростидобавления/удаления рейса? Поскольку невозможно реализовать и то, идругое одновременно, нужно сделать выбор, исходя из целей системы.
Иногдатакой выбор сделать непросто, и тогда создается семейство классов содинаковым интерфейсом, но с принципиально разной реализацией дляобеспечения вариативности поведения.Одним из наиболее трудных решений является выбор междувычислением элементов состояния объекта и хранением их в виде полейданных. Рассмотрим, например, класс Cone (конус) с соответствующим емуметодом volume (объем). Этот метод возвращает значение объема объекта. Вструктуре конуса в виде отдельных полей хранятся данные о его высоте ирадиусе основания. Следует ли еще создать поле данных для объема илиследует вычислять его по мере необходимости внутри метода volume [60]?Если мы хотим получать значение объема максимально быстро, нужносоздавать соответствующее поле данных.
Если важнее экономия памяти,лучше вычислить это значение. Оптимальный способ представления объектавсегда определяется характером решаемой задачи. В любом случае этот выборне должен влиять на интерфейс класса.Модульная структура. Аналогичные вопросы возникают прираспределении деклараций классов и объектов по модулям. В языке Smalltalkэта проблема отсутствует, здесь модульный механизм не реализован.