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

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

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

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

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

Тепер давайте заглибимося в деталі й розглянемо складніші практичні сценарії. У цій лекції ми зосередимося на нюансах синтаксису, на тому, як абстракція працює в багаторівневих ієрархіях, і чітко розмежуємо, коли використовувати abstract, а коли — virtual.

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

Абстрактний метод оголошується лише в абстрактному класі

Спроба оголосити абстрактний метод у звичайному (неабстрактному) класі призведе до помилки компілятора. Чому? Бо звичайний клас можна створювати безпосередньо, а що ми отримаємо при спробі викликати нереалізований метод? Лише загадковий погляд IDE.


// Це спричинить помилку компіляції:
public class WrongClass
{
    public abstract void Oops(); // Так робити не можна!
}

Якщо клас містить хоча б один абстрактний метод, він обовʼязково має бути позначений як abstract!

Реалізація абстрактних методів у похідних класах

Реалізація відбувається через ключове слово override. Клас, що наслідує абстрактний клас і не реалізує всі абстрактні методи, також має бути абстрактним (інакше компілятор повідомить про помилку).

Продовжмо ідею нашого застосунку:

У попередніх лекціях ми створили клас Shape (фігура), у якому оголосили абстрактний метод для обчислення площі. Тепер створимо конкретні фігури:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Чому абстрактні методи такі важливі?

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

2. Абстрактні властивості (properties)

Що таке абстрактна властивість

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

Приклад:
Додамо властивість Name для фігури — вона має бути в кожного нащадка, але кожен сам вирішує, що повертати.


public abstract class Shape
{
    public abstract string Name { get; }

    public abstract double CalculateArea();
}

Тепер кожен нащадок зобовʼязаний реалізувати цю властивість:


public class Rectangle : Shape
{
    public double Width { get; }
    public double Height { get; }
    public override string Name => "Прямокутник";

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; }
    public override string Name => "Коло";

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Особливості синтаксису

Абстрактна властивість схожа на інтерфейсну: вона оголошується без тіла реалізації, але із зазначенням, чи матиме вона тільки getter, чи також setter.


public abstract class Creature
{
    // властивість лише для читання
    public abstract string Species { get; }

    // властивість для читання й запису
    public abstract int Age { get; set; }
}

У класі, який її реалізує, можна визначити логіку отримання (і за потреби — зміни) значення.

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

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

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

3. Схема: як повʼязані абстрактний клас, методи та властивості

Давайте поглянемо на наступну схему, щоб побачити, як це працює:


┌─────────────┐
│ abstract    │
│   Shape     │
│-------------│
│ +Name: str  │  <-- abstract property
│ +Area(): dbl│  <-- abstract method
└─────┬───────┘
      │
 ┌────▼────┐      ┌───────┐
 │Rectangle│      │ Circle│
 │........ │ .... │ ......│
 │+Name    │      │+Name  │
 │+Area()  │      │+Area()│
 └─────────┘      └───────┘

Так ми забезпечуємо єдиний інтерфейс роботи з будь-яким нащадком класу Shape, але кожен нащадок вільний робити «під капотом» що завгодно.

UML-діаграма для абстрактного методу та властивості:


┌────────────────────────────┐
│        abstract class      │
│           Animal           │
│────────────────────────────│
│+ Name: string {abstract}   │
│+ MakeSound(): void {abstract}│
└─────────────┬──────────────┘
              │
     ┌────────┴──────────┐
     │                   │
┌────────────┐     ┌─────────────┐
│    Cat     │     │    Dog      │
│────────────│     │─────────────│
│+ Name      │     │+ Name       │
│+ MakeSound()│    │+ MakeSound()│
└────────────┘     └─────────────┘

4. Практичні сценарії та користь для реальних проєктів

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

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

Питання «навіщо» і повернення до поліморфізму

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

Такий підхід активно використовується в системах плагінів (IDE, графічні редактори, CRM‑системи), коли сторонні розробники створюють свої розширення, але зобовʼязані реалізувати мінімальний набір функцій, потрібних платформі. Наприклад, якщо ви пишете модуль для обробки файлів, базовий клас може містити абстрактну властивість FileExtension і абстрактний метод Open(), щоб будь-який плагін міг коректно обробляти файли свого типу.

Відмінності між абстрактними, віртуальними та звичайними членами класу

Характеристика Звичайний метод/властивість virtual abstract
Наявність реалізації Є Є Немає
Обовʼязковість override Ні Ні (можна перевизначати) Так (обовʼязково в нащадку)
Можна викликати напряму Так Так Ні
Може бути у звичайному класі Так Так Ні
Може бути позначений sealed Ні Так Ні

5. Застосування в нашому навчальному застосунку

Тепер, озброївшись новими знаннями, повернімося до нашого застосунку з фігурами й подивімося, як абстрактні властивості та методи працюють разом, щоб створити гнучку й зрозумілу систему.


// Наприклад, в основному методі програми
List<Shape> shapes = new List<Shape>
{
    new Rectangle(4, 5),
    new Circle(2.5)
};

foreach (Shape shape in shapes)
{
    // Завдяки абстрактній властивості Name і методу CalculateArea
    // ми не переймаємося конкретним типом фігури
    Console.WriteLine($"{shape.Name}, Площа: {shape.CalculateArea():F2}");
}

Результат:

Прямокутник, Площа: 20.00
Коло, Площа: 19.63

Краса: код не знає і не переймається, яка саме це фігура — він просто викликає потрібні властивості й методи, а .NET CLR подбає про правильну реалізацію!

6. Часті помилки та особливості реалізації

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

Помилка № 2: метод оголошено як abstract, а клас — ні.
Такий код теж не компілюється. Якщо ви додали у клас abstract-метод, отже, сам клас має бути абстрактним:


public abstract class Polygon : Shape
{
    // Не реалізуємо CalculateArea(), клас залишається abstract
}

Помилка № 3: спроба реалізувати абстрактний метод через virtual.
Абстрактний метод має бути реалізований з ключовим словом override, а не virtual. Але якщо ви хочете, щоб реалізацію могли перевизначати далі по ієрархії, ви можете спочатку перевизначити метод, а згодом оголосити його як virtual у дочірньому класі:


public override double CalculateArea()
{
    // Реалізація за замовчуванням...
}

У похідному класі нижчого рівня такий метод уже можна перевизначити знову. Однак зробити його знову abstract — не можна, бо реалізація вже є.

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