Абстрактные классы (C++)
Абстрактные классы используются в качестве обобщенных концепций, на основе которых можно создавать более конкретные производные классы. Невозможно создать объект абстрактного типа класса. Однако можно использовать указатели и ссылки на абстрактные типы классов.
Вы создаете абстрактный класс, объявляя по крайней мере одну чистую виртуальную функцию-член. Это виртуальная функция, объявленная с помощью синтаксиса чистого описателя ( = 0 ). Классы, производные от абстрактного класса, должны реализовывать чисто виртуальную функцию; в противном случае они также будут абстрактными.
Рассмотрим пример, представленный в функциях Virtual. Класс Account создан для того, чтобы предоставлять общие функции, но объекты типа Account имеют слишком общий характер для практического применения. Это означает Account , что это хороший кандидат для абстрактного класса:
// deriv_AbstractClasses.cpp // compile with: /LD class Account < public: Account( double d ); // Constructor. virtual double GetBalance(); // Obtain balance. virtual void PrintBalance() = 0; // Pure virtual function. private: double _balance; >;
Единственное различие между этим и предыдущим объявлениями состоит в том, что функция PrintBalance объявлена со спецификатором чисто виртуальной функции pure ( = 0 ).
Ограничения на использование абстрактных классов
Абстрактные классы нельзя использовать для:
- переменных и данных членов;
- типов аргументов;
- типов возвращаемых функциями значений;
- типов явных преобразований.
Если конструктор абстрактного класса вызывает чистую виртуальную функцию напрямую или косвенно, результат не определен. Однако конструкторы и деструкторы абстрактных классов могут вызывать другие функции-члены.
Определенные чистые виртуальные функции
Чистые виртуальные функции в абстрактных классах можно определить или реализовать. Такие функции можно вызывать только с помощью полного синтаксиса:
Определенные чистые виртуальные функции полезны при разработке иерархий классов, базовые классы которых включают чистые виртуальные деструкторы. Это связано с тем, что деструкторы базового класса всегда вызываются во время уничтожения объектов. Рассмотрим следующий пример:
// deriv_RestrictionsOnUsingAbstractClasses.cpp // Declare an abstract base class with a pure virtual destructor. // It's the simplest possible abstract class. class base < public: base() <>// To define the virtual destructor outside the class: virtual ~base() = 0; // Microsoft-specific extension to define it inline: // virtual ~base() = 0 <>; >; base::~base() <> // required if not using Microsoft extension class derived : public base < public: derived() <>~derived() <> >; int main() < derived aDerived; // destructor called when it goes out of scope >
В примере показано, как расширение компилятора Майкрософт позволяет добавлять встроенное определение в чистую виртуальную ~base() . Его можно также определить за пределами класса с помощью base::~base() <> .
Когда объект aDerived выходит из область, вызывается деструктор класса derived . Компилятор создает код для неявного вызова деструктора класса base после derived деструктора. Пустая реализация для чистой виртуальной функции ~base гарантирует, что для функции существует хотя бы какая-то реализация. Без него компоновщик создает неразрешенную ошибку внешнего символа для неявного вызова.
В предыдущем примере чистая виртуальная функция base::~base вызывается неявно из derived::~derived . Кроме того, можно явно вызывать чистые виртуальные функции с помощью полного имени функции-члена. Такие функции должны иметь реализацию, или вызов приводит к ошибке во время связи.
Зачем нужны абстрактные классы
Кроме обычных классов в Java есть абстрактные классы . Абстрактный класс похож на обычный класс. В абстрактном классе также можно определить поля и методы, но в то же время нельзя создать объект или экземпляр абстрактного класса. Абстрактные классы призваны предоставлять базовый функционал для классов-наследников. А производные классы уже реализуют этот функционал.
При определении абстрактных классов используется ключевое слово abstract :
public abstract class Human < private String name; public String getName() < return name; >>
Но главное отличие состоит в том, что мы не можем использовать конструктор абстрактного класса для создания его объекта. Например, следующим образом:
Human h = new Human();
Кроме обычных методов абстрактный класс может содержать абстрактные методы . Такие методы определяются с помощью ключевого слова abstract и не имеют никакой реализации:
public abstract void display();
Производный класс обязан переопределить и реализовать все абстрактные методы, которые имеются в базовом абстрактном классе. Также следует учитывать, что если класс имеет хотя бы один абстрактный метод, то данный класс должен быть определен как абстрактный.
Зачем нужны абстрактные классы? Допустим, мы делаем программу для обслуживания банковских операций и определяем в ней три класса: Person, который описывает человека, Employee, который описывает банковского служащего, и класс Client, который представляет клиента банка. Очевидно, что классы Employee и Client будут производными от класса Person, так как оба класса имеют некоторые общие поля и методы. И так как все объекты будут представлять либо сотрудника, либо клиента банка, то напрямую мы от класса Person создавать объекты не будем. Поэтому имеет смысл сделать его абстрактным.
public class Program < public static void main(String[] args) < Employee sam = new Employee("Sam", "Leman Brothers"); sam.display(); Client bob = new Client("Bob", "Leman Brothers"); bob.display(); >> abstract class Person < private String name; public String getName() < return name; >public Person(String name) < this.name=name; >public abstract void display(); > class Employee extends Person < private String bank; public Employee(String name, String company) < super(name); this.bank = company; >public void display() < System.out.printf("Employee Name: %s \t Bank: %s \n", super.getName(), bank); >> class Client extends Person < private String bank; public Client(String name, String company) < super(name); this.bank = company; >public void display() < System.out.printf("Client Name: %s \t Bank: %s \n", super.getName(), bank); >>
Другим хрестоматийным примером является система геометрических фигур. В реальности не существует геометрической фигуры как таковой. Есть круг, прямоугольник, квадрат, но просто фигуры нет. Однако же и круг, и прямоугольник имеют что-то общее и являются фигурами:
// абстрактный класс фигуры abstract class Figure < float x; // x-координата точки float y; // y-координата точки Figure(float x, float y)< this.x=x; this.y=y; >// абстрактный метод для получения периметра public abstract float getPerimeter(); // абстрактный метод для получения площади public abstract float getArea(); > // производный класс прямоугольника class Rectangle extends Figure < private float width; private float height; // конструктор с обращением к конструктору класса Figure Rectangle(float x, float y, float width, float height)< super(x,y); this.width = width; this.height = height; >public float getPerimeter() < return width * 2 + height * 2; >public float getArea() < return width * height; >>
Зачем нужны абстрактные классы
а если класс реализует 2 интерфейса с дефолтными реализациями метода которые называются одинаково? Нам нужно будет просто переопределять или как?
александр пьянов Уровень 34
24 февраля 2024
а если класс имплемитирует два интерфейса у обоих которых имеется метод с одинакоавм названием , как в примере пустб будеь public void on()?
Максим Li Уровень 36
12 ноября 2023
Хорошая статья!
Islam Yunusov Уровень 31
20 сентября 2023
Классная статья! Теперь дифференциация между интерфейсами и абстрактными классами стала понятнее после объяснение почему отказались от множественного наследования классов. Абстрактный класс — поведение и состояние концептуального класса. Интерфейс — просто описание поведения.
Dmitry Vidonov Уровень 29 Expert
1 сентября 2023
Хорошая статья!
26 августа 2023
Спасибо, все понятно и доступно объяснили)
chess.rekrut Уровень 25
21 августа 2023
Ant Уровень 29
22 июля 2023
Именно из-за такой «непонятки», когда объекту неясно, какое поведение он должен выбрать, создатели Java отказались от множественного наследования Даже не знаю, думаю пример не очень в статье, так как в большинстве источников сказано, что больше проблемы вызывают как раз обращение к переменным, при множественном наследовании. По сути те же интерфейсы могу создать неопределенную ситуацию с методами, но их(интерфейсы) все же добавили в язык.
Ислам Уровень 33
8 июня 2023
Евгений JackSun Уровень 32 Expert
7 марта 2023
У меня возник вопрос. Вот мы не можем наследовать от нескольких классов, потому что может возникнуть ситуация, когда в этих классах усть одинаковые названия методов с разной реализацией. Но что произойдёт, если мы реализуем несколько интерфейсов, у которых также есть одинаковые названия методов да ещё и с разной дефолтной реализацией?
Абстрактные классы и интерфейсы: 7‑я часть гайда по ООП
Узнайте истинную мощь наследования и полиморфизма! Раскрываем секреты абстрактных классов и интерфейсов.
Евгений Кучерявый
Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.
В предыдущей статье мы увидели, насколько удобнее становится ООП благодаря наследованию. Но оно может стать ещё лучше, если использовать абстрактные классы и интерфейсы.
Все статьи про ООП
- Что такое классы и объекты.
- Особенности работы с объектами.
- Модификаторы доступа, инкапсуляция.
- Полиморфизм и перегрузка методов.
- Полиморфизм.
- Наследование и ещё немного полиморфизма.
- Абстрактные классы и интерфейсы.
- Практикум.
Что такое абстракция в ООП
В широком смысле абстракция — это когда мы фокусируемся на тех свойствах системы, которые важны в рамках текущей задачи, а менее существенные отбрасываем.
Абстракции часто встречаются в повседневной жизни. Например, когда мы набираем и отправляем сообщения в мессенджере, то работаем лишь с клавиатурой и кнопкой «Отправить». Мы не задумываемся о версии приложения, о том, какую кодировку использует операционная система, сколько весит наше сообщение и т.д.
Другой пример — вождение автомобиля. Водитель думает лишь о том, куда поворачивать руль и когда нажимать педали. Он не задумывается о том, какого цвета его автомобиль, из какого материала сделана обивка салона, сколько присадок в моторном масле, и прочих вещах, которые не влияют на процесс вождения.
Похожая ситуация в объектно-ориентированном программировании, только там мы имеем дело с абстракцией данных и методов. Например, чтобы рассчитать зависимость количества потребляемого топлива от веса автомобиля, нам достаточно использовать атрибуты «вес» и «номер» (чтобы различать автомобили) и метод «ехать» (для моделирования пробега).
Также можно использовать индексаторы и события (это тема для отдельной статьи). Теперь рассмотрим применение этого интерфейса.
class NPC : Character, IInteractive < public NPC(string name, int x, int y) :base(name, x, y) < >public override int Y < get < return this.y; > > public override void ShowPosition() < Console.WriteLine($"[, ]"); > public void Interact(Player p) < Console.WriteLine($" interacting with "); > >
В отличие от абстрактных методов, методы интерфейса не нужно реализовывать с ключевым словом override.
Также есть одна особенность: метод, реализация которого находится внутри интерфейса, не может использовать этот метод — класс нужно привести к интерфейсу. Для примера добавим в класс Player следующий метод:
public void Interact(IInteractive obj) < if(obj.IsInteractive()) < obj.Interact(this); > >
В качестве параметра в этот метод можно передавать любой класс, который использует интерфейс IInteractive.
Player p = new Player("Gamer", 5, 10); NPC npc = new NPC("Cube", 10, 10); p.Interact(npc);
Это очень удобно в разработке игр, в которых взаимодействовать можно с самыми разными объектами — от NPC до предметов.
Более подробно об отличиях интерфейсов и абстрактных классов на примерах из Java можно прочитать в другой нашей статье.
Домашние задание
Создайте игру, в которой будут использоваться абстрактные классы Character и Item, а также интерфейсы IInteractive, ITalkable, IMovable. Методы и свойства придумайте, исходя из названий.
Заключение
Вот мы и рассмотрели основные части объектно-ориентированного программирования. Дальше вас ждёт практикум, в котором мы поработаем над полноценным проектом, чтобы закрепить полученные знания и узнать ещё немного полезностей.
Больше интересного про код в нашем телеграм-канале. Подписывайтесь!
Читайте также:
- Не Windows единой: как писать кроссплатформенные приложения с GUI на C#
- Лицензии BSD и MIT: чем они различаются и в каких проектах их используют
- Перечисления в C#: как правильно использовать enum