Письмо 02 - Наследование

Наследование, как отмечалось ранее, – это передача подклассу свойств, структуры и поведения суперкласса. Пожалуй, наследование представляет самую красивую концепцию в объектной технологии. Однако передача свойств и структуры – это механизм или средство, но не цель. Реальных целей, достигаемых с помощью наследования, несколько. К ним относится, например, упорядочивание типов сущностей, то есть классификация. Действительно, не так просто разобраться в нюансах, отличающих один тип сущности от другого типа, если не представлять иерархию их развития, её идеологическую основу. Как и любая другая классификация, иерархия классов значительно упрощает использование сущностей, их модификацию и сопровождение.

В основе разработки иерархии классов, как правило, лежит их функциональное родство. Так, например, при разработке среды хранения нас в первую очередь интересуют классы способные сохранять, искать, выбирать и удалять информацию, представленную самыми различными типами. А при разработке среды графического интерфейса основу иерархии образуют классы способные отображать информацию в том или ином виде. В свою очередь, коммуникационная среда базируется на иерархии классов, которые умеют передавать информацию, распространяя её между компьютерами, связанными произвольным образом. Функциональная однородность, лежащая в основе построения иерархий классов, позволяет получать простые и эффективные реализации классов, но она может служить причиной образования множества независимых, не имеющих общего суперкласса, иерархий.

Благодаря классификации становится возможным введение и широкое практическое использование абстракции. Значение абстракции при проектировании сложных систем переоценить невозможно. Абстракция даёт возможность исключить из рассмотрения малозначительные детали и сосредоточиться на том, что действительно важно на данном уровне рассмотрения. Как правило, абстракция используется для определения интерфейса, который позже реализуется и детализируется в подклассах. Например, можно реализовать абстрактное предприятие и определить необходимый интерфейс, который позволит взаимодействовать предприятию с внешним миром: другими предприятиями, государственными службами и фондами, и т.п. При этом совершенно неважно, чем будет заниматься это предприятие: выпуском продукции, предоставлением услуг, торговлей или чем-то ещё.

Абстракцию также полезно использовать для задания поведения класса. Здесь под поведением понимается последовательность действий по обработке какого-то сообщения (или реакция класса на некоторое воздействие). Подклассы наследуют реализацию данного алгоритма, что приводит не только к значительной экономии кода за счёт его многократного повторного использования, но и позволяет при необходимости менять поведение множества подклассов простой заменой одного алгоритма другим у общего суперкласса. Такая замена может успешно применяться при переходе от модели или макета будущей системы к рабочей реализации.

Несмотря на неоспоримые достоинства и логическую простоту, споры вокруг механизма наследования не утихают. Безусловно, главной причиной дебатов является множественное наследование. Введение этого механизма было избыточным, а его применение порождает больше проблем, чем отказ от него. При рассмотрении концепции полиморфизма отмечалось, что часть ситуаций, когда применяют множественное наследование, исключаются, если рассматривать полиморфизм, как самостоятельное явление, а ни как составную часть концепции наследования. Другая часть ситуаций решается за счёт агрегации, о которой речь пойдёт в следующем письме. При агрегации вводятся связи вложенности (принадлежности), которые могут естественным образом заменять связи наследования, используемые при множественном наследовании. Этого вполне достаточно, чтобы отказаться от механизма множественного наследования. Однако следует оговориться, что подмена будет полной только в случае, если объектное проектирование будет следовать правилу локализации, которое тоже будет рассмотрено в дальнейшем.

В чём суть множественного наследования?! Любая сущность может рассматриваться с самых различных позиций, и, как следствие, классифицироваться по различным признакам. То, что одна и та же сущность может находиться в нескольких классификаторах, означает, что она наследует свойства изначально разнородных сущностей. Это и является идеологической основой множественного наследования. Но при проектировании системы всегда есть возможность локализовать различные точки зрения на одну и ту же сущность, исходя из семантики её применения. Это с одной стороны, а с другой стороны, множественное наследование является частным и худшим вариантом агрегации. Наконец, исходя из правила локализации, можно отметить, что в семантически однородной среде не может существовать нескольких способов классификаций каких-либо сущностей, а, следовательно, и необходимость в механизме множественного наследования отсутствует.

