Лекция 14 (Лекции (2009) (Саша Федорова)), страница 2
Описание файла
Файл "Лекция 14" внутри архива находится в папке "Лекции (2009) (Саша Федорова)". Документ из архива "Лекции (2009) (Саша Федорова)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "Лекция 14"
Текст 2 страницы из документа "Лекция 14"
Пример.
Класс FileStream – поддерживает итерфейс IDisposable, содержащий в себе метод Dispose(), который явно помечает объекты как предназначенные для сборки мусора, делая сильную ссылку слабой.
FileStream реализует этот интерфейс явным образом. Но есть в нем невиртуальный метод Close(), вызывающий внутри себе Dispose(). Для чего эо сделано? Очевидно, Close() «аккуратнее», а человеку скорее придет в голову использовать метод Close(), а не Dispose.
При работе с файлами это крайне важно: если не закрыть файл(то есть не вызвать функции Close или Dispose), то он будет открыт до следующей сборки мусора, и снова открыть файл до этого момента будет трудно.
Пример(наверное C#)
class IControl{
void Paint();
};
interface IEdit: IControl{
…………..Paint()…………..
};
Interface IDropList: IControl{
………….Paint()………………….
};
Combobox в средах с графической визуализацией обладает свойствами и IEdit, и DropList, и ему нужны оба Paint().
Как реализовать метод Paint()? Тут, очевидно, напрашивается явная реализация интерфейсов – нам нужы оба метода. Реализовывать надо и для DropList, и для IEdit.
Реализация:
class ComboBox: IDropList, IEdit
{
void IDropList::Paint(){………};
void IEdit::Paint(){……………};
//для того, чтобы отрисовывать как-то по умолчанию, андо завести еще свой метод.
public void Paint(){…….IEdit.Paint();…………..}
Заметим, тут стоит public – вотличие от вышестоящей явной реализации, в которой может стоять только private.
};
В реализованном нами кллассе обращаться к методам можно как угодно: и явно, и неявно.
Замечание1. Если посмотреть реализацию некоторых вещей на C#, можно заметить, что многие методы в рализации интерейсов являются ззапечатанными(sealed)., а значит, их нельзя ззаместить. Следовательно, если мы по каким-то причинам попытаемся унаследовать ComboBox
class FancyComboBox{
public void Paint() {...........}; //просто не будет вызван ввиду того, что в C# референциальная модель данных и какую функцию выбрать, будет определяться динамически
//если тут написать override, то тоже будет ошибка – исходный метод ComboBox запечатан.
Почему все устроено именно так? Когда запечатывают виртуальный метод, это делается и соображений эфффектиности: если метод запечатан и виртуален, его нет в таблице виртуальный функций.
Замечание2. Интерфейсы-маркеры.Реализованы в Ада. Являюются разновидностьюстандартных интерфейсов, относящихся к категории ссылочных типов. Цель маркеров состоит в том, чтобы вызов через интерфейс был равносиелн по эффективности вызову виртуального метода.
Java: Iterable.
Интерфейсы-маркеры – это стандартные интерфейсы, доведенные до абсолюта. Контракт интерфейсов-маркеров описан только в документации. Это пустые интерфейсы, мы не можем увидеть их с своем коде.
Пример: интерфейс Cloneable – класс, поддерживающий этот интерфейс, обязан реализовать метод Clone.
Как мы уже заметили, интерфейсы поддерживают множественное наследование. Остановимся на нем подробнее.
Множественное наследование
Существует мнение, что то, что есть в языке Smalltalk(и не больше!) должно быть в каждом объектно-ориентированном языке. В Smalltalk множественного наследования нет.
В чистом виде множественное наследование есть только в С++,
а в Java, C#, Delphi наследование возможно только для интерфейсов.
Проблема реализации ComboBox в том, что он писался как терминальный в иерархии класс, не предполагалось, что он наследовался.
public new virtual void Paint() {……………..}
Как же нам использовать ComboBox? Можно не наследовать, а включить его.
(Кстати, специалисты Майкрософт слово «включение» заменили словом «агрегация».)
С какими проблемами мы сталкиваемся при множественном наследовании?
-
Конфликт имен
-
Виртуальные методы.
Конфликт имен решается через явное указание имени базы или через приведение к ссылке на базу.
Как обстоит дело с виртуальными методами?
Пусть у нас есть 2 базовых класса A и B и класс наследник С.
C++:
C c;
A * pa;
B * pb;
pa=&c; //в процессе такого присваивания указатели преобразуются. Допустимо
pb=&c; //допустимо
pa->f(); //C::f
pb->g();//C::g
Но! pa->g(); //Нельзя – ошибка – в А нету g().
Вывод: добавление механизма множественного наследования сильно усложняет механизм выова виртуальных методов.
Язык С++ хорош тем, что если мы не используем какие-то супер-особенности языка, мы не платим за это.
Ромбовидное(бриллиантовое) наследование
Пусть есть следующая иерархия классов:
С точки зрения распределения памяти наследование выглядит так:
Пусть А и В совпадают(такая вещь вполне допустима в случае, например, обобщенных списков: если Х содержится в линейном списке и Y содержится в линейном списке, то С содержится в обоих линейных списках)
Яркий пример ромбовидного наследования – потоки ввода/вывода. От базового класса base_ios наследуются istream(read) ostream(write). В базовом классе base_ios присутствует в членах-данных файловый дескриптор int fd;. В классе С, унаследованном от классов istream и ostream, будет содержаться 2 копии одного и того же файлового дескриптора.
Пусть base_ios – это А, istream – это Х, а ostream – это Y. тогда распределение памяти будет такое:
А иерархия будет выглядеть вот так:
На практике ромбовидное наследование встречается едва ли не чаще, чем обыкновенное множественное наследование. Таблица виртуальных методов, очевидно, должна для подобного наследования реализовываться по-другому. Именно поэтому, ввиду высокой сложности множественного наследования, его не реализуют в большинстве языков.
Чтобы базовый класс допускал ромбовидное наследование, надо сделать наследование виртуальным(а точнее, оба наследования):
class A….
class X: public virtual A{
.........................................
};
class Y: public virtual A{
.........................................
};
Ромб реализуется так:
class C: public X, public Y{…………….}
Тут virtual нигде писать не надо.
Вывод: ввиду нетривиальности механизма виртуальных методов и наследования иерархию классов перед программированием надо продумывать целиком сразу.