Объекты — центральная идея СОМ. Но определение и использование объектов здесь иногда отличается от других популярных объектных технологий. Чтобы понять соотношение СОМ и других объектно-ориентированных технологий, следует разобраться, что обычно стоит за термином “объектно-ориентированный” и как СОМ этому соответствует.
Определение объекта
Объектно-ориентированная технология должна характеризоваться несколькими ключевыми понятиями. И главным среди них является общепризнанное определение того, что составляет объект. Принято считать, что объект состоит из двух элементов: предопределенного набора данных (его также называют состоянием или атрибутами) и группы методов. Последние, реализуемые обычно в виде процедур или функций, позволяют клиенту запросить у объекта выполнение определенных задач.
До этого места СОМ-объекты именно таковы. Но в большинстве объектных технологий объект поддерживает только один интерфейс с одним набором методов. А вот СОМ-объекты могут — и почти всегда это делают — поддерживать более одного интерфейса. Например, у С++-объекта лишь один интерфейс, включающий в себя все методы объекта. СОМ-объект с его несколькими интерфейсами может быть отлично реализован с несколькими объектами C++ — по одному на каждый интерфейс СОМ-объекта (хотя C++ — не единственный язык, который можно использовать для создания СОМ-объектов; cледует отметить, что, подобно объектам СОМ, объекты языка программирования Java также могут иметь несколько интерфейсов, фактически, Java отлично подходит и для создания СОМ-объектов другими способами.).
Еще одна распространенная концепция в объектно-ориентированной технологии — понятие класса. Скажем, все объекты, представляющие банковские счета, можно отнести к одному классу. Любой конкретный объект “банковский счет”, например, представляющий счет, является экземпляром данного класса.
СОМ-объекты имеют и классы. Класс в СОМ понимается как конкретная реализация набора интерфейсов. Может существовать несколько разных реализации одного и того же набора интерфейсов, каждая из которых будет отдельным классом.
С точки зрения клиента, только интерфейсы имеют значение. Клиента не касается реализация интерфейсов, т.е. то, что фактически определяет класс. Возможность работать с объектами разных типов, каждый из которых поддерживает данный набор интерфейсов, но реализует их по-разному, называется полиморфизмом.
Инкапсуляция, полиморфизм и наследование
Чтобы считать технологию объектно-ориентированной, достаточно ли того, что она, моделируя предметы как группы методов и данных, затем организует эти группы в классы? В общем случае объектная ориентированность требует поддержки еще трех характеристик: инкапсуляции, наследования и полиморфизма.
Инкапсуляция означает, что данные объекта недоступны его клиентам непосредственно. Вместо этого они инкапсулируются — скрываются от прямого доступа извне. Единственный способ доступа к данным объекта — его методы. В совокупности они представляют предопределенный интерфейс с внешним миром, и пользователь объекта может считывать или модифицировать данные последнего только через этот интерфейс. Инкапсуляция предохраняет данные объекта от нежелательного доступа, позволяя объекту самому управлять доступом к своим данным. Предотвращая случайные, некорректные изменения данных объекта, инкапсуляция может оказать значительную помощь в создании более качественных программ.
C++ предоставляет непосредственную поддержку инкапсуляции (впрочем, и способы ее обойти). В случае некорректной попытки непосредственно модифицировать данные объекта компилятор может выдать программисту сообщение об ошибке. Хотя СОМ и не является языком программирования, идея остается той же самой. Клиент имеет доступ к данным объекта СОМ только через методы интерфейсов этого объекта. Данные объекта СОМ инкапсулированы.
Второй определяющей характеристикой объектно-ориентированных технологий является полиморфизм. Полиморфизм, попросту говоря, означает, что клиент может рассматривать разные объекты как одинаковые и каждый из объектов будет вести себя соответствующим образом. Возьмем, к примеру, объект, представляющий расчетный счет.
У него, вероятно, имеется метод Withdrawal, которые неявно вызывается всякий раз при выписки чека. Также может быть объект, представляющий сберегательный счет, также обладющий методом Withdrawal. Для клиента оба метода выглядят одинаково; и при вызове любого из них происходит то же самое: остаток на счете в объекте уменьшается.
Однако фактическая реализация этих двух методов может быть абсолютно разной. Реализация для сберегательного счета может просто сравнить снимаемую сумму с остатком на счете. Если изъятие меньше остатка, операция выполняется, в противном случае — нет. С другой стороны, метод Withdrawal для расчетного счета может быть сложнее. Обычно расчетные счета допускают некоторый объем автоматического кредитования, если сумма чека превышает остаток. Реализация метода Withdrawal для объекта — расчетного счета могла бы сравнивать сумму чека как с текущим остатком счета, так и с максимально допустимым размером кредита. В данном случае запрос отрабатывается успешно, и чек принимается к оплате, если его сумма меньше суммы текущего остатка и максимального размера кредита.
С точки зрения клиента, оба метода Withdrawal одинаковы; различия в реализации — причем, существенные — скрыты. Возможность рассматривать разные вещи единообразно, хотя каждая ведет себя по-своему, — суть полиморфизма. Приведенный пример также демонстрирует огромную пользу полиморфизма: клиенты могут оставаться в блаженном неведении относительно вопросов, которые их не касаются, что упрощает разработку клиентского программного обеспечения.
Эту идею реализуют в полной мере СОМ-объекты. Объекты двух разных классов вполне могут предоставлять своим клиентам одинаковый набор интерфейсов или, может быть, только одно общее определение метода, даже если каждый объект реализует соответствующие методы по-своему.
Последняя определяющая характеристика традиционных объектно-ориентированных технологий — наследование. Идея проста: имея некоторый объект, можно создать новый, автоматически поддерживающий все или некоторые “способности” старого.
Подобно тому, как человек без каких-либо усилий со своей стороны может унаследовать от своих родителей какие-либо признаки, так и объект может автоматически получать характеристики другого объекта.
Виды наследования бывают разные. Следует отметить различия наследования реализации и наследования интерфейса. В первом случае объект наследует от своего родителя код. Когда клиент дочернего объекта вызывает один из унаследованных методов, на самом деле выполняется код метода родителя. Однако в случае наследования интерфейса потомок наследует только определения методов родителя. При вызове клиентом потомка одного из этих методов последний должен самостоятельно предоставить код обработки запроса.
Наследование реализации — это механизм повторного использования кода, широко применяемый в языках типа C++ и Smalltalk. В противоположность этому наследование интерфейса в действительности означает повторное использование спецификации — определений методов, поддерживаемых объектом. Наследования интерфейса облегчает к тому же реализацию полиморфизма. Определение нового интерфейса путем наследования от существующего гарантирует, что объект, поддерживающий новый интерфейс, можно рассматривать как объект, который поддерживает старый интерфейс.
Языки программирования типа C++ и Smalltalk поддерживают как наследование реализации, так и наследование интерфейса. Однако СОМ-объекты поддерживают только наследование интерфейса. Создатели СОМ полагали, что с учетом ее универсальности наследование реализации будет неприемлемым (и даже потенциальной опасным) способом повторного использования одного СОМ-объекта другим. Например, поскольку наследование реализации часто открывает наследующему объекту детали реализации родителя, постольку это может нарушить инкапсуляцию последнего. Поддержка только наследования интерфейса, что имеет место в СОМ, позволяет использовать повторно ключевой элемент другого объекта — его интерфейс — и в то же время избежать указанной выше проблемы.
Но как СОМ-объект повторно использует код другого объекта в отсутствие наследования реализации? Для этого в СОМ имеются механизмы включения (containment) и агрегирования (aggregation). При включении один объект просто вызывает другой по мере надобности для выполнения своих функций.
При агрегировании объект представляет один или несколько интерфейсов другого объекта как свои собственные; то, что клиент видит как один объект, предоставляющий группу интерфейсов, на самом деле — два или несколько объектов, агрегированных вместе. Как можно ожидать, реализация агрегирования требует несколько больших усилий, чем включение, но оба механизма — эффективные способы создания надстройки над существующими СОМ-объектами.
Является ли СОМ по-настоящему объектно-ориентированной?
У СОМ много общего с другими объектно-ориентированным технологиями. В ее основе лежит понятие объекта как набора данных и методов, схожее с идеей такого языка, как C++, хотя СОМ и позволяет одному объекту иметь несколько интерфейсов. СОМ также обеспечивает инкапсуляцию, полиморфизм и наследование интерфейсов, однако повторное использование кода в ней осуществляется через включение и агрегирование, а не посредством наследования реализации. Основой СОМ являются объекты, однако способ определения и их поведение несколько отличаются от других распространенных объектно-ориентированных технологий.
Итак, является ли СОМ по-настоящему объектно-ориентированной? Смотря что имеется в виду. Если спрашивается: "Являются ли объекты СОМ в точности такими же, как объекты в языках типа C++? " — ответ однозначно отрицательный. Это не должно слишком удивлять, так как СОМ предназначена для решения совершенно иных проблем, нежели объектно-ориентированные языки программирования. Но если вопрос стоит так: "Предоставляет ли СОМ основные возможности и преимущества объектов? " — то ответ столь же очевидно положительный, и только такая постановка проблемы имеет значение.