JavaRush /Курси /C# SELF /Абстрактні класи та методи

Абстрактні класи та методи

C# SELF
Рівень 22 , Лекція 1
Відкрита

1. Вступ

Абстрактний клас — це клас, який не можна створити безпосередньо через new. Його призначення — слугувати основою для інших, конкретніших класів. Це щось на кшталт шаблону або «креслення», що визначає загальну структуру та поведінку для похідних класів, але залишає деталі реалізації їм.

Аналогія для простоти: уявіть, що є концепція «Транспорт». У реальному світі ніхто не катається на абстрактному «Транспорті» (хіба що у власній уяві). Хтось їздить на машині, хтось плаває на кораблі, хтось літає на літаку. Абстрактний клас — це ваше креслення «Транспорт», яке описує, що в будь-якого транспорту має бути ключова можливість пересуватися (Move), але як саме це відбувається, слід визначити в похідних класах.


// Приклад абстрактного класу
public abstract class Transport
{
    public string Model { get; set; }

    // Абстрактний метод — без реалізації
    public abstract void Move();
}

Спробуєте створити об’єкт типу Transport — отримаєте помилку компіляції. Компілятор скаже: «не можна створити екземпляр абстрактного класу».

Коли і навіщо потрібні абстрактні класи?

  • Хочете об’єднати спільні характеристики та поведінку різних, але схожих об’єктів в одному місці.
  • Потрібно задати «єдиний контракт»: наприклад, «усі нащадки повинні вміти рухатися».
  • Є спільна реалізація деяких методів, але частину логіки слід уточнювати в похідних класах.

Абстрактний клас — дієвий інструмент для проєктування архітектури великої системи, де існують спільні для всіх «фічі», а специфіку реалізує кожен конкретний нащадок.

2. Відмінність абстрактного класу від звичайного класу

Питань тут чимало, тож розкладемо все по поличках.

Абстрактний клас vs звичайний клас

  • Звичайний клас можна створювати через new; він може містити повністю реалізовані методи.
  • Абстрактний клас:
    • не можна створити безпосередньо (abstract в оголошенні класу)
    • може містити як реалізовані, так і абстрактні (без тіла) методи
    • може містити поля, властивості, конструктори й навіть статичні методи

public abstract class Animal
{
    public string Name { get; set; }
    public void Sleep()
    {
        Console.WriteLine("Тварина спить!");
    }

    public abstract void MakeSound();
}

3. Абстрактні методи та властивості

Абстрактний метод

Абстрактний метод оголошують в абстрактному класі, але він не має тіла (реалізації). Похідний клас зобов’язаний реалізувати його так, як йому потрібно.


public abstract class Animal
{
    public string Name { get; set; }

    // Абстрактний метод
    public abstract void MakeSound();
}

Абстрактна властивість

Можна також оголосити абстрактну властивість, щоб усі нащадки були зобов’язані її реалізовувати:


public abstract class Shape
{
    // Абстрактна властивість
    public abstract double Area { get; }
}

4. Наслідування від абстрактних класів

Абстрактний клас може мати як абстрактні, так і реалізовані методи. Похідний клас зобов’язаний реалізувати усі абстрактні методи та властивості, інакше він сам стане абстрактним.

Розвиньмо наш «тваринний» застосунок, розпочатий у попередніх лекціях:


public abstract class Animal
{
    public string Name { get; set; }

    public void Sleep()
    {
        Console.WriteLine($"{Name} солодко спить!");
    }

    public abstract void MakeSound();
}

Тепер створімо звичайний, неабстрактний клас:


public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} каже: Гав!");
    }
}

Спробуйте не реалізувати MakeSound() — і побачите помилку компіляції.

Схема: ієрархія класів з абстрактним класом


+---------------------+
|   Animal (abstract) |
+---------------------+
| Name: string        |
| + Sleep()           |
| + MakeSound()*      |
+---------------------+
         ^
         |
  +------+-------+
  |              |
 Dog            Cat

* MakeSound() — абстрактний метод

5. Поліморфізм у дії: як працює абстракція на практиці

Застосуймо це в нашому мінізастосунку. Уявіть, що в нас є список тварин — нехай це будуть собаки й коти.


List<Animal> animals = new List<Animal>
{
    new Dog { Name = "Бровко" },
    new Cat { Name = "Мурчик" }
};

foreach (var animal in animals)
{
    animal.MakeSound(); // Поліморфний виклик: спрацює своє
    animal.Sleep();     // Викликається реалізований у базовому класі метод
}

Клас Cat реалізуємо аналогічно:


public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} каже: Няв!");
    }
}

Практична користь у справжніх проєктах

Абстрактні класи широко використовують в архітектурі великих застосунків. Наприклад, у графічних редакторах зазвичай є абстрактний клас Shape із методами на кшталт Draw() і властивостями на кшталт Area. Конкретні фігури (прямокутник, коло) реалізують ці абстрактні методи за власними формулами. Завдяки такому підходу можна створювати багато різних фігур, не переписуючи всю логіку програми.

6. Особливості та обмеження абстрактних класів

Створити екземпляр не можна

Компілятор категорично заборонить робити так:


Animal a = new Animal(); // Помилка!

Створювати можна лише похідні (конкретні) класи.

Абстрактні класи можуть містити:

  • Оголошення абстрактних методів (без реалізації)
  • Реалізовані методи (з тілом)
  • Властивості та поля (абстрактні або реалізовані)
  • Конструктори — так, в абстрактного класу можуть бути конструктори, але їх може викликати тільки нащадок, а не безпосередньо код, що створює об’єкт.

Абстрактні класи підтримують наслідування

Можна будувати цілі ієрархії абстракцій:


public abstract class Creature
{
    public abstract void Live();
}

public abstract class Animal : Creature
{
    public abstract override void Live();
    public abstract void MakeSound();
}

Якщо в ланцюжку наслідування клас не реалізує всі абстрактні методи, він має бути оголошений як абстрактний.

7. Абстрактні класи та конструктори

Абстрактний клас може містити конструктори, які викликаються з конструкторів похідних класів.
Важлива особливість: конструктори абстрактних класів зазвичай оголошують як protected, щоб підкреслити, що вони призначені лише для використання нащадками, а не для прямого створення екземплярів.


public abstract class Animal
{
    public string Name { get; set; }
    
    // Конструктор оголошено як protected
    protected Animal(string name)
    {
        Name = name;
        Console.WriteLine($"Створено тварину на імʼя {name}");
    }
    
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public Dog(string name) : base(name)
    {
        // base(name) викличе конструктор Animal
        Console.WriteLine("Створено собаку");
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} каже: Гав!");
    }
}

Навіщо використовувати protected-конструктори?

  • Ясність намірів: protected явно показує, що конструктор призначено лише для нащадків
  • Запобігання помилкам: хоча компілятор і так заборонить створення екземпляра через new, protected-конструктор робить цей намір явним на рівні API класу, прибираючи навіть теоретичну можливість непорозуміння
  • Добра практика: дотримання принципів інкапсуляції та чіткого API

Можна також використовувати public-конструктори в абстрактних класах, але protected точніше відображає їхнє призначення.

8. Порівняння абстрактних і віртуальних методів

Абстрактні методи — це різновид віртуальних (по суті вони завжди virtual, хоча це слово до них не застосовується), але в них не можна писати реалізацію. Якщо ви хочете, щоб дехто з нащадків міг перевизначити метод (але не зобов’язаний), використовуйте virtual. Якщо зобов’язаний — лише abstract.


public abstract class Worker
{
    // Кожен зобовʼязаний реалізувати
    public abstract void Work();

    // Будь-хто може перевизначити, але не зобовʼязаний
    public virtual void Rest()
    {
        Console.WriteLine("Стандартний відпочинок!");
    }
}
Абстрактний метод Віртуальний метод
Наявність реалізації в базовому класі Ні, лише сигнатура (оголошення без тіла) Так, є реалізація за замовчуванням
Де можна оголошувати Тільки в абстрактному класі У будь-якому незапечатаному (non-sealed) класі, зокрема й в абстрактних
Нащадок зобов’язаний перевизначити Обов’язково для першого неабстрактного нащадка Необов’язково: можна перевизначити, а можна використати базову реалізацію
Можна викликати напряму Ні (метод не має тіла) Так (виконається базова версія)
Основне призначення Примусити нащадків надати свою унікальну реалізацію; визначити «контракт» Дати нащадкам змогу змінити поведінку, якщо це потрібно

9. Застосування абстрактних класів у вашому проєкті

Продовжуємо розвивати наш навчальний застосунок. Припустімо, тепер потрібен абстрактний клас для обробки користувацького введення (наприклад, форми різних типів).


public abstract class InputForm
{
    public string Title { get; set; }

    // Абстрактний метод, який реалізує процес введення
    public abstract void ShowAndHandleInput();
}

public class LoginForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("Введіть логін: ");
        string login = Console.ReadLine();
        Console.Write("Введіть пароль: ");
        string password = Console.ReadLine();
        Console.WriteLine("Автентифікацію завершено!");
    }
}

public class RegistrationForm : InputForm
{
    public override void ShowAndHandleInput()
    {
        Console.WriteLine($"=== {Title} ===");
        Console.Write("Вигадайте логін: ");
        string login = Console.ReadLine();
        Console.Write("Вигадайте пароль: ");
        string password = Console.ReadLine();
        Console.WriteLine("Реєстрацію завершено!");
    }
}

Використання:


InputForm[] forms = new InputForm[]
{
    new LoginForm { Title = "Вхід" },
    new RegistrationForm { Title = "Реєстрація" }
};

foreach (var form in forms)
{
    form.ShowAndHandleInput();
}

Цей підхід стає все потужнішим у міру зростання вашого застосунку. Абстрактний клас — чудова основа для створення розширюваних систем, де принципи DRY і SOLID (не повторюйтеся, пишіть модульно й зручно для розширення) — ваші найкращі друзі.

10. Типові помилки під час роботи з абстрактними класами та методами

Помилка № 1: забута реалізація абстрактного методу.
Якщо похідний клас не реалізує всі абстрактні методи та властивості, компілятор видасть помилку. Це трапляється доволі часто, особливо під час роботи з чужим кодом або великими ієрархіями. Уважно перевіряйте, чи не пропущено чогось важливого.

Помилка № 2: спроба зробити метод одночасно abstract і static.
Так не вийде. Абстрактні методи призначено для реалізації в екземплярах класу, а статичні — взагалі не потребують екземпляра. Ці підходи суперечать один одному, і компілятор не дасть це скомпілювати.

Помилка № 3: «ліниве» уникнення реалізації всіх абстрактних членів.
Якщо ви не хочете реалізовувати всі абстрактні методи, доведеться зробити клас теж abstract. Це не помилка як така, але може свідчити про заплутану архітектуру. Іноді варто винести часткову реалізацію в проміжний абстрактний клас, щоб полегшити життя нащадкам.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