1. Вступ
Уявіть, що ви проєктуєте сучасний автомобіль. Усередині — сотні складних деталей, чутлива електроніка, тонкі налаштування. Очевидно, що ніхто не має просто так діставатися до блока керування двигуном і починати крутити якісь гвинтики. Якщо кожен втручатиметься там, де не слід, машина може поводитися непередбачувано, а вам доведеться влаштовувати техогляд із зʼясуванням, хто і що зламав.
Так само й у програмуванні. Коли ви створюєте клас, природно хотіти захистити його внутрішню будову від стороннього втручання — щоб ніхто випадково не зіпсував важливі дані або не втрутився в роботу методів, про які зовнішній світ не має знати. У цьому й полягає суть інкапсуляції — одного з трьох ключових принципів об’єктно-орієнтованого програмування.
Інкапсуляція дає змогу приховати деталі реалізації й чітко визначити, що доступне «ззовні», а що має залишатися всередині. Це як відкрити користувачеві речове відділення, але надійно замкнути капот. Клас сам вирішує, які дані показувати зовнішньому коду, а які — залишити в таємниці. Завдяки цьому код стає надійнішим, логічнішим і простішим у супроводі.
Якщо висловитися точніше, інкапсуляція — це спосіб об’єднати дані (поля) і поведінку (методи), пов’язану з ними, в єдину структуру, обмежуючи при цьому прямий доступ до внутрішніх компонентів об’єкта.
2. Модифікатори доступу: захищаємо власну територію
У C# (та інших об’єктно-орієнтованих мовах) інкапсуляцію реалізують за допомогою модифікаторів доступу. Це своєрідні «ярлики», які визначають, які члени класу (поля, методи, властивості тощо) доступні ззовні, а які — лише зсередини.
Ми вже з ними стикалися. Нагадаємо основне й трохи розширимо список модифікаторів:
| Модифікатор | Доступний… |
|---|---|
|
Усім! Будь-якому коду в проєкті (і за його межами, якщо проєкт — бібліотека) |
|
Лише зсередини цього ж класу |
|
Із цього класу та всіх його нащадків |
|
Лише в межах поточної збірки (проєкту) |
|
Або з поточної збірки, або з класу-нащадка |
|
Лише з класу-нащадка всередині поточної збірки |
У цій лекції зосередимося на найчастіше використовуваних: public, private і коротко зачепимо protected, щоб не перевантажувати матеріалом.
Відокремлюємо внутрішнє й зовнішнє
Напишімо клас Dog:
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} каже: Гав!");
}
}
Тут усі поля та методи оголошені з модифікатором public. Це означає, що будь-хто може змінити імʼя або вік собаки:
Dog rex = new Dog("Рекс", 5);
rex.Name = "Шарик"; // Неочікувана зміна особистості!
rex.Age = -999; // Серйозні проблеми з віком
Втім, поля Name і Age — це важливі дані, які не варто виставляти назовні без контролю.
Так робити не варто
Залишаючи всі поля public, ми ризикуємо порушити логіку роботи класу: будь-яке зовнішнє втручання може зробити об’єкт некоректним. Наприклад, задати собаці від’ємний вік або встановити їй імʼя на кшталт «%$#!??».
3. Ховаємо поля: використовуємо private
Зазвичай поля класу роблять закритими (з модифікатором private). Це означає, що змінити їхні значення можна лише зсередини самого класу, а ззовні — ніяк.
public class Dog
{
private string name;
private int age;
public void Bark()
{
Console.WriteLine($"{name} каже: Гав!");
}
}
Тепер спроба звернутися до полів безпосередньо ззовні:
Dog rex = new Dog("Рекс", 5);
rex.name = "Шарик"; // Помилка компіляції
Компілятор одразу скаже: «Немає доступу!».
Навіщо так жорстко? Де ж гнучкість?
Уся «гнучкість» забезпечується завдяки спеціальним методам або властивостям (про них — детальніше в наступній лекції), які дають змогу контролювати доступ і змінювати дані лише за певними правилами.
4. Інкапсуляція на практиці: приклад із контролем
Уявімо, що ми хочемо, щоб у собаки не можна було задати від’ємний вік:
public class Dog
{
private string name;
private int age;
public Dog(string name, int age)
{
this.name = name;
if (age >= 0)
this.age = age;
else
this.age = 0; // Не даємо встановити некоректний вік
}
public void Bark()
{
Console.WriteLine($"{name} каже: Гав!");
}
// Метод для безпечної зміни віку
public void SetAge(int newAge)
{
if (newAge >= 0)
age = newAge;
// Можна додати else: повідомлення про некоректне значення
}
}
Тепер ніхто ззовні не може безпосередньо зіпсувати поля. Для зміни віку є спеціальний метод, що виконує необхідну перевірку.
5. Поля проти методів доступу (гетери/сетери)
Такий спосіб — робити поля private і надавати методи для роботи з ними — називають інкапсуляцією даних (data encapsulation). Для читання значення поля часто створюють гетери, а для запису — сетери.
public class Dog
{
private string name;
public Dog(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
public void SetName(string newName)
{
// Тут можна додати перевірку коректності імені
name = newName;
}
}
Але! З появою в C# властивостей (properties) такі методи використовують дедалі рідше — властивості роблять код значно чистішим і зручнішим (детальніше — у наступній лекції).
6. Модифікатори доступу для методів і класів
Поля — це далеко не все! Модифікатори доступу застосовують і до методів (функцій-членів класу), і навіть до самих класів.
Методи, які використовують лише всередині класу (наприклад, допоміжні функції для внутрішньої логіки), прийнято робити private.
Публічні методи — це так званий інтерфейс класу (не плутати з ключовим словом interface), тобто те, що доступне користувачу класу.
7. Типові помилки у роботі з інкапсуляцією
Помилка № 1: усе підряд оголошується як public.
Здається, що що більше відкрито, то простіше користуватися. Насправді це відкриває доступ до внутрішніх частин класу, які не повинні змінюватися ззовні. Такий код стає вразливим і непередбачуваним, особливо у великих проєктах.
Помилка № 2: зміна модифікатора доступу ламає зовнішній код.
Під час рефакторингу можна випадково змінити модифікатор доступу методу або поля, і тоді весь інший код, який із ними працював, раптово перестає компілюватися або поводиться неправильно. Особливо це критично для публічних API.
Помилка № 3: плутанина між локальними змінними та полями класу.
Іноді розробники забувають, що змінна, оголошена всередині методу, живе тільки в цьому методі. А поля класу — доступні у всіх його методах. Це призводить до неочевидних помилок, особливо якщо імена змінних збігаються.
Помилка № 4: нехтування private і protected.
Чимало розробників уникають обмеженого доступу, остерігаючись, що потім не зможуть звернутися до потрібного елемента. Але інкапсуляція якраз у цьому й полягає — ховати все зайве, а назовні виводити лише те, що справді потрібно.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