Предисловие переводчика
В 2005 г. Майкл Блаха специально для портала www.odbms.org написал статью "Дилемма инкапсуляции и оптимизации запросов". Статья показалась нам интересной в двух отношениях. Во-первых, в ней используется непривычная для нас трактовка термина "инкапсуляция". По Блахе методы объекта являются инкапсулированными, если из них вызываются только методы объектов-соседей данного объекта по связям. Такой метод объектно-ориентированного программирования соответствует требованиям закона
Деметры (или закона хорошего стиля программирования), сформулированного Карлом Либерхером, Яном Холландом и Артуром Райлом (Karl Lieberherr, Ian Holland, Arthur J. Riel) еще в 1988 г. Во-вторых, автор достаточно убедительно показывает, что подобная инкапсуляция конфликтует с такими методами формулировки запросов к базам данных, которые дают возможность оптимизатору запросов производить наиболее эффективные планы. Как утверждает автор (и с этим трудно не согласиться), этот конфликт является непреодолимым в принципе. Но, поскольку при наличии этого конфликта нужно продолжать создавать качественные и эффективные приложения, автор дает несколько практических советов, которые, хотя и являются достаточно очевидными, могут пригодиться на практике.
Аннотация
Между целями инкапсуляции и оптимизации запросов имеется фундаментальный, неразрешимый конфликт [1] [2].
В литературе, посвященной объектно-ориентированному подходу, инкапсуляции придается особое значение. Объект должен иметь доступ только к непосредственно связанным с ним объектам. Объект может иметь доступ к косвенно связанным через методы промежуточных объектов. Инкапсуляция повышает устойчивость приложения; локальные изменения в модели приложения вызывают локальные изменения в коде приложения.
С другой стороны, оптимизаторы систем управления базами данных (СУБД) принимают логический запрос и генерируют план выполнения. Чем шире формулируются запросы, тем большей свободой обладает оптимизатор при нахождении эффективного плана.
Таким образом, инкапсуляция способствует устойчивости, но ограничивает потенциальные возможности оптимизации. С другой стороны, широкие запросы благоприятны для оптимизации, но небольшое изменение в приложении может повлиять на многие запросы.
1. Инкапсуляция
Принцип инкапсуляции выразительно устанавливается законом Деметры [3]. «Для любого класса C и для любого метода M, связанного с C, все объекты, которым M посылает сообщение, должны быть экземплярами классов, ассоциированными со следующими классами:
- Классами аргументов M (включая C).
- Классами переменных экземпляра C.»
В терминах UML закон гласит, что объект должен иметь доступ только к тем объектам, с которыми непосредственно связан (классы объектов непосредственно соединены ассоциацией).
Смысл состоит в том, что метод должен обладать лишь ограниченным знанием модели классов. Метод может проходить по связям для получения соседних объектов и вызова их методов, но он не должен обладать возможностью прохождения по вторичной связи от своего соседа к сторонним объектам, поскольку вторичная связь не является для него непосредственно видимой. Для обхода неподсоединенных объектов полагается вызывать соответствующие методы соседних объекта. Этот подход уменьшает воздействие на методы изменений в сети ассоциаций.
Аналогично, следует избегать применения вторичного метода к результату вызова метода, если только класс результата не является уже известным в качестве класса атрибута, аргумента, соседнего объекта или класса результата из низкоуровневой библиотеки. Вместо этого для выполнения комбинированного метода следует написать новый метод в исходном целевом классе.
У инкапсуляции имеются две основные мотивировки:
- Упрощение изменений. Инкапсуляция минимизирует зависимость метода от других классов.
- Уменьшение сложности программирования. Инкапсуляция ограничивает число типов, которые должны быть известны программисту при написании метода.
При отсутствии инкапсуляции в пределе метод мог бы обходить все ассоциации модели классов.
2. Оптимизация запросов
У оптимизации запросов другая основная цель – обеспечить СУБД свободу для оптимизации. Во многих СУБД – в особенности, реляционных СУБД (РСУБД) – имеются сложные оптимизаторы, использующие эффективные алгоритмы, которые мог бы не заметить прикладной программист. Кроме того, во многих СУБД имеется возможность модификации алгоритмов при настройке структур данных или изменении распределений данных.
РСУБД обычно демонстрируют лучшую производительность при соединении нескольких таблиц с использованием одного оператора SQL, а не рассредоточивании таблиц по нескольким операторам. Обратите внимание, что это противоречит инкапсуляции! Инкапсуляция стимулирует разработчиков к минимизации комбинаций таблиц в запросах; оптимизация запросов поощряет в точности противоположный подход – крупные комбинации таблиц.
Можно было бы расценивать это как конфликт между языками объектно-ориентированного программирования (ОО-языками) и РСУБД. Однако в действительности это конфликт между ОО-языками и СУБД (как реляционными, так и объектно-ориентированными, ООСУБД). Этот конфликт особенно бросается в глаза для РСУБД, потому что в них подчеркивается непроцедурность запросов на основе использования языка SQL. Однако, по мере развития ООСУБД, их оптимизаторы запросов будут совершенствоваться и приведут к той же дилемме.
3. Пример
На рис. 1 показана часть диаграммы классов системы резервирования авиационных билетов. Авиалинии (Airlines) совершают рейсы между Аэропортами (Airports). ОписаниеРейса (FlightDescription) – это опубликованное описание воздушного путешествия между двумя Аэропортами (Airports). В отличие от этого, Рейс (Flight) – это реальный рейс самолета в конкретную дату. Атрибут частота (frequency) показывает дни недели, для которых применимо ОписаниеРейса. Атрибуты начальнаяДействующаяДата (startEffectiveDate) и конечнаяДействующаяДата (stopEffectiveDate) показывают период времени, в течение которого действует данное ОписаниеРейса.
Примером ОписаниеРейса является TW 250 из St. Louis в Milwaukee. Осенью 1995 г. рейс TW 250 по расписанию должен был отбывать в 7:42 утра, расчетное время полета составляло час пятнадцать минут, рейс должен был выполняться в каждый день недели, кроме субботы, и всегда самолетом DC9. Реальный Рейс TW 250 был, например, выполнен 23 октября 1995 г. со временем убытия в 7:42 утра и временем в пути один час пятнадцать минут.
В каждом ОписанииРейса присутствует ОписаниеСамолета (AircraftDescription). Например, в соответствии с ОписаниемРейса, рейс TW 250 должен был выполняться на самолете DC9. В отличие от этого, Рейсу соответствует реальный физический Самолет (Aircraft), у которого имеется уникальный регистрационныйНомер (registrationNumber).
Рис. 1. Модель классов UML для системы резервирования авиационных билетов
Модель позволяет отвечать на запросы, подобные следующему: «Найти Авиалинию, выполняющую рейсы на указанном Самолете». Инкапсуляция привела бы к следующей последовательности вызовов методов. (Заметим, что единственным осмысленным путем является Aircraft.Flight.FlightDescription.Airline.)
- Aircraft.getAirline() вызывает Flight.getAirline() для каждого Рейса данного Самолета. Модель классов допускает наличие нескольких Авиалиний для одного Самолета, хотя это не предусматривается. (В модели классов отсутствует ограничение, которое могло бы предотврарить это явление.) В код приложения пришлось бы вставить соответствующую проверку, которая выдавала бы в данном случае код ошибки.
- Flight.getAirline() вызывает FlightDescription.getAirline().
- FlightDescription.getAirline() возвращает Airline.
В качестве альтернативы можно было бы написать следующий SQL-запрос. (Это код расчитан на MS SQL Server и предполагает наличие очевидной схемы базы данных.)
SELECT DISTINCT(FD.airlineID)
FROM Aircraft AS Ac
INNER JOIN Flight AS F
ON Ac.aircraftID = F.aircraftID
INNER JOIN FlightDescription AS FD
ON F.flightDescriptionID = FD.flightDescriptionID
WHERE Ac.aircraftID = theGivenAircraft
Теперь рассмотрим влияние пересмотра модели. Модель на рис. 1 нехороша тем, что в ней не поддерживаются сквозные полеты. Авиакомпании регулярно организуют рейсы, которые начинаются в одном городе, делают остановки в одном или нескольких промежуточных городах и завершаются в городе назначения. У всей последовательности рейсов имеется единый номер рейса, но присутствуют промежуточные аэропорты, отличные от аэропортов отправления и прибытия. Для поддержки сквозных рейсов на рис. 2 добавляется ОписаниеЭтапаРейса (FlightLegDescription).
Рис. 2. Пересмотренная модель классов UML для системы резервирования авиационных билетов с добавлением класса FlightLegDescription
Это исправление влияет на ассоциацию между Flight и FlightDescription. Соответственно, требуется переписать метод Flight.getAirline(), чтобы он вызывал новый метод FlightLegDescription.getAirline(). Никакие другие исправления в предыдущих методах не требуются. Так что при использовании искапсуляции небольшое изменение модели классов влияет только на непосредственно воздействуемые методы. В отличие от этого, сложные запросы к СУБД затрагивают много классов, поэтому вероятность потребности их переписывания гораздо больше.
4. Анализ в сравнении с проектированием
Следует прояснить один аспект нашего обсуждения. Согласование инкапсуляции и оптимизации запросов – это проблема проектирования, а не анализа. Целью анализа является понимание приложения. На этом этапе нас не заботит точное конструирование окончательного кода.
В отличие от этого, на этапе проектирования, следующем за этапом анализа, определяется, как следует писать код, чтобы обеспечить желаемые функциональные возможности. Принцип инкапсуляции вступает в игру на стадии проектирования, когда мы пытаемся обеспечить устойчивость методов к изменению модели. Оптимизация запросов также важна на стадии проектирования, поскольку другой целью является хорошая производительность.
5. Что же делать?
Мы разъяснили суть конфликта между инкапсуляцией и оптимизацией запросов, но что же в связи с этим может сделать разработчик?
- Программные приложения. Здесь нет проблем, поскольку в языках программирования нет оптимизатора запросов. Нужно просто инкапсулировать код, следуя советам литературы, посвященной объектно-ориентированному программированию.
- Приложения ООСУБД. Обычно следует склоняться к инкапсуляции. В настоящее время у большинства ООСУБД имеются достаточно слабые оптимизаторы запросов, не слишком стимулирующие формулировку широких запросов.
- Приложения РСУБД. В этом случае нет простого ответа. Имеются три разных случая.
- Сложное программирование. Если программирование является сложным, а требования к производительности не слишком велики, следует инкапсулировать код.
- Простое программирование и хорошая эффективность обработки запросов. Если свободно формулируемые запросы существенно повышают производительность РСУБД, а программирование кода и запросов является относительно простым, можно согласиться с переписыванием приложения при изменении модели.
- Простое программирование и плохая эффективность выполнения запросов. Как это не парадоксально, иногда можно повысить эффективность путем фрагментации запросов. Оптимизаторы запросов не являются совершенными, и иногда может потребоваться помочь оптимизатору вручную. В этом случае вы смешиваете инкапсуляцию и оптимизацию запросов, пишите запросы, обходящие несколько классов, но меньшее число, чем в предыдущем случае.
Литература
[1] Michael Blaha and William Premerlani. Object-Oriented Modeling and Design for Database Applications. Upper Saddle River, New Jersey: Prentice Hall, 1998.
[2] Michael Blaha and James Rumbaugh. Object-Oriented Modeling and Design with UML, Second Edition. Upper Saddle River, New Jersey: Prentice Hall, 2005.
[3] K. Lieberherr, I. Holland, and A. Riel. Object-oriented programming: An objective sense of style. OOPSLA’88 as ACM SIGPLAN 23,
11 (November 1988), 323–334.
Об авторе
Майкл Блаха (Michael Blaha) защитил кандидатскую диссертацию в области баз данных в Университете им. Вашингтона г. Санкт-Луи, шт. Миссури. Восемь лет он проработал в Центре исследований и разработок компании General Electric в компании Джеймса Рэмбо (James Rumbaugh). С 1993 г. Блаха является консультантом и преподавателем в областях моделирования, архитектуры программного обеспечения, проектирования баз данных и обратной инженерии. Блаха - автор четырех книг и многочисленных статей. В 2005 г. вышло второе издание его книги (в соавторстве с Рэмбо) "Объектно-ориентированное моделирование и проектирование c использованием UML" (Object-Oriented Modeling and Design with UML, Second Edition, Prentice Hall, 2005, ISBN 0130159204).