CodeGym /Kursy /C# SELF /Enkapsulacja i modyfikatory dostępu

Enkapsulacja i modyfikatory dostępu

C# SELF
Poziom 17 , Lekcja 0
Dostępny

1. Wprowadzenie

Wyobraź sobie, że projektujesz nowoczesny samochód. W środku — setki skomplikowanych części, czuła elektronika, precyzyjne ustawienia. Oczywiste jest, że nikt nie powinien tak po prostu dostać się do sterownika silnika i zacząć kręcić jakimiś śrubkami. Jeśli każdy będzie grzebał tam, gdzie nie powinien, auto może zacząć zachowywać się nieprzewidywalnie, a Ty będziesz musiał robić przegląd i ustalać, kto i co popsuł.

Tak samo jest w programowaniu. Gdy tworzysz klasę, chcesz chronić jej wnętrze przed obcymi rękami — żeby nikt przypadkiem nie popsuł ważnych danych albo nie namieszał w metodach, o których świat zewnętrzny nie powinien wiedzieć. Na tym właśnie polega enkapsulacja — jeden z trzech kluczowych filarów programowania obiektowego.

Enkapsulacja pozwala ukryć szczegóły implementacji i jasno określić, co jest dostępne „z zewnątrz”, a co powinno zostać w środku. To tak, jakbyś otworzył użytkownikowi schowek na rękawiczki, ale maskę zamknął na klucz. Klasa sama decyduje, które dane pokazać zewnętrznemu kodowi, a które — zostawić w tajemnicy. Dzięki temu kod jest solidniejszy, bardziej logiczny i łatwiejszy w utrzymaniu.

Mówiąc bardziej formalnie, enkapsulacja — to sposób na połączenie danych (pól) i zachowania (metod), które są z nimi związane, w jedną strukturę, ograniczając przy tym bezpośredni dostęp do wewnętrznych komponentów obiektu.

2. Modyfikatory dostępu: pilnujemy swojego terenu

W C# (i innych językach OOP) enkapsulacja realizowana jest za pomocą modyfikatorów dostępu. To takie „etykietki”, które określają, które człony klasy (pola, metody, właściwości itd.) są dostępne z zewnątrz, a które — tylko od środka.

Już się z nimi spotkaliśmy, ale przypomnijmy sobie, co już wiemy i trochę rozszerzmy listę modyfikatorów:

Modyfikator Dostępny dla...
public
Dla wszystkich! Każdy kod w projekcie (i poza nim, jeśli projekt to biblioteka)
private
Tylko od środka tej samej klasy
protected
Z tej klasy i wszystkich jej potomków
internal
Tylko w ramach bieżącego assembly (projektu)
protected internal
Albo z bieżącego assembly, albo z klasy potomnej
private protected
Tylko z klasy potomnej w bieżącym assembly

W tym wykładzie skupimy się na tych najczęściej używanych: public, private i krótko wspomnimy o protected, żeby nie przeładować głowy.

Oddzielamy wnętrze od zewnętrza

Napisaćmy klasę Dog:


public class Dog
{
    public string Name;
    public int Age;

    public void Bark()
    {
        Console.WriteLine($"{Name} mówi: Hau!");
    }
}

Tutaj wszystkie pola i metody są zadeklarowane z modyfikatorem public. To znaczy, że każdy może zmienić imię lub wiek psa:

Dog rex = new Dog("Reks", 5);
rex.Name = "Szarik"; // Niespodziewana zmiana tożsamości!
rex.Age = -999;     // Poważne problemy z wiekiem

A przecież pola Name i Age — to najważniejsze dane, których nie chcemy oddawać każdemu.

Tego lepiej nie robić

Zostawiając wszystkie pola public, ryzykujemy naruszenie logiki działania klasy: każda zewnętrzna ingerencja może sprawić, że obiekt stanie się nieważny. Na przykład, można ustawić psu ujemny wiek albo nadać mu imię typu "%$#!??".

3. Ukrywamy pola: używamy private

Zazwyczaj pola klasy robi się prywatne (z modyfikatorem private). To znaczy, że ich wartości można zmieniać tylko od środka klasy, a z zewnątrz — wcale.


