Что такое полиморфизм в ООП?

Ответ

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

Основные формы в C#:

  1. Полиморфизм подтипов (наследование и переопределение): Базовый класс объявляет виртуальный метод, производные классы предоставляют свою реализацию.

    public class Document
    {
        public virtual void Print() => Console.WriteLine("Печать базового документа.");
    }
    
    public class PdfDocument : Document
    {
        public override void Print() => Console.WriteLine("Отправка PDF на виртуальный принтер.");
    }
    
    public class WordDocument : Document
    {
        public override void Print() => Console.WriteLine("Запуск MS Word для печати.");
    }
    
    // Использование: один интерфейс, разное поведение
    List<Document> docs = new() { new Document(), new PdfDocument(), new WordDocument() };
    foreach (var doc in docs)
    {
        doc.Print(); // Вызовется своя реализация для каждого типа
    }
  2. Полиморфизм через интерфейсы: Разные, не связанные наследованием классы, реализуют один интерфейс.

    public interface ILogger
    {
        void Log(string message);
    }
    
    public class FileLogger : ILogger
    {
        public void Log(string message) => File.AppendAllText("log.txt", message);
    }
    
    public class ConsoleLogger : ILogger
    {
        public void Log(string message) => Console.WriteLine($"[LOG] {message}");
    }
    
    // Метод работает с любым ILogger
    public void ProcessOrder(ILogger logger)
    {
        logger.Log("Начало обработки заказа.");
        // ... логика
    }
  3. Перегрузка методов (Ad-hoc полиморфизм): Несколько методов с одним именем, но разными параметрами.

    public class Calculator
    {
        public int Add(int a, int b) => a + b;
        public double Add(double a, double b) => a + b;
        public string Add(string a, string b) => a + b; // конкатенация
    }

Выгода: Полиморфизм уменьшает связанность кода. Вы можете добавлять новые типы (например, ExcelDocument), не изменяя существующий код, который работает с базовым типом Document.

Ответ 18+ 🔞

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

В C# это обычно выглядит тремя способами, и сейчас я тебе их разжую, как говно за щекой.

1. Полиморфизм через наследование (или «когда дети умнее родителей»)

Тут есть базовый класс, который говорит: «Вот, смотрите, есть такой метод, он виртуальный». А дальше приходят его дети-наследники и такие: «Папаша, твой метод — говно, мы сделаем по-своему». И переопределяют его. В итоге ты вызываешь метод у папы, а работает реализация ребёнка. Магия, блядь.

public class Document
{
    // Базовый класс, метод виртуальный — можно переопределить
    public virtual void Print() => Console.WriteLine("Печать базового документа.");
}

public class PdfDocument : Document
{
    // А вот сынок уже умный, говорит: «Нахуй эту печать, я в PDF!»
    public override void Print() => Console.WriteLine("Отправка PDF на виртуальный принтер.");
}

public class WordDocument : Document
{
    // И этот тоже не лыком шит
    public override void Print() => Console.WriteLine("Запуск MS Word для печати.");
}

// А теперь смотри, в чём прикол
List<Document> docs = new() { new Document(), new PdfDocument(), new WordDocument() };
foreach (var doc in docs)
{
    doc.Print(); // Вызывается у всех одна и та же штука, но работает по-разному!
}

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

2. Полиморфизм через интерфейсы (или «договорились на берегу»)

А это когда классы вообще не родственники, но они оба подписали одну бумажку — интерфейс. Типа: «Ладно, мужики, вы можете быть кем угодно — хоть файлом, хоть консолью, — но метод Log у вас будет, договорились?» И все идут и делают.

public interface ILogger
{
    void Log(string message); // Контракт, блядь. Подписался — делай.
}

public class FileLogger : ILogger
{
    // Этот пишет в файл, тихий такой, трудяга
    public void Log(string message) => File.AppendAllText("log.txt", message);
}

public class ConsoleLogger : ILogger
{
    // А этот орет на всю консоль
    public void Log(string message) => Console.WriteLine($"[LOG] {message}");
}

// И вот ты пишешь метод, который принимает любого, кто подписал контракт ILogger
public void ProcessOrder(ILogger logger)
{
    logger.Log("Начало обработки заказа."); // А ему похуй, кто это — лишь бы Log был
    // ... дальше какая-то логика
}

Красота в чём? Захотел сменить логгер с файла на базу данных — написал новый класс DatabaseLogger, который тоже реализует ILogger, и подсунул его в метод. Весь остальной код даже не заметит подмены, потому что он работает с интерфейсом, а не с конкретной реализацией. Гениально и просто, как три копейки.

3. Перегрузка методов (или «один имя, много лиц»)

Это когда у тебя в одном классе несколько методов с одинаковым именем, но разными параметрами. Компилятор сам разберётся, какой из них вызывать, в зависимости от того, что ты ему суёшь.

public class Calculator
{
    public int Add(int a, int b) => a + b; // Для целых чисел
    public double Add(double a, double b) => a + b; // Для дробных
    public string Add(string a, string b) => a + b; // А это для строк — конкатенация, ёпта
}

Ты вызываешь Add, а система смотрит: ага, два int — значит, первый метод. Два double — второй. Две строки — третий. Удобно, не нужно выдумывать кучу разных имён вроде AddInts, AddDoubles, ConcatStrings. Всё называется Add, и всем хорошо.

Так в чём, блядь, выгода?

А выгода в том, что твой код становится гибким, как жопа гимнастки. Ты пишешь систему, которая работает с абстракцией (базовый класс Document или интерфейс ILogger), а потом можешь добавлять новые конкретные реализации (ExcelDocument, TelegramLogger) — и основная логика программы не меняется ни на йоту. Она продолжает тупо вызывать Print() или Log(), а новенькие классы сами разберутся, что делать.

Это и есть снижение связанности: твой высокоуровневый код не зависит от низкоуровневых деталей. Он просто знает, что есть некий контракт, и ему всё равно, кто и как его выполняет. А если нужно что-то новое — просто написал новый класс, реализовал тот же интерфейс, и вперёд. Без правок в старом коде, без риска всё сломать. Красота, да и только.

Видео-ответы