И.Г. Головин - Конспект лекций по курсу Языки программирования (1161120), страница 22
Текст из файла (страница 22)
В Java была попытка более ловкорешить проблему клонирования.Microsoft советует: Если Вы хотите реализовать копирование – то называйте интерфейс какхотите, но опишите подробно в комментарии. Не надо реализовывать ICloneable.Cloneable в Java - пустой интерфейс.void f() throws (x,y,z){…}throw x,y,zЕсли в программе возникает исключение какого-либо другого типа, то выполнениепрограммы прерывается.В Java такая ситуация, что выскочит что-то кроме x,y,z, невозможно.В Java введено 4 уровня поддержки операции копирования:1) Полная поддержкаclass X implements Cloneable {public X clone() throws(){…}}Пользоваться таким классом безопасно, он делает честную копию.2) Условная поддержкаclass X implements Cloneable {public X clone(CloneNotSupported) throws(){…}}3) Условная неподдержкаНе реализуется Cloneable.
Извне не доступен.1344) ЗапретНе реализует интерфейс. От клонирования остался Protected Object Clone().Перекрывается: throw new CloneNotSupported(); - меня копировать нельзя.Доступен производным, а также всем классам из данного пакета. Сволочь, котораяможет вас скопировать – это классы из Вашего пакета, либо наследник.В общем случае это говорит о сложности проблемы копирования.
Проблемы упрограммистов на C# такие же.АБСТРАКТНЫЕ ТД В ОО ЯПВ C#, Java, Delphi есть спецификатор abstract, который определяет задание абстрактногокласса.Что означает инкапсуляция с точки зрения понятия класса? И (чисто формально) как будетвыглядеть модуль АТД с точки зрения инкапсуляции? Определяется ТД и множествоопераций, причем понятие операций сводится к понятию функций, т.е. определяем ТД исоответствующие функции. От чего мы абстрагируемся? От структуры ТД.Обсудим, как инкапсуляция организована в классах.Закрытость членов классов для доступа извне реализуется при помощи управлениядоступом и управления видимостью.
Что это такое?Управление видимостью – говорим, что некоторые члены класса видимы.Есть некий класс. С точки зрения инкапсуляции все члены равноправны:class X{int i,j;void f(){i..}}доступ к i и j полностью открыт и равноправен. А как же осуществляется доступ с точкизрения внешних функций (глобальных функций или других методов)?В Java, например, нет глобальных функций, есть только методы.
В С#, Java существуют такжемеханизмы, описывающие совокупности классов.Необходимо рассмотреть, что в разных ЯП значат такие понятия, как единица дистрибуциии единица области действия(в данном случае имени).В С# единица видимости – пространство имен namespace(из С++).using namespace …;Все имена, доступные в пространстве имен, становятся непосредственно видимыми.В С# есть понятие частичных классов.
Частичные классы – конструкция, позволяющаяопределить один класс в нескольких файлах. Зачем нужен частичный класс? Чтобыотделить то, что сгенерировано, и трогать не нужно, и то, что трогать можно. Второевыделяется в отдельный класс.partial class …{…}1351 файл генерируется программными средствами, а второй для изменяемости – чистотехнологичная штучка. Ничего с точки зрения принципиального, разделение не дает.В Java появилось понятие пакета (package).package имя_пакета;Обязательно ли писать package и using? Package обязательно (в отличие от using)!Как использовать пакет? Можно с помощьюimport имя_пакета *; //все имена из пакетаИлиimportпакет.имя; // если нужно только 1 имяА что же такое все-таки получить доступ к соответствующей дистрибуции?Java снова упрощает вещи.
Это не всегда хорошо, но в данном случае это удобно.В Java сказано, что пакет = единица дистрибуцииКласс не является единицей дистрибуцией, только пакет! Чтобы использовать классы изпакета надо обратиться к соответствующему jar.В С# все запутанней.Можно одно и то же пространство имен «размазывать» по сборкам. Пространство имён ипакеты иерархичны, и это существенно с точки зрения видимости и поиска. Но квложенности файлов не имеет отношения. Можно несколько пространств имен поместить в1 сборку или разнести («размазать») по нескольким.Что такое сборка? Сборка – двоичный файл, содержащий управляемый код.
Вообще этодостаточно рыхлое понятие, но понятие инкапсуляции вводится именно в терминах сборки.При управлении видимостью: инкапсуляция говорит о том, что члены классы невидимы длядругих классов.При управлении доступом: любые имена видимы (правда есть скрытие), а если имя видимо,то оно может быть доступно или недоступно.В С++ реализовано управление доступом, в Java и то, и то (когда речь о пакете, то управлениедоступом, но когда речь о разных пакетах, то управление видимостью).В чем тонкости?Рассмотрим на примере поиска имен (С++): берется использующее вхождение, нужно найтиопределяющее вхождение.Как будем искать? Сначала в блоке, если в нем не нашли, смотрим в объемлющем блоке –например, базовом классе, если в нем не нашли, ищем еще в объемлющем блоке, например,пространстве имен.
Так, рано или поздно, дойдем до корня, тогда уже точно это будетnamespace. Когда будет найдено определяющее вхождение, поиск прекращается.136Специфика управления доступа: нашли имя – остановились, даже если доступа нет, имя всеравно видимо.При управлении видимостью – проскакиваем мимо имени.int f;class X{private void f()}class D:X{f = 0;// то, что есть функция f из X никого не волнует}Правило доступа определяет доступ.В Java:package p;class X{private void f()}// если тут разделение пакетов, то все private имена недоступныimport p.Хclass D extends X{public void f(){…}}Если всё в 1 пакете, то ситуация вообще говоря нехорошая:package p;class X{private void f()public void g() {f();}//в С++ такое невозможно}import p.xclass D extends X{private void F(){…}public void smth{g();}// вызовется f из этого класса(из-за// виртуальности)}В С# такие вещи вообще запрещены, т.к.
нельзя замещать приватную функцию.Мораль: нельзя делать приватные виртуальные функции в С#.А в Java можно?137Если всё в 1 пакете, то можно, а если разделение пакетов, то нет.F замещает f? Нет, потому что первой f не существует для Java - другой пакет!G вызовет f из ее класса («даже если мы заместим в другом классе из другого пакета, то мыее не заместили»).Важно понятие, что такое другой класс.
Рассмотрим на примере С++. Основные типы членовкласса:private - доступ открыт самому классу (т.е. функциям-членам данного класса) идрузьям (friend) данного класса, как функциям, так и классамprotected – доступ открыт «своим» + наследникамpublic – доступ открыт кому угодноДрузья(friend) – всего лишь «нашлепка». Основные первые три.Чем плоха эта модель? Если есть совокупность взаимодействующих классов, то она слишкомжесткая.В C#, Java вводится дополнительный уровень доступа internal, в Java пакетный (словазарезервированного для него нет).
Если в Java опущено явное слово, то это пакетныйуровень доступа. internal – разрешается доступ всех классов из одной единицыдистрибуции (!) Пакетный – разрешается доступ к любым классам из пакета (в C# изсборки).«Неудобно когда пространство имен включает другие пространства имен, которыеоказываются в другой сборке – но это лично моё мнение» – И.Г.public перед именем члена означает доступ к этому члену из любого другого класса (класснаследник, из данной единицы дистрибуции и из других единиц дистрибуции).private существует во всех языках, и обозначает(ура!) одно и то же –доступ только изданного класса.Осталось понять, что такое protected.В С++ доступ из любых классов наследников.В Java protected доступны с точки зрения любых производных классов независимо от пакета,НО! Ограничение: доступ только через ссылку на этот же класс.class X{protected prot;}class Y extensed X{prot//через ссылку на Y или его наследника}138С++, C#:Рассмотрим иерархию классовXYZXvoid Y::f{ Z z;z.prot;}X x;x.prot;В Java через ссылку thisX x = new X();x.prot;В С# по сравнению с Java семантика protected как в С++.Но есть еще уровень protected internal.Это уровень объединения доступов, т.е.
доступ к членам класса имеют наследники классаили любой класс из данной единицы дистрибуции, т.е. доступ из наследников и изпространства имен.Если речь об объединении можно было бы сделать и пересечение. Оно и сделано в С++/СLI в.Net, а также в MSIL, но в С# такого уровня нет.Как же реализуются АТД? (не следует путать с абстрактными классами).Чисто формально, АТД эквивалентен публичному интерфейсу (только методы).В ООП есть понятие абстрактного класса, правда то понятие не имеет отношения к АТД.НО! Есть понятие «интерфейс» (понятие чисто языковое), вот оно то имеет большоеотношение к АТД!Что такое абстрактный класс?Есть понятие мощности для бесконечных множеств (например, можно говорить,что Qпринадлежит множеству всех целых чисел).139Абстрактный класс: чисто виртуальные функции (ЧВФ).ВDВ – базовый, D – производный.В классе B: void f()В классе D:void f(int) – не есть перегрузка(разные области видмости), это скрытие(hiding)К f из B можно обратиться B::f() или B.f()В другом случае:В классе B: virtual void f()В классе D: void f() – функция-заместительКовариантные типы - это T1 и T2 такие, что T1 – подмножество Т2Производный тип всегда ковариантен базовому.Контрвариантные типы – это (наоборот) T1 и T2 такие, что T2 – подмножество T1Инвариантные типы - этоT1 и T2 такие, что T1 и T2 не ковариантные и не контрвариантные.В С++ легко создать инварантные ТД.В С#, Java:Object41231 и 2 – инвариантные, на разных уровнях иерархии3 и 4 – ковариантные140Заместитель: имеет тип, ковариантный замещаемому.
Совпадающий тип ковариантенсамому себе.Статический ТДДинамический ТД(дан при объявлении)(ссылки, указатели)динамический тип ковариантен статическомуВиртуальный вызовЕсли функция вызывается через указатель(ссылку)X *px;px -> f();Если f() объявлена, как virtual, то вызывается заместитель, который ближе к динамическомутипу.X – f()……Цепочка классовнаследников…YСнизу вверх будет произведен выбор ближайшей функцииВ С++На самом деле поиска заместителя производиться не будет, выберется из таблицывиртуальных методов:141XX1…XnYXk::f()Компоновщик поставитсюда нужный адресА вот в SmallTalk произойдет настоящий поиск. В нем все методы виртуальные, поэтомуязык крайне неэффективен. Зато можно дать возможность программисту выбрать какуювызвать функцию. Язык гибкий ( примерно также в JavaScript).В C++ как снять виртуальность?Px -> X::f();{ x.f();}void g(X&a);a.f(); - вызов виртуальныйX x;Y y;X =>Yg(x);g(y);void g(X a)a.f()X x;Y y;X =>Y142g(x);g(y)// виртуального вызова нетНа С++ «харакири» нам сделать никто не помешает:a = new X();a.f();– компилятор снимает виртуальностьfinal – запрет замещенияfinal class X{}final void f{} – все вызовы невиртуальныеX f() – виртуальнаяY f() – не может быть невиртуальной, т.к.