Абстрактные классы и интерфейсы: 7‑я часть гайда по ООП
2026-02-21 15:53 Diff

#Руководства

  • 24 янв 2020
  • 0

Узнайте истинную мощь наследования и полиморфизма! Раскрываем секреты абстрактных классов и интерфейсов.

vlada_maestro / shutterstock

Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.

В предыдущей статье мы увидели, насколько удобнее становится ООП благодаря наследованию. Но оно может стать ещё лучше, если использовать абстрактные классы и интерфейсы.

В широком смысле абстракция — это когда мы фокусируемся на тех свойствах системы, которые важны в рамках текущей задачи, а менее существенные отбрасываем.

Абстракции часто встречаются в повседневной жизни. Например, когда мы набираем и отправляем сообщения в мессенджере, то работаем лишь с клавиатурой и кнопкой «Отправить». Мы не задумываемся о версии приложения, о том, какую кодировку использует операционная система, сколько весит наше сообщение и т.д.

Другой пример — вождение автомобиля. Водитель думает лишь о том, куда поворачивать руль и когда нажимать педали. Он не задумывается о том, какого цвета его автомобиль, из какого материала сделана обивка салона, сколько присадок в моторном масле, и прочих вещах, которые не влияют на процесс вождения.

Похожая ситуация в объектно-ориентированном программировании, только там мы имеем дело с абстракцией данных и методов. Например, чтобы рассчитать зависимость количества потребляемого топлива от веса автомобиля, нам достаточно использовать атрибуты «вес» и «номер» (чтобы различать автомобили) и метод «ехать» (для моделирования пробега).

class Car { private string id_number; private int weight; //константа пути (100 км) private int const distance = 100; public Car(string id_number, int weight){ this.id_number = id_number; this.weight = weight; } int Go(){ //формула для расчета количества потребляемого топлива } };

Конечно, реальные автомобили отличаются цветом, габаритами, размером колес, типом шин, материалом обивки салона и т.д. А еще они умеют не только ехать, но и заводиться, тормозить и глохнуть. Но все эти подробности не имеют отношения к поставленной задаче, а значит, мы можем их смело опустить.

Абстрактные классы в объектно-ориентированном программировании — это базовые классы, которые можно наследовать, но нельзя реализовывать. То есть на их основе нельзя создать объект.

Пример такого класса из жизни — животное. Рыбы, пауки и насекомые являются животными, но каждое из них также принадлежит своему подклассу с набором специфических свойств. При этом, просто «животных» в природе не существует.

В программировании абстрактные классы могут понадобиться, чтобы объединить реализацию нескольких схожих классов. Например, в вашей игре должны быть персонаж игрока и NPC (неигровые персонажи). У них могут быть общие свойства (имя, координаты) и методы (перемещение, изменение анимации).

Чтобы не повторять код несколько раз, можно вынести реализацию этих свойств и методов в абстрактный класс Character. Для его объявления используют ключевое слово abstract:

abstract class Character { private string name; private int x; public int y; public Character(string name, int x, int y) { this.name = name; this.x = x; this.y = y; } public int X { get { return this.x; } } public void ShowName() { Console.WriteLine(this.name); } public abstract int Y { get; } public abstract void ShowPosition(); }

Тут всё как у обычных классов, но в конце можно заметить объявление свойства и метода без реализации. Реализация этих абстрактных свойств должна находиться в дочернем классе:

class Player : Character { public Player(string name, int x, int y) :base(name, x, y) { } public override int Y { get { return this.y; } } public override void ShowPosition() { Console.WriteLine($"[{X}, {Y}]"); } }

Когда объявляется реализация такого члена класса, необходимо указать ключевое слово override. Абстрактными могут быть следующие члены класса:

  • методы;
  • свойства;
  • индексаторы;
  • события.

Дочерний класс должен реализовывать все члены родительского абстрактного класса, кроме тех случаев, когда дочерний класс тоже абстрактный.

В остальном всё очень похоже на обычные классы. Например, поле Y класса Character публичное, чтобы можно было использовать его в свойстве Y дочерних классов.

Важно!

Абстрактный класс должен быть публичным.

Интерфейс похож на абстрактный класс: вы так же не можете объявить его экземпляр, дочерний класс должен реализовывать все члены интерфейса. Реализация членов может находиться как в интерфейсе, так и в дочернем классе.

Сходств очень много, но у интерфейсов есть особенность: один класс может наследовать несколько интерфейсов сразу.

Объявляется интерфейс следующим образом:

//Используется ключевое слово interface interface IInteractive //Названия интерфейсов принято начинать с заглавной буквы I { void Interact(Player p); //Метод без реализации //public bool Accessor { get; set; } //Доступно начиная с C# 8.0 bool IsInteractive() //Метод с реализацией { return true; } //const bool Interactive = true; //Константы //static bool Enabled = true; //Статические поля //Поля объявлять нельзя }

Также можно использовать индексаторы и события (это тема для отдельной статьи). Теперь рассмотрим применение этого интерфейса.

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($"[{X}, {Y}]"); } public void Interact(Player p) { Console.WriteLine($"{p.Name} interacting with {this.Name}"); } }

В отличие от абстрактных методов, методы интерфейса не нужно реализовывать с ключевым словом 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. Методы и свойства придумайте, исходя из названий.

Вот мы и рассмотрели основные части объектно-ориентированного программирования. Дальше вас ждёт практикум, в котором мы поработаем над полноценным проектом, чтобы закрепить полученные знания и узнать ещё немного полезностей.

Бесплатный курс по Python ➞
Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу