И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 23
Текст из файла (страница 23)
Внутренний(пакетный) уровень доступа означает разрешение доступа со сторонывсех классов, входящих в сборку (пакет).Внутренний (пакетный) доступ позволяет обойтись без понятия«друг класса». Полного аналога друга класса в C# и Java нет, поскольку закрытый доступ в этих языках запрещает доступ любым другимклассам. Однако если в сборке (пакете) есть классы, которые нуждаются во взаимном доступе (например, функции преобразованияв С#), следует обеспечить внутренний (пакетный) доступ к членамклассов.Все классы в сборке (пакете) считаются логически связаннымидруг с другом (отсюда и особый уровень доступа). Здесь возникаеттонкий момент, связанный с понятием наследования.
Являются липроизводные классы логически связанными с классами из «родительской» сборки (пакета)? Понятно, что производный и базовыйклассы тесно связаны, поэтому и возникает (во всех языках) защищенный уровень доступа. Однако если классы в сборке логическисвязаны с базовым классом, то они должны быть связаны и с производными классами, но насколько тесно? Язык Java разрешает всемклассам из пакета иметь доступ к защищенным членам классов изэтого же пакета (тем самым ослабляя понятие защищенного уровняпо сравнению с C++). Понятие защищенного доступа в языке C#аналогично C++, но появляется еще один (пятый) уровень доступа,эквивалентный защищенному уровню Java: внутренний защищенный( p r o te c te d i n t e r n a l ) , который определяется как «внутреннийили защищенный».Теперь окончательно уточним определения уровней доступа.В языке C# это следующие уровни:• p u b lic — открытый (неограниченный), доступный методам любых классов из любых сборок;• i n t e r n a l — внутренний, доступный методам всех классов изэтой же сборки;• p r o te c te d i n t e r n a l — внутренний, или защищенный, доступный только собственным методам, методам производных классов(из любых сборок) и методам всех классов из этой же сборки;• p r o t e c t e d — защищенный, доступный только собственным методам и методам производных классов (из любых сборок);• p r i v a t e — закрытый, доступный только собственным методам.П р и м е ч а н и е .
Строго говоря, можно было бы ввести и ещ е одинуровень доступа — внутренний и защ ищ енный, т. е. доступны й толькособственным методам и методам производных классов из этой же сборки.Такой уровень есть в промежуточном языке платформы .N E T и в реализации C++/CLI для этой платформы, но авторы C # не стали включать егов язык.113Уровни доступа в языке Java следующие:• p u b li c — открытый (неограниченный), доступный методам любых классов из любых пакетов;• (не имеет ключевого слова) — пакетный, доступный методам всехклассов из этого же пакета;• p r o t e c t e d — защищенный, доступный только собственным методам, методам производных классов из любых пакетов и методамвсех классов из этого же пакета;• p r i v a t e — закрытый, доступный только собственным методам.Синтаксически в C# и Java нет областей доступа, поэтому модификатор доступа распространяется только на член, перед которым онстоит.
При отсутствии модификатора доступа в C# подразумеваетсяp r i v a t e (как для классов, так и для структур), а в Java — пакетныйуровень.В заключение отметим, что в C# и Java есть возможность управлятьдоступностью классов из сборки (пакета), отсутствующая в C++.Если перед классом стоит модификатор p u b lic , то он доступениз любой сборки (пакета).
Отсутствие модификатора означает доступность класса только изнутри сборки (пакета).Абстрактные типы данныхВспомним, что в современных языках программирования типданных определяется как пара: множество значений и множествоопераций.Если тип данных рассматривать как класс, то множество значенийопределяется набором членов-данных, а множество операций — набором методов класса.Абстрактный тип данных (АТД) — это тип, в котором внутренняя структура данных полностью инкапсулирована.
Другими словами,с точки зрения пользователя абстрактный тип данных представлентолько множеством операций. Класс является абстрактным типомданных, если открытыми членами являются только методы.Говорят, что совокупность открытых членов класса составляетинтерфейс класса, поэтому интерфейс АТД представлен толькооперациями.Конечно, некоторые методы могут быть закрытыми. Эти методы называют вспомогательными. Главное, чтобы структура класса(члены-данные) была скрыта.Объектно-ориентированная парадигма подразумевает широкоеиспользование АТД. Некоторые языки, например SmallTalk, вообщезапрещают открытые члены-данные, тем самым любой класс является в этом языке абстрактным типом данных.Языки C++, С#, Java позволяют открывать члены-данные, но этосчитается «дурным тоном» с точки зрения объектно-ориентирован114ного стиля.
Даже если некоторые операции сводятся к присваиванию и(или) считыванию значения некоторой переменной — членакласса, то и в этом случае предлагается использовать не открытыйдоступ к члену-данному, а методы класса, называемые селекторами.Функции-селекторы — это пара функций, одна из которых (функцияget) считывает значение члена-данного, а другая (функция set) присваивает новое значение. Отсутствие одной из функций селекторовозначает запрещение соответствующей операции. Например, классVector на C++ (см. подразд. 7.2) может содержать get-селектор,возвращающий длину вектора, но set-селектора в классе нет, поскольку длина вектора не меняется в процессе его жизни:class Vector{public:int GetLengthO{ return size; }};Функции-селекторы подчеркивают дуализм данных и операций,поскольку позволяют абстрагироваться от того, как именно реализована сущность: как данное или как операция.
Важно, что функцииселекторы могут реализовываться не только как считывание-записьнекоторого данного, но и как полноценные операции, содержащиенетривиальные вычисления. Внутреннее устройство селекторов недоступно и неинтересно пользователю.Язык C# поддерживает абстракцию функций-селекторов, вводяконструкцию «свойство» (property).Свойство синтаксически выглядит, как член-данное класса. Обращение к свойству неотличимо от обращения к члену-данному (занекоторыми исключениями). Объявление свойства выглядит, какобъявление члена-данного, сразу за которым следует объявлениеget- и set-селекторов в фигурных скобках. При присваиваниисвойству значения вызывается set-селектор, в его теле присваиваемое значение доступно через идентификатор value (в контекстетела селектора value — это ключевое слово, а в других контекстах — идентификатор).
При считывании свойства вызывается getселектор, который должен вернуть значение свойства.Приведем простой пример:class PropSample{private int _datum = 0;public int Datum // выглядит как член-данное{/ / н о это свойствоget { return _datum; } // чтение115set { _ d a t u m = value;} // запись}}PropSample ps = new PropSample();ps.Datum = -1; // вызов set-селектора с value=-lConsole.WriteLine(ps.Datum) ; // вызов get-селектораЧасто свойства доступны только для считывания (не содержатset-селектора), что позволяет сохранить целостность объекта.Приведем пример класса Stack из подразд. 7.1 на языке C# (обратитевнимание на свойства):public class Stack{int [] body;int top = 0;public Stack(int size){body = new int [size];}public int Pop() { return body[--top]; }public void Push(int x) { body[top++] = x; }public bool Empty{get { return top ==0; }}public bool Full{get {return top == body.Length;}}public int Length{get { return body.Length; }}}В заключение отметим, что понятие АТД выходит за рамкиобъектно-ориентированного подхода и широко используется и вдругих парадигмах.
Еще один подход к реализации понятия АТДс объектно-ориентированной точки зрения, представленный интерфейсами, обсудим в следующей главе.Глава 8ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ МЕХАНИЗМЫВ СОВРЕМЕННЫХ ЯЗЫКАХПРОГРАММИРОВАНИЯ8.1. НаследованиеОбъектно-ориентированный стиль отличается использованиемследующих четырех основных механизмов:• абстракция;• инкапсуляция;• наследование;• полиморфизм.Здесь мы обсудим понятие наследования и связанное с ним понятие динамического полиморфизма.Наследование — отношение между классами, при котором класснаследник обладает всеми членами родительского класса (или множества родительских классов в случае множественного наследования).При этом класс-наследник может добавлять новые и переопределятьунаследованные члены.В документации по языкам C++ и C# родительский класс называется базовым, а класс-наследник — производным. Авторы языкаJava придерживались терминологии из SmallTalk: родительскийкласс — суперкласс, класс-наследник — подкласс.
Мы для единообразия будем использовать терминологию из C++, т. е. базовый ипроизводный.Сосредоточимся на одиночном наследовании (множественноенаследование в полном объеме реализовано только в C++).В объявлении производного класса указывается базовый класс,дополнительные и переопределяемые члены класса (объявление унаследованных членов повторять не надо).Рассмотрим одиночное наследование в языке C++. Синтаксисобъявления производного класса следующий:class имя-производного-класса :модификатор-доступа имя-базового-класса{ объявления-новых-и-переопределенных-членов}Например:class Derived: public Base{int new imember;117public:Derived ();int new_method ();};Вместо ключевого слова class может стоять слово struct (каки в определении класса без наследования).Модификатор доступа может отсутствовать, тогда для class подразумевается private, а для struct — public.Поясним смысл модификатора при имени базового класса.
В C++производный класс может усилить (но не уменьшить!) ограниченияна доступ к унаследованным членам. Если модификатор public, топрава доступа не изменяются (самый частый случай), если модификатор — protected, то унаследованные публичные члены становятсязащищенными в производном классе.