Код
#статьи

Простыми словами о полиморфизме: 5‑я часть гайда по ООП

Объясняем сложную концепцию объектно-ориентированного программирования на примере интернет-магазина.

Иллюстрация: Катя Павловская для Skillbox Media

Если вы начали изучать объектно-ориентированное программирование, то наверняка слышали о четырёх его главных концепциях: абстракции, инкапсуляции, наследовании и полиморфизме. Первые три мы подробно обсуждали в других статьях, а в этой поговорим о полиморфизме — разберёмся, что это такое, как работает и в чём его практический смысл для разработчика.

Перед началом: многие нюансы из этой статьи будет сложно освоить без понимания сути и основных принципов ООП. Если хотите освежить свои знания, почитайте наш подробный материал.

Все статьи про ООП

Что такое полиморфизм

Мы уже знаем, что в ООП все программы состоят из объектов. Но у разных на вид объектов может быть одинаковый интерфейс — какие-то общие методы, которые они выполняют каждый по-своему. Например, у объектов «квадрокоптер» и «самолёт» общим методом будет «летать».

Так вот, полиморфизм даёт возможность использовать одни и те же методы для объектов разных классов. Неважно, как эти объекты устроены, — в ООП можно сказать самолёту и квадрокоптеру: «Лети», и они будут делать это как умеют: квадрокоптер закрутит лопастями, а самолёт начнёт разгон по взлётно-посадочной полосе.

Грубо говоря, полиморфизм — это диспетчер в аэропорту. Ему неважно, какую топливную систему предусмотрел авиаконструктор и как работает система форсажа — он просто даёт команду: «Взлёт разрешаю». После этого на лайнере начинают происходить какие-то свои внутренние процессы, на которые диспетчер уже не влияет.

Минутка семантики. Слово «полиморфизм» переводится с греческого как «многоформенность». Смысл в том, что один и тот же метод может воплощаться по-разному — например, как полёт у дрона и самолёта.

Как полиморфизм выглядит в коде

Допустим, мы делаем онлайн-магазин с мерчем известного ютуб-канала. У нас есть три вида товаров: футболки, кружки и блокноты. Задача — сделать так, чтобы все их можно было складывать в корзину и сайт каждый раз автоматически выдавал покупателю сообщение: «Товар такой-то добавлен в корзину». Как это сделать?

Есть два варианта. Можно писать свою версию метода «добавить в корзину» на каждую категорию товара — но это долго, да и код получится неопрятный. А можно написать один полиморфный метод, а потом использовать его для каждого нового объекта — и вот это как раз наш случай. Разберём весь процесс пошагово.

Шаг 1. Создаём базовый класс «Товар»

Базовый класс описывает общие свойства объектов. Например, у нас это название товара и возможность добавлять его в корзину.

public class Good // Объявляем класс «Товар»  
{
    public string name; // Указываем свойство «Название»  
// Создаём метод «Добавить в корзину»
// Virtual означает, что метод потом можно будет дополнить под нужды конкретного товара.
    public virtual void AddToCart() 

    {
        Console.WriteLine("Товар " + name + " добавлен в корзину");
    }
}

Пока всё это выглядит очень абстрактно, но дальше — больше.

Шаг 2. Создаём три производных класса: «Футболка», «Кружка» и «Блокнот»

Производные классы расширяют функциональность основного. То есть задают какие-то дополнительные параметры — в нашем случае это характеристики товаров: размер футболки, объём кружки и количество страниц у блокнота.

 public class Cup : Good // Создаём класс «Кружка»  
{
    public int volume; // Вводим дополнительное свойство «Объём»  
    public override void AddToCart() 
// Override означает, что мы переопределяем метод AddToCart и добавляем ему новую функциональность 

    {
        Console.WriteLine("Кружка " + name + " объёмом " + volume + " мл добавлена в корзину");
    }
}

public class Note : Good // Создаём класс «Блокнот» 
{
    public int pages; // Добавляем свойство «Количество страниц» 
    public override void AddToCart()
    {
        Console.WriteLine("Блокнот " + name + " на " + pages + " страниц добавлен в корзину");
    }
}

public class Shirt : Good // Создаём класс «Футболка» 
{
    public int size; // Добавляем свойство «Размер»  
    public override void AddToCart()
    {
        Console.WriteLine("Футболка " + name + " размером " + size + " добавлена в корзину");
    }
}

Шаг 3. Вызываем метод «Добавить в корзину»

Допустим, пользователь зашёл в наш магазин и решил купить три товара: футболку «Кислота» 48 размера, кружку «Омут» на 250 мл и блокнот «Древо жизни» на 180 страниц. Поставил он галочки напротив каждого товара, нажал «Добавить в корзину» — и вот какая история начинает происходить в коде:

using System;
class Program { 
  static void Main() {
    // Создаём объекты для каждого товара
    Shirt someShirt = new Shirt(); 
    someShirt.name = "Кислота"; // Футболка «Кислота»  
    someShirt.size = 48;
    
    Cup someCup = new Cup(); // Кружка «Омут» 
    someCup.name = "Омут";
    someCup.volume = 250;
    
    Note someNote = new Note();
    someNote.name = "Древо жизни"; // Блокнот «Древо жизни» 
    someNote.pages = 180;
    
    // Создаём массив из всех трёх объектов
    Good[] goods = new Good[3];
    goods[0] = someShirt;
    goods[1] = someCup;
    goods[2] = someNote
 // С помощью цикла вызываем метод «Добавить в корзину» для каждого товара
    for(int i = 0; i < 3; i++)
    {
        goods[i].AddToCart();
    }
  }
}

Мы объединили товары в массив и с помощью цикла применили метод AddToCart сразу ко всем. Если бы наш магазин был реальным, пользователь получил бы примерно такие сообщения:

Футболка «Кислота размером» 48 добавлена в корзину
Кружка «Омут» объёмом 250 мл добавлена в корзину
Блокнот «Древо жизни» на 180 страниц добавлен в корзину

То есть мы достигли как раз того результата, который и планировали. Запустить код программы целиком и посмотреть, как он работает, можно по этой ссылке.

Для чего нужен полиморфизм

Смысл полиморфизма в том, что нам не надо писать для каждого товара свой метод — например, какой-нибудь AddToCartShirt для футболки или AddToCartCup для кружки. У нас просто есть один AddToCart, и мы на него полагаемся. Если в магазине появятся, например, кепки, мы просто немного допилим наш метод под особенности кепок, и дело в шляпе.

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

Отсюда можно выделить три главных преимущества полиморфизма:

  • Читаемость. Чем меньше кода и чем лучше он упакован, тем проще работать программистам и тем быстрее идёт разработка.
  • Масштабируемость. Можно добавить в магазин носки, ручки, напульсники, подвески и ещё кучу разных товаров, а за их покупку всё равно будет отвечать одна команда — AddToCart.
  • Предсказуемость кода. Когда у нас есть один метод для разных объектов, мы чувствуем себя спокойно. Можно не переживать, что команда «Добавить в корзину трусы» случайно применится к носкам и вызовет какой-то сбой в программе.

Что дальше

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

Проверьте свой английский. Бесплатно ➞
Нескучные задания: small talk, поиск выдуманных слов — и не только. Подробный фидбэк от преподавателя + персональный план по повышению уровня.
Пройти тест
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована