И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 28
Текст из файла (страница 28)
Реализация интерфейса полностьюинкапсулирована.Таким образом, понятие интерфейса очень хорошо сочетаетсяс понятием абстрактного типа данных (см. подразд. 7.3).Отметим, что за счет множественного наследования класс C++может реализовывать произвольное количество интерфейсов.Реализация интерфейсов в C# и JavaЭти языки в отличие от C++ явно поддерживают интерфейсы наязыковом уровне.
Одной из причин этого является то, что в них нереализовано множественное наследование в полном объеме.C# и Java поддерживают одиночное наследование класса и множественное наследование интерфейсов. Не вдаваясь в детали, отметим,что множественное наследование классов с членами-данными и виртуальными методами создает некоторые проблемы при реализации.Этих проблем нет, если наследуемые множественным образом классымогут иметь только (абстрактные) методы.Таким образом, в C# и Java есть конструкция «интерфейс».Интерфейс состоит только из объявлений прототипов методов.Модификаторов доступа нет, так как скрывать надо только реализацию, а методы интерфейса обязаны быть открытыми. Из данныхдопускаются только статические члены-константы.
Допускаютсятакже объявления вложенных интерфейсов (для «композиции» интерфейсов).Приведем примеры объявлений интерфейсов:interface IEnumerable // стандартный интерфейс C#{IEnumerator GetEnumerator();}141interface IEnumerator // стандартный интерфейс C#{object Current { get;bool M o ve N e x t ();void R e s e t () ;}}interface Iterable // стандартный интерфейс Java{Iterator iterator();}interface Iterator // стандартный интерфейс Java{boolean h a s N e x t ();Object n e x t ();void re mo v e ();}Приведем также примеры объявлений классов, реализующихинтерфейсы:class MyCollection : IEnumerable // C#{ --public IEnumerator GetEnumerator(){ ....
/* реализация метода */ }}class MyCollection implements Iterable // Java{ --public Iterator iterator (){ /* реализация метода */ }}Это примеры интерфейсов (из стандартной библиотеки C# и Java),которые объявляют «контракт» итераторов, позволяющих последовательно перебирать элементы коллекции.Класс может реализовывать произвольное количество интерфейсов:class Sample extends Base implements II, 12, 13, 14{ - - .}class SampleCS : Base, II, 12, 13, 14 {...}Объектов-интерфейсов нет, и это понятно почему: ведь интерфейс — обобщение понятия «абстрактный класс», а объекты абстрактного класса создавать нельзя. Однако можно получать ссылкуна интерфейс (точнее, на реализацию интерфейса) из ссылки накласс, реализующий этот интерфейс.
Ссылки на интерфейс можноиспользовать для вызова методов интерфейса.Таким образом, ссылка на интерфейс имеет синтаксис и ведетсебя как ссылка на объект базового класса. Этой ссылке можно при142сваивать ссылку на объект класса, реализующего интерфейс (что похоже на неявное преобразование из производного класса в базовый).Также ее можно явно (и только явно) преобразовывать к ссылке наобъект класса, реализующего интерфейс (это похоже на явное преобразование из базового в производный класс).Приведем следующий пример:void RemoveLongStrings (MyCollection coll, int maxLen)Iterable obj = coll;// ссылку на коллекцию поместили в интерфейсIterator i = o b j .iterator ();while (i.hasNext ()){String s = (String)i.next ();if (s.
l e n gt h() > maxLen)i .remove ();}}Эта функция удаляет из коллекции coll все строки длинойбольше заданного значения. Если коллекция содержит нестроковыеобъекты, то преобразование (String) i .next () сгенерирует исключение (см. гл. 9).Интеграция интерфейсов в язык программированияКак уже отмечалось, интерфейсы — мощное средство интеграциипрограмм, настолько мощное, что его можно использовать и для интеграции механизмов языка и пользовательских классов. Языки C#и Java поддерживают ряд стандартных интерфейсов, позволяющихинтегрировать семантику языковых конструкций и классы, реализующие эти интерфейсы.Например, приведенные ранее интерфейсы итераторов интегрируют классы-коллекции, реализующие эти интерфейсы, с цикломf oreach:MyCollection coll;int sum = 0;for (int i : coll)sum += i;Компилятор языка Java вставит обращ ения к интерфейсамIterable и Iterator, а также распаковку из Object в int.Другим примером является интерфейс IDisposable, используемый в using-блоке языка C# (см.
гл. 9).143Язык Java использует понятие пустых интерфейсов (интерфейсовмаркеров), которые специально предназначены для интеграциис транслятором. Интерфейсы-маркеры не содержат никаких методов,их смысл зафиксирован в документации и воплощен транслятором.Например, если класс реализует интерфейс-маркер Cloneable, то этоозначает, что класс будет реализовывать открытый метод clone ()создания своей копии:class CloneAttack implements Cloneable{public Object c lo n e (){// возвращает свою копию}}Следует отметить, что любой класс уже содержит версию методаclone (), унаследованную от класса Object.
Этот метод возвращаетповерхностную копию объекта. Однако проблема состоит в том, чтоэтот метод — защищенный, следовательно, может использоватьсятолько из производных классов или из классов своего пакета. Чтобыпозволить копировать себя внешним классам, используется интерфейс Cloneable. Более подробно подходы к копированию объектовв Java рассматриваются в [2].Глава 9БЕЗОПАСНОСТЬ И ОТКАЗОУСТОЙЧИВОСТЬПРОГРАММ9.1. Надежность программ. Подходык обеспечению отказоустойчивостипрограммИнтуитивно надежность программы — это свойство программыработать без отказов.
Мера надежности программы — вероятностьбезотказной работы в заданном окружении в течение заданного промежутка времени.Причина отказов программы — ошибки. Можно условно выделитьдва основных подхода к проектированию «безошибочных» (надежных) систем: математический и инженерный.При математическом подходе программа рассматривается какпреобразователь предикатов. Есть предусловие — предикат, описывающий входные данные и окружение, и постусловие — предикат, описывающий требуемый результат работы программы. Есликорректно построить программу как преобразователь предусловияв постусловие, то можно математически доказать ее правильность.Математический подход весьма сложен и не до конца разработан.Сложность построения предикатов сопоставима со сложностью разработки соответствующей программы (а нередко и превосходит ее).Кроме того, встает вопрос о том, правильно ли сформулированыпредикаты и о правильности собственно доказательства правильности.
Современная индустрия программирования не используетдоказательства правильности программ, хотя при разработке рядаособо критичных систем используются элементы математическогоподхода.Инженерный подход рассматривает разработку надежных программ как искусство построения надежной системы из потенциальноненадежных компонент.Надежная система должна быть готовой к появлению ошибоки реакции на них. В случае некритичных ошибок система должнавосстанавливать свое состояние и продолжать свое нормальноефункционирование, а в случае серьезных сбоев система может деградировать, но обязана продолжать работу в режиме ограниченнойфункциональности.Различают два основных источника ошибок (точнее, сбоев илиотказов):• не выявленные до текущего времени ошибки в программе;145• проблемы внешнего окружения (сбои оборудования, отказы связии т.п.).В любом случае программа должна адекватно реагировать на любые отказы (насколько это возможно).
Отказоустойчивые программыобязаны проверять результат выполнения каждого потенциальносбойного участка программы. Например, системные вызовы (обращения к сервису виртуального компьютера ОС) всегда возвращаютнекоторое значение, которое сигнализирует об ошибке. Также должны быть устроены и другие компоненты надежного кода.Основная проблема состоит в том, как именно реагировать наобнаружение ошибки.
В подразд. 9.3 будут рассмотрены два основныхподхода к обработке ошибок: семантика продолжения и семантиказавершения.В современных языках для обработки ошибок введено понятиеисключительной ситуации. Исключительная ситуация (или исключение) ассоциируется с ошибкой. Важно подчеркнуть, что использование исключительных ситуаций для других целей влечет засобой и резкое снижение как эффективности программы, так и ееотказоустойчивости. Следует придерживаться следующего принципа:исключительная ситуация — всегда «авария».Далее мы рассмотрим следующие аспекты обработки исключительных ситуаций (ИС):• определение ИС;• возникновение ИС;• распространение ИС;• реакция на ИС.Классическая техника обработки ошибок в императивном языкеподразумевает, что при каждом аварийном происшествии программаприобретает примерно следующий вид:if (проверка-возникновения-ИС)нормальный-ход-выполнения} else { // ошибкапытаемся-исправитьif (не-получается)return error_code;{}return SUCCESS;В такой программе нормальный ход выполнения и собственнокод, который обрабатывает ошибки, переплетены между собой, чтовесьма неудобно.Механизм обработки ситуаций, чтобы быть управляемым и контролируемым, должен поддерживать принцип разделяемое™ (отдельно код для нормальной ситуации и отдельно — для исправления ошибок).
Причем часто реакция на ошибку находится совсемв другом месте относительно места возникновения ошибки. Далее146рассмотри^ средства обработки ИС в языках программирования C++,Java и С#. Причем отметим, что базовые механизмы обработки ИСв этих языках практически идентичны.9.2. Определение исключительной ситуацииИдея определения ИС состоит в том, что ошибке (исключительной ситуации) сопоставляется тип данных. В языке C++ разрешается связать с исключительной ситуацией любой тип данных (какбазисный, так и определенный пользователем класс).