public class Dog
{
    private string name;
    private int age;

    public void Bark()
    {
        Console.WriteLine($"{name} mówi: Hau!");
    }
}

Teraz próba dostania się do pól bezpośrednio z zewnątrz:

Dog rex = new Dog("Reks", 5);
rex.name = "Szarik";  // Błąd kompilacji

Kompilator od razu powie: „Brak dostępu!”.

Po co tak ostro? Gdzie elastyczność?

Cała „elastyczność” zapewniana jest przez specjalne metody lub właściwości (o nich więcej w następnym wykładzie), które pozwalają kontrolować dostęp i zmieniać dane tylko według określonych zasad.

4. Enkapsulacja w praktyce: przykład z kontrolą

Załóżmy, że chcemy, żeby nie dało się psu ustawić ujemnego wieku:


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; // Nie pozwalamy ustawić "dziwnego" wieku
    }

    public void Bark()
    {
        Console.WriteLine($"{name} mówi: Hau!");
    }

    // Metoda do bezpiecznej zmiany wieku
    public void SetAge(int newAge)
    {
        if (newAge >= 0)
            age = newAge;
        // Można dodać else: komunikat o niepoprawnej wartości
    }
}

Teraz nikt z zewnątrz nie popsuje pól bezpośrednio. Do zmiany wieku jest specjalna metoda, która robi odpowiednią weryfikację.

5. Pola kontra metody dostępu (gettery/settery)

Taki sposób — robienie pól private i udostępnianie metod do pracy z nimi — nazywa się enkapsulacją danych (data encapsulation). Do odczytu wartości pola często tworzy się metody-gettery, a do zapisu — metody-settery.


public class Dog
{
    private string name;

    public Dog(string name)
    {
        this.name = name;
    }

    public string GetName()
    {
        return name;
    }

    public void SetName(string newName)
    {
        // Tu można dodać sprawdzenie poprawności imienia
        name = newName;
    }
}

Ale! Odkąd w C# pojawiły się właściwości (properties), takie metody stosuje się coraz rzadziej — właściwości sprawiają, że kod jest dużo czytelniejszy i wygodniejszy (więcej o nich — w następnym wykładzie).

6. Modyfikatory dostępu dla metod i klas

Pola to nie wszystko! Modyfikatory dostępu stosuje się też do metod (funkcji członkowskich klasy), a nawet do samych klas.

Metody używane tylko wewnątrz klasy (np. pomocnicze funkcje do logiki wewnętrznej), standardowo robi się private.

Metody publiczne — to tzw. interfejs klasy (nie mylić ze słowem kluczowym interface), czyli to, z czego użytkownik klasy może korzystać.

7. Typowe błędy przy pracy z enkapsulacją

Błąd nr 1: wszystko jest zadeklarowane jako public.
Wydaje się, że im więcej dostępne, tym łatwiej korzystać. W praktyce — to otwiera dostęp do wnętrza klasy, które nie powinno być zmieniane z zewnątrz. Taki kod staje się podatny na błędy i nieprzewidywalny, szczególnie w dużych projektach.

Błąd nr 2: zmiana modyfikatora dostępu psuje zewnętrzny kod.
Przy refaktoryzacji można przypadkiem zmienić modyfikator dostępu metody lub pola i wtedy cały inny kod, który z nich korzystał, nagle przestaje się kompilować albo działać poprawnie. To szczególnie ważne w publicznych API.

Błąd nr 3: mylenie zmiennych lokalnych z polami klasy.
Czasem programiści zapominają, że zmienna zadeklarowana w metodzie żyje tylko w tej metodzie. A pola klasy — są dostępne we wszystkich jej metodach. To prowadzi do nieoczywistych błędów, zwłaszcza jeśli nazwy się pokrywają.

Błąd nr 4: lekceważenie private i protected.
Wielu boi się używać ograniczonego dostępu, bo potem nie będą mogli dostać się do potrzebnego elementu. Ale właśnie o to chodzi w enkapsulacji — ukrywać wszystko, co zbędne, a na zewnątrz wystawiać tylko to, co naprawdę potrzebne.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION