- Основы ООП: абстракция
- Основы ООП: инкапусляция
- Основы ООП: наследование
- Основы ООП: полиморфизм
- Основы ООП: композиция (дизайн)
- Определение класса
- Права доступа
- Конструкторы: по умолчанию, параметрический, копирования
- Деструкторы
- Ссылка на себя: this
- Наследование
- Динамическое создание объектов: new и delete
- Указатели
- Виртуальные методы (virtual и override)
- Статические члены
- Вложенные типы, пространства имен и оператор контекста '::'
- Разнесение объявления и определения класса
- Абстрактные классы и интерфейсы
- Перегрузка операторов
- Приведение объектных типов: dynamic_cast и указатель void *
- Указатели, ссылки и const
- Управление наследованием: final и delete
Абстрактные классы и интерфейсы
Для изучения абстрактных классов и интерфейсов вернемся к нашему сквозному примеру программы рисования. Её программный интерфейс для простоты состоит из единственного виртуального метода draw. До сих пор он был пустым, но вместе с тем даже такая пустая реализация является конкретной реализацией. Однако объекты класса Shape не могут изображаться — их фигура не определена. Поэтому метод draw имеет смысл сделать абстрактным или, как иначе это называют, чисто виртуальным.
Для этого блок с пустой реализацией следует убрать, а в заголовке метода добавить "= 0":
class Shape
|
Класс, у которого есть хотя бы один абстрактный метод, также становится абстрактным, потому что создать его объект не получится: нет реализации. В частности, у нас конструктор Shape был доступен производным классам (благодаря модификатору protected), и их разработчики могли, гипотетически, создать объект Shape. Но так было раньше, а после объявления абстрактного метода мы такое поведение пресекли, как запрещенное нами — авторами интерфейса рисования. Компилятор будет выдавать ошибку:
'Shape' - cannot instantiate abstract class
|
Наиболее правильным подходом для описания интерфейса является создание под него абстрактного класса, содержащего только абстрактные методы. В нашем случае, метод draw следует вынести в новый класс Drawable, а класс Shape унаследовать от него (Shapes.mq5).
class Drawable
|
Разумеется, интерфейсные методы должны находиться в секции public.
MQL5 предоставляет другой удобный способ описания интерфейсов — с помощью ключевого слова interface. Все методы в интерфейсе объявляются без реализации и считаются публичными и виртуальными. Описание интерфейса Drawable, эквивалентное вышеприведенному классу, выглядит так:
interface Drawable
|
В классах наследниках при этом ничего не приходится менять, если в абстрактном классе не было никаких полей (что было бы нарушением принципа абстракции).
Теперь пришло время расширить интерфейс и сделать тройку методов setColor, moveX, moveY также его частью.
interface Drawable
|
Обратите внимание, что методы возвращают объект Drawable, потому что ничего не знаю о Shape. В классе Shape у нас уже имеются реализации, которые подходят для переопределения этих методов, поскольку Shape унаследован от Drawable (объекты Shape "являются своего рода" объектами Drawable).
Теперь сторонние разработчики могут добавить в программу рисования другие семейства классов Drawable, в частности, не только фигуры, но и текст, растровые картинки, а также, представьте себе, коллекции других Drawable, что позволяет вкладывать объекты друг в друга и составлять сложные композиции. Достаточно наследоваться от интерфейса и реализовать его методы.
class Text : public Drawable
|
Если бы классы фигур распространялись в виде двоичной ex5-библиотеки (без исходных кодов), мы бы поставляли для неё заголовочный файл, содержащий только описание интерфейса, и никаких намеков о внутренних структурах данных.
Поскольку для виртуальных функций выполняется динамическое (позднее) связывание с объектом во время выполнения программы, есть вероятность получить критическую ошибку "вызов чистой виртуальной функции" ("Pure virtual function call"): программа при этом завершает работу. Это происходит, если программист по недосмотру "забыл" предоставить реализацию. Компилятор не всегда способен выявить такие упущения на стадии компиляции.