Лекция 14 (1160847)
Текст из файла
11
Лекция 14
Абстрактные классы и интерфейсы
Любой класс - это тип данных. Однако абстрактный класс – это не абстрактный тип данных!
Понятие абстрактного класса возникает довольно быстро, как только мы начинаем пользоваться объектно-ориентированным языком. Сами по себе иерархии классов не нужны без динамического связывания виртуальных меодов(язык без виртуальных методов – это уже не объектно-ориентированный язык).
Абстрактный класс возникает, когда в нескольких классах существуют признаки, общие для всех объектов. Именно эти общие признаки мы выносим в базовый класс, как можно дальше «наверх».
Однако некоторые функциональности могут быть реализованы только в производных классах. Таким образом неизбежно возникает понятие абстрактного класса.
Все языки, включающие понятие динамически свзяанного метода, включают понятие абстактного класса. Исключение: Оберон-2.
(Turbo Pascal 5.5– тоже не было понятия абстрактного класса, но там существовала стандартная процедура Abstract(). В Delphi абстрактный класс появился.) Большинство языкв для обозначения абстрактного класса используют ключевое слово abstract.
C#, Java
Абстрактный класс – это обычный класс со свойствами, признаками и методами, некоторая функциональность которого нам не известна.
Любого объект обладает поведением и состояние. Поведение объекта выражео его методами, а состояние – членами-данными.
Существуют методы и члены-данные, общие для всех – они принадлежат базовому классу, но реализовать некоторые из них мы можем только в конкретном(не астрактном) классе. Мы абстрагируемся от некоторого конкретного поведения.
Пример
Класс Figure. Чем он обладает:
1) точка привязки – есть у любой фигуры
2) все фигуры можно нарисовать, но каждую – по-разному(пример абстрактного поведения, разного для всех)
3)можем двигать фигуру – в принципе, обще для всех фигур. Любая фигура движется, по сути, одинаково(если мы имеем в виду параллельный перенос)
Можно ли реализовать алгоритм рисования для абстрактной фигуры?
Нет! Если фигура, к примеру, точка – это вызов функции рисования одного пикселя. Отрезок же рисуется совсем по-другому.
Move(dx, dy); //достаточно реализовать, например, в базовом классе
{ x+=dx; y+= dy; }
Почему почти во всех языках програмирования существует абстрактный класс. Объекты абстрактного класса, как правило, нельзя создавать. Компилятор Delphi, однако, раззрешает создавать объекты абстактного типа даных. Но выдает предупреждение. К неопределенным методам обращаться нельзя – иначе будет ошибка.
Объектный ко генерируется таким образом, что если мы написали прототип, но не реализовали его, то можно не вызывать эту функцию – и ошибки не будет. А если мы написали прототип виртуальной функции, но не реализовали ее ни в одном «рребенке»(производном классе), то компоновщик будет ругаться.
Попытки создавать объекты абстрактных классов невозможны почти во всех языках, что гарантирует нам меньше ошибо.
В C# и Java, если перед методом стоит ключевое слово abstract
1) реализация данного метода не требуется
2) класс становится абстрактным
3) перед определением класса должен стоять спецификатор abstract.
Замечание
Абстрактный класс и абсрактный тип даных – это ортогональные понятия.
abstract class Base{
abstract public void Draw();
….
};
Класс называется абстактным в нем есть хотя бы один виртуальный метод.
C#
class D: base{
public override void draw(){……};
};
Java
class D: extends base{
public override void draw(){……};
};
//обязательно поставить тело Draw!
Страуструп отказаслся от ключевого слова abstract, и использовал понятие чисто виртуальной функции
virtual прототип =0;
Может как содержать реализацию, так и не содержать.Существует даже контекст, где чисто виртуальная функция будет вызвана – но это приведет к ошибке.
Замечание.
При вызове функции в конструкторе ее виртуальность снимается(тут она не имеет смысла, ведь в конструкторе мы создаем объект).
Пусть у нас есть иерархия X->Y->Z
Класс X: пусть в нем есть функция f
virtual X() { f(); //this->f, через указатель, но компилятор подставит X::f() }
Пусть в классе Y f замещается.
Как мы знаем, конструкторы класов вызываются в порядке иерархии. При вызове функции в конструкторе класса Х функция f замещаться не будет!
На любой вызов нереализованной виртуальной функции вызовется стандартная процедура Abstract, которая повалит всю нашу программу.
Итог: в любом конексте выов чисто виртуальной функции ведет к ошибке, поэтому в большинстве языков создание объекта абстрактного класса запрещено.
Ада 1995, 2005
Если процедура привязана к тегированому типу данных, ее можно сделать абстрактной. При попытке ее вызвать опять же будет выдана ошибка. В Ада есть абстрактные методы, а абстрактных классов нет. Можно считать, что абстрактный класс в Ада – это тегированная запись с хотя бы одним абстрактным методом.
Примеры
Яркий пример неизбежности абстрактного класса – класс-архиватор. Существуют признаки, общие для всех: имя файала, куда он архивируется(либо какой-то поток в памяти, куда упрятываются архивированные данные).
Методы: extract, add, delete.
Все меоды, связанные с форматом архивации – виртуальные. Ведь мы хотим поддерживать разные форматы, а потому имеет смысл переорпределять методы для каждого конкретног формата в соответствющем классе-потомке.
Еще один пример – класс Image, метод FromFile – реализуется по-разному для каждого конкретного формата изображения.
Интерфейсы
Существует ли связь между абстрактным классом и интерфейсом?
Формально это два ортогональных понятия.
В абстрактном классе мы абстрагируемся от реализации методов.
В интерфейсе мы абстрагируемся от всей структуры класса.
Так как и там и там речь идет об абстракции, существуют ситуацииЮ когда абстактный класс превращается в абстрактный тип данных: если все методы скрыты, а открыты только виртуальные методы.
Пример. Множество.
Уже обсуждалось, что во всех индустриальных языках программирования множества реализованы в стандартной библиотеке, так как оптимальной реализации множества не существует. В STL, как мы помним, реализация основана на бинарных деревьях. В стандарте сказано, что сложность реализации должна быть логарифмической. Представим, что STL у нас нет или что мы очень ее не любим и хотим сами реализовать множество.
Очевидно, что в любом множестве должны поддерживаться операции include(const T&) и exclude(const T&).
Предлагается сделать так:
class ISet{
virtual void exclude(const T &)=0;
virtual void include(const T &)=0;
………………………………………..
};
Классы подобного рода принято называть интерфейсами.
Пусть у нас есть такой класс с только чисто виртуальными функциями. Структура его нам не нжна, мы укажем ее в конкретной реализации, в подклассах.
Такой класс называется абстрактным типом данных, если его структура полностью инкапсулирована(настолько, что ее вовсе нет).
Связь между абстрактным классом и абстрактным типом данных выражается именно вв интерфейсах. Интерфейс – это абстрактный класс, доведеннй до абсолюта.
Существует некий метод, который не хочется делать абстрактным. Это деструктор. Если мы не объявим деструктор как виртуальный, то это в 99% случаев будет свидетельствовать об ошибке.
Base * px = new Derived();
px->f();
delete px; //если мы не объявим деструктор как виртуальный, то уничтожится не объект класса Derived, а объект класса Base. Ресурсы освобождены не будут. Эту ошибку будет найти очень сложно, как и любую ошибку, связанную с утечкой ресурсов.
Общее правило: если есть классы с виртуальным методом, делайте деструктор виртуальным.
Итак, пишем:
class ISet{
virtual void exclude(const T &)=0;
virtual void include(const T &)=0;
virtual ~ISet(){};//ПУСТОЙ
};
По умолчанию, если не написать virtual, деструктор создасться невиртуальным.
Чисто виртуальным деструктор быть не может(тогда он вызовется в деструкторе подобъекта и будет ошибка)
Пример.
сlass SList{…………}; //базовый
class SList_set: public ISet, private SList{
{
ISet* Make() { …………..return new SList_set(); }
};
Заметим, что SList наследуется приватно – тот редкий сллучай, когда нужно приватное наследование.
Таким образом, что в С++ нет языкового понятия интерфейса, однако он легко моделируется вышесказанным образом.
В C# и Java понятие интерфейса существует на языковом уровне.
Interface имя {
объявление членов
};
Кроме функций-методов, как мы помним, в C# может появляться еще и свойство.
interface ISample{
int pi; //поле рассматриваетс как статическая константа
Если написать
int pi{gte; set}
то оно превратится в свойство.
Кстати, перед определением свойство может стоять слово virtual. То есть свойства могут быть виртуальными или чисто виртуальными.
В Java вместо get и set есть getpair и setpair.
Методы в интерфейсах объъявляются без модификаторов – все, что внутри интерфейса, обязано быть публичным по умолчанию. Однако protected все-таки может стоять. Модификатор private ставить бесмысенно.
Часто понятие интерфейса заменяют на контракт.
Класс, реализующий интерфейс, остается абстрактным, если не реализует ВСЕ методы.
Интерфейс на С++ можно смоделировать, как мы показали в примере на ээтой странице, при помощи множественного наследования. А множественное наследование всегда можно смоделировать при помощи включения объектов одного класа в другой как членов-данных.
Но множественное наследование удобно тем, что можно использовать несколько интерфейсов.
Заметим: в чистом виде множественное наследование есть только в С++, а C# и Java множественное наследование реализовано только для интерфейсов.
C#
class D: имя, интерфейсы:
Java
Class D extends Base implements именя интерфейсов
Интерфейсы могут содержать внутри себя любые вложенные классы.
Пример(Java)
Interface ISample{
сlass Settings{…} ; //может быть конретным классом
Settings InitData = new Settings();
//подразумевается, что жанное поле будет статическим
Замечание. Первая проблема при множественном наследовании – это конфликт имен. Пусть у нас есть колода карт(ну, не колода конечно, а класс):
interface ICard{
void Draw(); //раздавать колоду
interface IUIControl{
Draw();
}
Пусть наши программные средства умет работать с IUIControl.
Class Sangle implements ICard, IUIControl{//возникает конфликт имен: два имени Draw. Что из них мы должн релизовывать? Выбрать либо первый, либо второй класс.
В С++ такие реализации разрешаются через явное обращение. Вот так:
class D: public I1, I2{
virtual void I1::f(){…………};
virtual void I2::f(){…………};
………………
};
В С# появляется так называемая явная и неявная реализация интерфейсов.
Неявная реализация – это то, что мы всегда называем обычной реализацией.
Пример неявной реализации интерфейсов:
interface ISample{
void f(); };
class CoClass: ISample{
public void f(){………………..}; //если тут не поставить public, компилятор заругается.
};
Явная реализация интерфейсов:
class CoClass2: ISample{
void ISample f() {…………….}
//тут спецификатор public отсутствует, потому что попытка написать тут public карается
};
Как же вызвать данный метод, если он не публичный?
D * px;
px->f(); //непонятно, какой метд я хочу вызывать – ошибка.
Но px->I1::f(); //снова ошибка: попытка вызова чисто виртуальной функции.
Мы же хотим вызвать заместителя для I1. Это длается так:
((I1*)px)->f();
Замечание. Слова «явный» и «неявный» должны относиться к приведению типов, а не к классам(интерфейсам).
CoClass2 x;
(ISample)x.f();//явное приведение
При явной реализации вызов возможен лишь ри явом приведении.
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.