Не исключено, что приверженность механизму множественного наследования некоторых видных исследователей в области объектной технологии, связана с тем, что формирование иерархий классов производится ими достаточно произвольно, что отражено в книге Г. Буча. Он, в частности, пишет: «Трудно сразу расположить классы и объекты на правильных уровнях абстракции. Иногда, найдя важный класс, мы можем передвинуть его вверх в иерархии классов, тем самым, увеличивая степень повторности использования кода». Однако последовательное применение методов объектной композиции позволяет упорядочить процесс построения иерархий классов.

Не менее интересно высказывание Г. Буча по поводу агрегации. Он рассматривает два вида агрегации: агрегация на основе физического включения и ссылочная агрегация. Суть первого вида агрегации состоит в том, что класс-контейнер включает в себя вложенные в него классы, аналогично тому, как одна структура данных вкладывается в другую. Ссылочная агрегация подразумевает, что класс-контейнер включает в себя только ссылки на вложенные объекты. Так вот, рассуждая по поводу первого вида агрегации, Г. Буч пишет: «Часто агрегацию путают с множественным наследованием. Действительно, в С++ скрытое (защищённое или закрытое) наследование почти всегда можно заменить скрытой агрегацией экземпляра суперкласса. Решая, с чем вы имеете дело – с наследование или агрегацией – будьте осторожны. Если вы не уверены, что налицо отношение общего и частного (is a), вместо наследования лучше применить агрегацию или что-нибудь ещё» [ГБ стр. 133] . Из этой цитаты можно сделать вывод о том, что, по мнению Г. Буча, разница между агрегацией и множественным наследованием может быть столь тонкой, что её трудно различить. А механизмы агрегации и множественного наследования столь похожи, что не во всякой ситуации можно сделать правильный выбор между ними. Какой ещё аргумент может быть более весомым, чтобы отказаться от множественного наследования, нежели ситуация полной неопределённости и двусмысленности?

Возможно, имеет смысл рассмотреть эту ситуацию более пристально. Предположим, что мы имеем два суперкласса: “First” и “Second”. От этих двух суперклассов создадим на основе множественного наследования подкласс “Third”. Очевидно, что подкласс “Third” наследует структуры и свойства обоих суперклассов. Реально это означает, что в структуру подкласса вкладываются структуры суперклассов или, говоря другими словами, подкласс становится агрегатом суперклассов. Но в отличие от описанных в следующем письме контейнеров, этот агрегат имеет существенные и неоправданные ограничения. Например, нельзя заменить один суперкласс на другой без перекомпиляции подкласса, даже если замещающий суперкласс обладает интерфейсом аналогичным интерфейсу замещаемого суперкласса. Как следствие, теряется гибкость и модифицируемость. Другой негативной стороной подобной агрегации является то, что вместо схем надо использовать методы, которые, в отличие от схем, пишутся, а не конструируются. Таким образом, теряется простота создания агрегатов. И, что не менее важно, утрачивается возможность разделения логик: логики исполнения (логика вложенных классов) и логики управления (логика контейнеров). Но данное разделение имеет ключевое значение, поскольку на её основе становится возможным объектная декомпозиция, которая понижает трудоёмкость проектирования и разработки систем. Более подробно эта тема раскрывается в следующем письме.

Итак, наследование позволяет классифицировать используемые программные сущности, вводить высокоуровневые абстракции, передавать свойства и структуру суперкласса своим подклассам, что позволяет существенно экономить код. Наконец, наследование определяет направление развития классов, что существенно облегчает разработку иерархии классов, отражающей частный, но важный, случай взаимосвязи между сущностями реальной предметной области.

Сайт Alexus Software Development