Понятно, что объектная модель зависит от того, как ее будут реализовывать. Если язык программирования не позволяет автоматизировано использовать основные механизмы модели, или при составлении модели не учитывали возможность наследования или полиморфизма, то создание модели будет затруднительно. При образовании видов на объект надо учитывать, что эти виды могут быть потом описаны.
Предложенное здесь изложение С++ не претендует на его описание. Если у вас есть проблемы с синтаксисом или вы хотите узнать тонкости описания на С++, то вам надо использовать книгу по С++. Я думаю, что мне нет особого смысла переписывать оттуда сюда. Здесь даны частные случаи, которые хотя и не всегда работают, но которые нельзя обойти при программировании, мне так кажется.
Данное нами определения объекта - "набор свойств с названием" годится для работы с абстрактной объектной моделью, т.к. в ней допускается любое описание объекта. Но чтобы использовать модель для программы, объекты должны быть созданы. Объекты могут быть описаны на основе уже имеющихся. Наследование, это новый механизм образования объекта из уже имеющихся. Рассмотрим известные механизмы в сравнении и с точки зрения логической, ассоциативной связи:
Если вид Х на объект А позволяет считать объект А объектом Х, то создание объекта отличается от простого конструирования А из Х ( если вы вложили и запекли в пироге яблоко, то пирог удастся выдать за яблоко только слепому или голодному, или если расковырять пирог ).
Этому механизму реализации связи между объектами следует уделить особенное внимание. Во-первых, он не встречается в предыдущих моделях, во-вторых, по уверениям специалистов, именно он и приводит ко всем замечательным преимуществам ООП перед другими способами программирования.
Для описания класса в С++ применяют ключевое слово class
. Возможны и другие объявления, с помощью struct
и union
, но я думаю, что нет необходимости их использовать, т.к. это добавляет похожесть класса на структуру или объединение С, что плохо, а экономит лишь одну или две строки в описании класса.
Как уже говорилось, класс С++ описывает не сообщения, а методы их реализации, поэтому он часто явно определяет и механизм реализации ассоциативной связи.
За разным синтаксисом обращения к переменной и указателю скрыт тот простой факт, что переменная размещается в памяти компилятором автоматически, а указатели ссылаются на каким-то образом уже размещенные, в т.ч. самим компилятором, переменные и возможно изменение адреса памяти, использование другой переменной во время работы программы. Указатель позволяет создать шаблон алгоритма для работы с множеством объектов: списком или массивом.
Ссылка позволяет в некотором блоке считать переменную, может быть адресуемую указателем, одной и той√же в течение всего блока, и значит можно перейти к синтаксису обычной переменной, без подробностей размещения в памяти.
Синтаксис доступа как к переменной не влияет на механизм связывания сообщения с методом, а лишь на возможность или нет сменить реальный объект, на который указывает имя переменной. Т.е. если написано a.work()
, а не a->work()
, то это не означает, что в первом случае будет использовано раннее связывание, т.к. класс объекта известен на этапе компиляции.
Можно вообще допустить, что вместо трех видов доступа ( как к переменной, как к ссылке и как к указателю) для объекта идет доступ либо по ссылке (синтаксис как с обычной переменной), либо по указателю (подробности размещения в памяти). Но это допущение касается только доступа к свойствам объекта. Объект и ссылка на него это в целом совсем не одно и тоже.
Для того чтобы объявить новый класс заодно объектом некоторого другого класса, т.е. потомком базового класса, нужно указать базовый класс в описании потомка. В потомке можно как определить свои методы реализации сообщений базового класса, так оставить методы предка.
Ошибочно думать, что переопределенное свойство просто уничтожает, встает на место базового. Объект содержит в себе все свойства и базового класса, и явно определенные. При этом может возникнуть ситуация, когда имени одного сообщения соответствует более одного метода его реализации, т.е. имеет место несколько форм реализации сообщения, полиморфизм. Свойства с одинаковым именем и типом могут быть также определены как в базовом, так и в производном классе. Правила подбора свойства или метода описаны далее.
Шаблон кода, который получает параметр как ссылку на объект базового класса, может успешно работать и с классом потомком. Доступ к объекту потомку при этом происходит через указатель или ссылку на базовый класс.
Было бы неплохо, чтобы доступ к свойствам был различный, что позволит иметь свойства, ошибочное использование которых запрещено автоматически. В С++ существует три уровня доступа к свойству, которые можно применить к любому виду на объект, т.е. в одноименном уровне доступа С++ могут находится свойства разных наших видов. Это уровни:
public
- открытый, свойства доступны для всех
protected
- защищенный, свойства доступны для классов-потомков
private
- только для собственных свойств объекта
В контексте классов, различают доступ к объекту через:
public
секции вложенного объекта. К объекту другого класса объявленному вне описания класса, в программе, такой же доступ.
public
и protected
полей базового класса. Т.е. protected
расширяет обычный доступ специально для механизма наследования. Для любого объекта этого же класса (не только к тому, для которого вызван метод) такой же доступ. Интересно, что для методов из протокола класса на доступ не влияет то, как наследуется базовый класс: открыто или закрыто.
Несмотря на то, что класс является потомком, для методов из протокола класса доступ к ссылке на объект базового класса (не к собственным свойствам унаследованным от базового класса, который доступны через this или другой объект своего класса) происходит именно через ссылку на экземпляр базового класса.
При доступе к свойствам базового класса через ссылку на класс потомка, эти свойства открыты в зависимости от того, как потомок наследует свой базовый класс. Если потомок объявляет private
базовый класс, то не доступны сообщения и свойства базового класса даже из секции public
.
В наследнике можно изменить доступ к сообщениям базового класса, произвольным образом переопределяя их в новых уровнях. Если базовый класс наследуется как private
, то доступ к его public
сообщениям и свойствам в объекте наследнике возможен через указатель на базовый класс (Пример 1).
Пример 1
Области видимости при наследовании
Описание классов |
|
применение |
|
Есть способ игнорировать разграничения доступа, при доступе через экземпляр класса или протокол базового класса, описанные выше. Это использовать объявление в протоколе класса, к которому происходит доступ, тех классов или функций ( именно функций, т.к. они определены прямо в программе ) для которых этот доступ будет разрешен. Это нужно, если класс или функция работает сразу с двумя классами, которые не связаны между собой, но требуется доступ к защищенным свойствам. Я знаю несколько типовых применений этих свойств:
Как уже говорилось, при наследовании возможна ситуация, когда одному сообщению соответствует несколько методов. Компилятор должен как-то отличать метод с одинаковым именем одного класса от другого. По аналогии с А-моделью, можно ввести искажение имени, вставив в имя метода название класса, в котором он определен. То же можно сделать и с любым свойством класса. Это делается с помощью оператора указания контекста имен свойства ::
Tclass_name::property_name
Для свойств без явного задания контекста используется контекст имен по умолчанию. Все неоднозначности в вызове метода реализации сообщения можно снять, всегда явно указывая контекст имен используемого свойства.
Очевидно, что шаблон, работающий со ссылкой на объект некоторого класса, не может прибегнуть к такому способу, иначе, когда будет использоваться ссылка на потомка этого класса, невозможно будет вызвать переопределенные в потомке методы. Шаблон использует пространство имен по умолчанию, в надежде на то, что компилятор подставит нужный метод сам. Подбор свойства, таким образом, сводится к управлению пространством имен по умолчанию.
Как уже говорилось, класс С++ включает в себя описание части механизмов реализации связей объектов. В частности, для механизма наследования, надо задать для свойства правила подбора пространства имен по умолчанию.
Свойства объекта используются либо внешним, относительно объекта, кодом, либо собственными методами:
Сообщение в классе вызывает метод. Можно изменить подбор метода принятый по умолчанию (по типу ссылки на объект) на такой: по типу реального объекта. Это достигается путем объявления метода как virtual
. Начиная с этого класса, все классы, которые переопределяют метод этого сообщения, автоматически делают свой метод virtual
. Все методы во всех потомках для этого сообщения образуют виртуальный кластер.
Сообщение, отправленное как от внешнего кода, так и от внутреннего метода вызовет метод соответствующий реальному объекту во время выполнения. Здесь имеет место позднее связывание. Методы этого сообщения всех других классов из линии наследования доступны при явном указании контекста имен.
Свойства, те которые не сообщения, нельзя объявить как virtual
. Это означает, что внутренние методы, имеющие контекст имен по умолчанию как свой тип, уже на этапе компиляции свяжутся с таким свойством, и если переопределить его в потомке, то методы потомка будут работать с другой копией свойства, которая для них по умолчанию. Внешний код будет использовать свойство соответственно типу ссылки.
В некоторых случаях так и надо, но такое свойство не может содержать сообщения или свойства, которые надо вызывать соответственно типу объекта, а не указателя, т.е. нельзя переопределить virtual
сообщение в потомке, если оно расположено внутри некоторого подобъекта.
Может оказаться так, что группа сообщений по логике модели окажется в некой подгруппе, но хочется сделать так, чтобы эти вложенные сообщения были бы на правах сообщений непосредственно описанных в классе. Это можно сделать, если эти логически созданные сообщения, имея не виртуальные относительно объекта-носителя методы, будут вызывать виртуальные методы с другими именами и описанными непосредственно в классе. Здесь будет просто преобразование имен.
Часть свойств можно сделать общими для всех объектов некоторого класса. Это могут быть также и методы. Для метода потребуется дополнительный параметр - ссылка на объект класса, чтобы иметь доступ к не статическим свойствам конкретного объекта. В протоколе класса статическое свойство описывается как static
.
Можно сделать для свойства некоторое подобие virtual
метода, используя для доступа к свойству указатель на него, определенный только в одном классе. В этом случае, каждый класс в линии наследования может иметь свою копию свойства, и просто устанавливает указатель на свою копию. Те методы, как внешние, так и внутренние, которые используют доступ к свойству через такой указатель, получают копию свойства реального объекта.
Конструктор - создает объект из ничего (конструктор по умолчанию) или из других объектов этого (конструктор копирования) или других (конструктор преобразования) классов. Объект создается, значит вызывается конструктор, при объявлении объекта, с помощью new
, при использовании временной переменной или при преобразовании одного типа в другой.
Конструктор вызывает перед своим выполнением конструкторы всех базовых классов от самого первого. Конструктор не может быть виртуальным, не имеет возвращаемого значения и конструкторов может быть много. Конструктор может находиться в защищенной части. Внешние методы не смогут его вызвать, и создать объект таким конструктором. Если базовый класс не имеет конструктора по умолчанию, но имеет другие виды конструкторов, то в конструкторе потомка надо указать явно, какой конструктор базового класса использовать.
Конструктор копирования используют, когда хотят, чтобы копировались, например, не указатели на свойства, а реальные свойства.
Конструкторы с параметрами объектов других типов служат для преобразования этих типов в данный тип. Альтернативой конструктору некоторого класса, для которого просто не может быть известно всех будущих классов, используется оператор преобразования типа, который определяется в этом неизвестном будущем классе.
Конструктор это не оператор для выделения памяти, в свете чего его многие пытаются рассматривать. Он нужен для создание объекта из чего-нибудь, в том числе и из ничего. Его можно рассматривать как аналог оператора преобразования одного типа в другой, а не оператора new
.
В противовес ему, деструктор удаляет объект, выполняя все необходимые операции по освобождению занятых ресурсов: освобождает память, закрывает файлы и т.п. Деструктор может быть виртуальным, если надо, чтобы был вызван деструктор соответствующий типу реального объекта, что чаще всего необходимо, а не типу ссылки.
Деструктор класса последовательно вызывает деструкторы базовых классов от себя в направлении самого первого базового класса автоматически. Поэтому в потомке, как правило, нужно освободить только те ресурсы, которые занимает конструктор именно этого класса, а не всех предков.
Деструктор вызывается автоматически при удалении объекта с помощью delete
или когда авто объект выходит из области определения. В последнем случае, вызов деструктора может произойти когда угодно, даже при выходе из программы и в произвольном порядке, в соответствии со стратегией сборки ╚мусора╩ ( удаленных автоматических объектов ) в вашем компиляторе и операционной системе. Может это и не совсем точно, но лучше не делать никаких предположений о порядке вызова деструктора для авто объектов компилятором, и, соответственно, не включать зависимый от этого код в деструктор. Другими словами, компилятор гарантирует лишь что ресурс память будет освобожден правильно, но если вывыделяете еще какие либо ресурсы, которые зависят от очередности создания и удаления объектов, то компилятор не может ими правильно управлять.
Перечисленные выше правила подбора метода реализации сообщения осуществляются автоматически. Может возникнуть вопрос, возможен ли подбор метода нужного базового класса, ╚автоматический switch╩ по какому-то другому параметру, не только по типу ссылки на объект или типу объекта. Ответ нет. Правда, я слышал, что существует какое-то расширение Borland C++, которое позволяет в качестве параметра использовать целое, но я это никогда не использовал.
Проблема подбора метода базового класса возникает также при конвейерной обработке сообщения методами по линии наследования. Если потомок полностью переопределяет метод базового класса, то такой проблемы нет. Подробнее об этом в разделе ╚Полиморфизм╩.
Описание базового класса может иметь сообщения, для которых не существует метода их реализации. Методы таких сообщений описывают с помощью чисто виртуальных функций, а сообщения, если хотите, можно называть абстрактными сообщениями, а класс называют абстрактным. Нельзя создать объект такого класса. Этот класс может иметь методы для других сообщений, тех которые не абстрактные.
Такой абстрактный базовый класс используется для того, чтобы либо сосредоточить описание интерфейса, т.е. для объявления сообщений, либо описать сообщения для нескольких принципиально разных классов, общие методы реализации для которых выделить затруднительно. Чтобы не определять бессмысленных пустых методов, т.к. класс С++ описывает методы, а не сообщения и по-другому задать наличие сообщения, кроме как описав метод, нельзя.
Абстрактный базовый класс, состоящий только из чисто виртуальных функций, не является аналогом описания объекта из пространства сообщений, потому что определяет неявно механизм использования такого класса - наследование и виртуальные методы, что не оговаривается для пространства сообщений.
Как и другие два способа создания объекта: вложение и прямое описание, наследование это механизм создания объекта, но он обладает рядом отличий. Как и вложение, наследование позволяет создавать объект на основе уже имеющихся объектов, но в отличие от него:
Наследование более тесная связь, чем вложение, при котором вложенный объект, как правило, ничего не знает о том, кто его объект-носитель. Дополнительные отличия добавляет следующая особая форма наследования - множественное.
Такое наследование, при котором у одного класса более одного базового класса перечислены в описании класса. Наличие множественного наследования не привносит никакой путаницы при имеющейся модели, но сильно увеличивает повторное использование кода. Создается возможность образовать такой вид на объект, при котором результат будет отражать свойства обоих предков, т.е. можно создать что-то совершенно невообразимое в контексте каждого из свойств. Можно соединить корабль и цветочный горшок, причем его можно будет использовать и как горшок в комнате, и как корабль в океане, если кто-то может себе такое представить.
Такое объединение нескольких классов может использоваться как для локализации в классе потомке связи между двумя базовыми абстракциями и ее обработки, так и для добавления потомку ╚фоновых╩ свойств, которые позволят, например, откликаться на какие-то сообщения, обработка которых в объекте не производится.
Альтернативой множественного наследования может быть явное задание интерфейса, который будет транслировать вызовы во вложенный объект. Недостатком такого подхода является невозможность отдельного задания абстракций, невозможность использования шаблона кода, работающего с базовым классом и потомком и прочее.
Множественное наследование позволяет независимо разрабатывать разные методы или группы методов одного базового класса, всегда имея работоспособный объект и, затем, объединять их, эти методы в одном классе.
Часть побочных эффектов множественного наследования рассмотрены в ╚Виртуальный базовый класс╩. Описание класса при множественном наследовании сложнее, чем при простом, но это плата за дополнительное повторное использование кода.
При множественном наследовании возможна ситуация, когда имеется несколько копий состояний одного из базовых классов, полученных независимым наследованием. Из-за этой ситуации множественное наследование и не любят и, видимо, приходят к мысли, что во время выполнения, для класса существуют объекты классов предков, между которыми как-то передаются сообщения.
Что получится при такой ситуации? Те не статические свойства, которые определены в этом дублированном базовом классе будут иметь свои копии для каждого из потомков. Во многих случаях, это правильно. Методы разных объектов одного класса есть одно и тоже, и если они работают с private
свойствами своего класса и методами базовых классов, то множественное наследование на них не влияет.
Виртуальные методы такого дублированного базового класса потребуют явного переопределения в потомке, так же как и те не виртуальные методы, которые вызываются через ссылку на потомка.
Наибольшую сложность могут вызвать те свойства, которые должны быть, по логике объекта, общими для обоих копий дублированного базового класса, но не общими для всех объектов этого класса, т.е. некий аналог статических свойств, но глобальных только для всех базовых классов этого потомка. Эта глобальность может быть логической и ее отсутствие не даст ошибки во время компиляции, но приведет к неправильным результатам или невозможности использования такого класса в шаблоне.
Можно сказать, что если создавать класс, который потом можно будет использовать для множественного наследования, то надо придерживаться определенных правил в описании такого класса, в описании public
и protected
свойств. Видимо отсюда пошла неприязнь к свойствам не сообщениям в открытой части протокола класса. Если в открытой части протокола только методы, то они глобальные для всех объектов в силу своей природы, поэтому открытые свойства пытаются одеть в методы доступа.
Чтобы сделать свойства не методы общими при множественном наследовании, базовый класс с этими свойствами можно наследовать как virtual
во всех повторяющихся классах. Проблема в том, что обычный потомок не знает, что кто-то когда-то возьмет и потребует, чтобы он наследовал свой базовый класс как виртуальный, а для самого обычного потомка это не требуется. В своих программах, чтобы не корректировать ранее определенный код, что не всегда и возможно, и не наследовать все классы виртуально, лучше в том классе, который поддерживает множественное наследование себя в дальнейшем, все открытые(public
, protected
) свойства не методы, которые должны быть общими для всех возможных в рамках объекта-потомка копий базовых классов, вынести в отдельный виртуальный базовый класс.
При множественном наследовании, в отличие от обычного, надо указать явно конструкторы всех тех виртуальных базовых классов, которые принадлежат к дублированным базовым классам и не имеют конструктора по умолчанию. Хотя конструкторы виртуальных базовых классов могут быть указаны в конструкторах других базовых классов, будет вызван только вариант конструктора, указанный в реально создаваемом объекте. Здесь имеет место аналогия с виртуальным методом, когда для одного виртуального базового класса имеется несколько вариантов вызова конструктора.
На самом деле, я затрудняюсь указать все правила, которые гарантируют безопасное использование методов базовых классов в общем случае. Но в ряде частных случаев, множественное наследование позволяет облегчить себе жизнь.
Одним из них является случай, когда определяется структура сообщений с помощью абстрактного класса, поддерживающего множественное наследование, создаются шаблоны работающие со ссылками на такой объект. Этот абстрактный класс имеет несколько видов, методы которых используют друг друга, например: доступ к потоку, перемещения по просмотру, и методы последнего вида вызывают первый. Каждый вид совершенствуется независимо. Реальный объект наследует по одному варианту каждого из видов. Можно создать шаблон класса такого реального объекта, в качестве параметров которого выступают классы каждого из видов.
Альтернативой такому способу не является наследование реальным объектом только абстрактного базового класса, вложение каждого вида и определение методов абстрактного класса как вызывающих метод одного из видов, т.к. имеет место взаимодействие видов, и необходимо, чтобы они находились в одном контексте. При вложении неудобно делать конвейерную обработку сообщений методами других классов. Вид может иметь абстрактные методы и его нельзя будет создать как объект.
Конечно, можно создать такой вариант, когда подобъект будет вызывать методы другого подобъекта через объект-носитель, но это громоздко, хотя и не требует множественного наследования. Иногда, если реальное создание методов вида базового класса происходит во время выполнения по параметру конструктора, применение множественного наследования также затруднительно.
При достаточно большой иерархии классов, на практике, реальное нормальное использование методов базовых классов возможно только при наличии удобного браузера методов, и правил создания метода, которые гарантируют их применимость во всех потомках.
Чтобы разбавить слова реальным кодом, есть ряд примеров с множественным наследованием: (Пример 2,Пример 3). Более осмысленные примеры применения множественного наследования можно найти в реализации ╚просмотра файла с памятью╩.
Пример 2
Не виртуальные базовые классы при множественном наследовании
Описание классов |
|
Использование |
|
Результаты выполнения |
|
Пример 3
Виртуальные базовые классы при множественном наследовании
Описание классов |
|
Использование |
|
Результаты выполнения |
|
Вернемся к нашей объектной модели. Как преобразовать сообщение из пространства сообщений, в сообщение класса С++? Можно ли по виду на объект пространства сообщений прямо получить механизм? Какой механизм лучше с точки зрения сопровождения и эффективности программы?
Избежать этих вопросов при написании программы нельзя. Мы не можем во время программирования вдаваться и в сложности понятий отношения, абстракции и т.д. Нам, как пользователям метода ООП, лучше использовать готовые простые критерии по выбору способа образования. Если это задается произвольно, зачем тогда вообще это разделять? ООП это разделяет.
Можно сказать, что этот выбор зависит от имеющихся базовых объектов, а базовые объекты заданы декомпозицией задачи. Но можно задать вид на базовые объекты как угодно произвольно. Объекты контекста задачи вполне могут иметь любые общие базовые классы, что, как кажется, противоречит логике контекста задачи.
Механизмы образования объектов: часть 2
Рассмотрим ряд известных утверждений, призванных ответить, является ли часть объекта, группа свойств, базовым классом или вложенным объектом. Разница есть, иначе бы эти механизмы были бы одним и тем же. Надо перечислить эти отличия, которые и будут критерием выбора.
Что означает понятное определение? Это означает, что его можно перефразировать, уточнить любое слово, сохранив смысл. Вот замечательные по своей сути определения, которые каждый может найти:
Первое просто утверждает, что если объект не наследник, то он контейнер. Но вопрос не в этом, это утверждение очевидно, если исключить повторное описание. Именно не наследник ли мы и хотим узнать. Неявно используется то, что мы все видели собак и хвосты и считаем, что в базовых объектах "псовые" заложено то, что хвост не является собакой, а собака это "псовое". Это кажется очевидным, что хвост не собака, а собака не хвост, но, в общем случае, это далеко не очевидно и как раз это то и представляет интерес.
Если с хвостами и другими органами животных все может и понятно, то как быть с дверями? Куда деть двери в комнате: в "комната" является "строением" и содержит "двери" и или в "комната" является "строением с дверями" и содержит "ничего"? Или вот. Файл Х типа является файлом, но может еще являться базой данных, а может включить ее как одно из своих полей-свойств, что выбрать при создании типа?
За приведенным примером с собаками, пытаются скрыть то, что скрыто за словом "является".
Второе просто ставит в недоумение, не приводя примеры, как раскрыть слово "есть".
Проиллюстрируем еще. У того же Буча ( пример с "есть" ) есть пример с кошкой, который я уже приводил, в котором кошка взглядом патологоанатома и бабушки выглядит по разному, и для бабушки шерсть "есть" кошка, а не кошка "содержит" часть кошки - шерсть, как для патологоанатома.
Как другими словами заменить или уточнить "является" и "есть". Этого достаточно для выбора, если это не "является" или "есть", тогда вместо наследования используем вложенность.
Механизмы образования объектов: часть 2
Есть важные но, которые я решил добавить. Тот факт, что мы вынесли наследование в механизмы, говорит о том, что
В конечном итоге, похоже на то, что я пока не могу, видимо, дать лучшее объяснение, чем с "собаками и хвостами". Если я смогу в дальнейшем сформулировать эти критерии полнее и лучше, или совсем их переменю, то это будет здесь отражено.
Вот еще попытка внести ясность. Т. Бадд (в книге которую я поместил на этом же сайте) приводит пример с автомобилем с двигателем, и уверяет, что двигатель - это часть ("patf of") автомобиля, а автомобиль это детализация общего ("is-a") транспортного средства. Он просто напросто сам себя убедил в этом заранее, до применения своего теста, либо он часто смотрит как собирают или разбирают автомобиль. Другими словами, он уже задал, что в его модели есть гайки, винты, двигатели и прочее, из чего собирают автомобиль, и зачем он вобще применяет свой тест неясно.
Хорошо считать такую модель естественной, но плохо - единственно верной. Можно легко представить себе такое разложение на объекты, когда автомобиль будет потомком телеги и какого-то потомка парового двигателя. Для историка автопромышленности, такая модель будет более приемлима. Можно придумать еще более нелепые варианты, которые будут вполне логичны в определенной предметной области.
Так что же получается, можно использовать как вложение, так и наследование? Это так. Они очень похожи, и когда у вас нет уже заранее обоснованной модели, (обоснованной условиями задачи, имеющимися базовыми моделями или еще чем-нибудь, т.е. когда вы уже, фактически, определились с тем, что использовать для создания) тесты с "хвостами" и "перестановками А и В" не годятся, потому как они не дают однозначного и легко проверяемого критерия.
Я могу указать только один критерий, который работает почти всегда, по крайней мере мне не попадались пока случаи, когда он не сработает. Этот критерий помечен как (отличительный от вложения критерий) - использование разных объектов в одном шаблоне. Наследование - единственный путь задать равнозначность для шаблона двух разных объектов, а возможность переопределять уже готовые методы предка - приятное дополнение к такой возможности. Не наследованием задать равнозначность нельзя. С другой стороны, наследованием нельзя получить переменное во время выполнения число предков или потомков, вложение - связь менее сильная, чем наследование. Если сильные, отличительные условия для вложения и наследования не выполняются, то можно или наследовать, чтобы получить повторное использование кода или хорошую, в вашем понимании, логику объектов или вкладывать, чтобы ослабить связи, в этих случаях я затрудняюсь указать обоснованную различимость.
Механизмы образования объектов: часть 2
Если требования, перечисленные в 1 и 2 в данном случае безразличны, то безразлично какой механизм использовать, по желанию программиста, но связь при вложенности более слабая. В большинстве случаев возможно преобразование пространства сообщений как в наследование, так и во вложенность.
То, что в пространстве сообщений группа сообщений выглядит как вложенный объект, не означает, что надо обязательно применить механизм вложения, и наоборот, т.е. нахождение или не нахождение сообщения в пространстве имен объекта по умолчанию не влияет на выбор механизма в подавляющем большинстве случаев. Глядя на логический формат сообщения для объекта нельзя сказать, являются ли реальные методы группы сообщений наследуемыми или вложенными.
Вложенный объект имеет не полиморфичные, относительно объекта-носителя сообщения, даже если собственные методы вложенного объекта объявлены как virtual
, но он может транслировать их, в полиморфичные, относительно объекта-носителя, сообщения самого объекта-носителя, с произвольными именами, которые можно назвать сообщениями реализации модели. При этом при создании вложенного объекта, используется ссылка на объект-носитель.
При наследовании нельзя иметь несколько экземпляров предка, и вообще, предок и объект становятся одно, нельзя говорить об экземпляре предка.
Вложенная группа сообщений более ╚чужая╩ для объекта, чем полученная при наследовании, и в общем случае, запрещает как связь объекта с protected
секцией вложенного объекта, так и связь вложенного объекта с объектом-носителем.
Может произойти совпадение имен нескольких базовых классов. Правильное имя всегда можно получить указав пространство имен
class_name::msg_name()
либо использовав указатель на один из базовых классов (Пример 4).
Пример 4
Совпадение имен базовых классов
Описание классов |
|
Использование |
|
Мы уже вплотную приблизились к способам задания модели на С++. Я надеюсь, что теперь только надо узнать как мы хотим описать то, что хотим получить, для чего надо открыть книгу по С++. Часть особенностей, которые показались мне важными, перечислены в следующем разделе "Объектная модель и С++".
Может вызвать неясность, как объектная модель связана с А-моделью, которая в свою очередь связана с С-моделью. Объектная модель задана аксиоматически, но легко заметить, что объект можно, в некотором приближении, считать АТД с добавлением операции наследования. Приближение используется потому, что в предложенном задании О-модели, наследование не является абсолютно необходимой чертой, а относится к разряду механизмов и стоит на равне с вложением и прямым описанием. Функция и переменная тоже не полностью соответствуют сообщению, методу и свойству объекта, однозначной связи между ними нет.
На самом деле, существует некоторый разрыв между АТД и объектом, разрыв этот создан, может быть, искусственно, специально используя другие термины и прочее. Но он действительно отражает тот факт, что объект против АТД, как абстрактная вещь против надстройки над структурой С, против технического приема программирования, решают разные задачи и формируются разными способами. Объект самодостаточен, элементарен и не требует разложения на какие-то другие элементарные составляющие, как надо для АТД. Объект сам есть элементарная составляющая, а не сборка из более мелких. Объект может быть выражен как угодно в программе, через структуры, функции, переменные - это не имеет значения.
Такое разделение О-модели на информационное взаимодействие ( пространство сообщений ) и реализацию взаимодействия ( механизмы ), на целевые и вспомогательные объекты весьма спорно и может забуксовать для многих применений. К тому же, не разрешился вопрос о формализации видов на объект.
Но это модель для обучения. Она, вероятно, не самая непротиворечивая, но может использоваться и для перехода от простого структурно-алгоритмического программирования к программированию с использованием средств ООП ( для чего она и создавалась ), и для практического решения ряда задач. Полезность и недостатки ее, скорее всего, можно определить только при реальном использовании, а не теоретически.
При переходе от структурного к объектному составлению программ надо ясно представлять себе отличия структурной модели от объектной и знать способы получения как той, так и другой. Понимание этих различий является самым трудным шагом при переходе к объектному методу создания программ. Все что было до этого написано, было написано именно для того, чтобы подчеркнуть эти отличия. Так как все равно остается последовательность действий, которая и есть программа, то структурные навыки тоже могут применяться, но, возможно, что это просто плохой стиль работы с объектами.