Pola i modyfikatory dostępu – schowki na dane
Nauczysz się przechowywać dane w klasach i kontrolować, kto ma do nich dostęp. To fundament programowania obiektowego!
Co to są pola w klasie?
Pola to zmienne zdefiniowane wewnątrz klasy, które przechowują dane obiektu. Można je porównać do szuflad – każdy obiekt ma swoje własne szuflady z własnymi wartościami.
Wyobraź sobie szafki w szatni szkolnej:
- Klasa = projekt szafki (jak ma wyglądać, jakie ma przegródki)
- Obiekt = konkretna szafka ucznia (np. szafka Janka, szafka Ani)
- Pola = przegródki w szafce (na książki, zeszyty, telefon)
- Modyfikatory dostępu = kto może otwierać które przegródki
public class Uczen { // To są POLA klasy - "przegródki" każdego ucznia public string imie; // Każdy może zobaczyć private string numerTelefonu; // Tylko sam uczeń ma dostęp public int wiek; private double srednia; }
Zmienna lokalna – istnieje tylko wewnątrz metody, znika po jej zakończeniu.
Pole – istnieje tak długo, jak istnieje obiekt. Każda metoda w klasie ma do niego dostęp.
Pola instancji vs pola statyczne
Są dwa rodzaje pól: pola instancji (każdy obiekt ma swoją kopię) i pola statyczne (jedna wspólna wartość dla wszystkich obiektów).
Pola instancji (zwykłe)
Każdy obiekt ma własną kopię pola z własną wartością:
public class Samochod { // Pola instancji - każdy samochód ma SWOJE wartości public string marka; public int rocznik; public double przebieg; } // Użycie: Samochod auto1 = new Samochod(); auto1.marka = "Toyota"; auto1.przebieg = 50000; Samochod auto2 = new Samochod(); auto2.marka = "BMW"; auto2.przebieg = 80000; Console.WriteLine(auto1.marka); // "Toyota" Console.WriteLine(auto2.marka); // "BMW" – różne wartości!
przebieg = 50000
przebieg = 80000
↑ Każdy obiekt ma własne kopie pól
Pola statyczne (static)
Jedna wartość wspólna dla WSZYSTKICH obiektów danej klasy:
public class Produkt { // Pole STATYCZNE – wspólne dla wszystkich produktów public static int iloscProduktow = 0; // Pola instancji – każdy produkt ma swoje public string nazwa; public double cena; // Konstruktor public Produkt(string nazwa, double cena) { this.nazwa = nazwa; this.cena = cena; iloscProduktow++; // Zwiększamy WSPÓLNY licznik } } // Użycie: Produkt p1 = new Produkt("Laptop", 3500); Produkt p2 = new Produkt("Mysz", 50); Produkt p3 = new Produkt("Klawiatura", 120); // Dostęp przez NAZWĘ KLASY (nie przez obiekt!) Console.WriteLine(Produkt.iloscProduktow); // 3
↑ Wszystkie obiekty współdzielą jedno pole statyczne
Do pola statycznego odwołujemy się przez nazwę klasy, nie przez obiekt:
✅ Produkt.iloscProduktow
❌ p1.iloscProduktow – zadziała, ale to zły styl!
| Cecha | Pole instancji | Pole statyczne (static) |
|---|---|---|
| Liczba kopii | Każdy obiekt ma swoją | Jedna dla całej klasy |
| Dostęp | obiekt.pole | Klasa.pole |
| Przykład | imię ucznia, cena produktu | licznik obiektów, stałe |
| Pamięć | W każdym obiekcie | Raz, niezależnie od obiektów |
Modyfikatory dostępu – „poziomy bezpieczeństwa”
Modyfikatory dostępu określają, kto może odczytywać i zmieniać dane pole. To jak poziomy bezpieczeństwa w budynku.
Od najbardziej otwartego do najbardziej restrykcyjnego
public – dostęp dla wszystkich
public class Osoba { public string imie; // Każdy może odczytać i zmienić public int wiek; } // Gdziekolwiek w kodzie: Osoba jan = new Osoba(); jan.imie = "Jan"; // ✅ Działa jan.wiek = -50; // ✅ Działa... ale to błąd logiczny!
Każdy może wpisać dowolną wartość, nawet błędną (np. wiek ujemny). Brak kontroli nad danymi!
private – dostęp tylko z tej klasy
public class Osoba { private string imie; // Tylko metody TEJ klasy mają dostęp private int wiek; // Metoda publiczna do ODCZYTU public string GetImie() { return imie; } // Metoda publiczna do ZAPISU z walidacją public void SetWiek(int nowyWiek) { if (nowyWiek >= 0 && nowyWiek <= 150) { wiek = nowyWiek; } else { Console.WriteLine("Nieprawidłowy wiek!"); } } } // Użycie: Osoba jan = new Osoba(); // jan.wiek = -50; // ❌ BŁĄD KOMPILACJI! Pole jest private jan.SetWiek(-50); // ✅ Kompiluje się, ale wyświetli "Nieprawidłowy wiek!" jan.SetWiek(25); // ✅ Działa poprawnie
Jeśli nie napiszesz żadnego modyfikatora, pole jest domyślnie private. Ale zawsze pisz modyfikator jawnie – kod jest wtedy czytelniejszy.
protected – ta klasa + klasy dziedziczące
public class Osoba { protected string pesel; // Ta klasa + klasy pochodne } public class Pracownik : Osoba // Pracownik dziedziczy po Osoba { public void WyswietlPesel() { Console.WriteLine(pesel); // ✅ Działa – protected } } // Ale z zewnątrz: Osoba jan = new Osoba(); // jan.pesel = "12345"; // ❌ BŁĄD! Nie jesteśmy w klasie Osoba ani jej dziecku
internal – dostęp z tego projektu (assembly)
public class Konfiguracja { internal string connectionString; // Dostęp z całego projektu }
W Visual Studio projekt to jeden plik .csproj. Wszystkie klasy w tym samym projekcie mają dostęp do pól internal. Klasy z innego projektu (np. biblioteki zewnętrznej) – nie mają.
W prostych programach (jeden projekt) internal działa podobnie jak public.
Tabela podsumowująca
| Modyfikator | Ta klasa | Klasa pochodna | Ten projekt | Inny projekt |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
internal | ✅ | ✅ | ✅ | ❌ |
protected | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
Enkapsulacja – dlaczego ukrywać dane?
Enkapsulacja (ang. encapsulation) to jedna z fundamentalnych zasad programowania obiektowego: ukrywamy dane (private), a udostępniamy kontrolowany interfejs (public).
Bankomat to doskonały przykład enkapsulacji:
- Ukryte (private): pieniądze wewnątrz, mechanizm wydawania, połączenie z bankiem
- Dostępne (public): ekran, klawiatura, szczelina na kartę
Nie możesz sięgnąć ręką po pieniądze – musisz użyć interfejsu (wpisać PIN, wybrać kwotę).
Przykład bez enkapsulacji (źle)
public class KontoBankowe { public double saldo; // ❌ Każdy może zmienić! } // Problem: KontoBankowe konto = new KontoBankowe(); konto.saldo = 1000; konto.saldo = -99999; // ❌ Ujemne saldo? Brak kontroli! konto.saldo = 999999999; // ❌ Ktoś sobie "dopłacił"
Przykład z enkapsulacją (dobrze)
public class KontoBankowe { private double saldo; // ✅ Ukryte – nikt nie zmieni bezpośrednio // Odczyt salda (każdy może zobaczyć) public double GetSaldo() { return saldo; } // Wpłata – kontrolowana operacja public void Wplata(double kwota) { if (kwota > 0) { saldo += kwota; Console.WriteLine($"Wpłacono {kwota} zł. Saldo: {saldo} zł"); } } // Wypłata – z walidacją public bool Wyplata(double kwota) { if (kwota > 0 && kwota <= saldo) { saldo -= kwota; Console.WriteLine($"Wypłacono {kwota} zł. Saldo: {saldo} zł"); return true; } Console.WriteLine("Brak środków lub nieprawidłowa kwota!"); return false; } } // Użycie: KontoBankowe konto = new KontoBankowe(); konto.Wplata(1000); // Wpłacono 1000 zł. Saldo: 1000 zł konto.Wyplata(200); // Wypłacono 200 zł. Saldo: 800 zł konto.Wyplata(9999); // Brak środków lub nieprawidłowa kwota! // konto.saldo = 99999; // ❌ BŁĄD KOMPILACJI – pole jest private
❌ Bez enkapsulacji
public double saldo;
- Każdy może zmienić na dowolną wartość
- Brak historii operacji
- Brak walidacji
✅ Z enkapsulacją
private double saldo;
- Zmiana tylko przez Wplata/Wyplata
- Możemy logować operacje
- Walidacja kwot
Pola rób private, dostęp dawaj przez metody.
Dzięki temu masz kontrolę nad tym, co i jak jest zmieniane.
Pola readonly – ustaw raz, nie zmieniaj
Pole readonly można ustawić tylko raz – przy deklaracji lub w konstruktorze. Potem jest już tylko do odczytu.
public class Osoba { // readonly – ustawiamy w konstruktorze, potem nie można zmienić public readonly string pesel; public readonly DateTime dataUrodzenia; // Zwykłe pole – można zmieniać public string imie; public Osoba(string pesel, DateTime dataUrodzenia, string imie) { this.pesel = pesel; // ✅ OK – konstruktor this.dataUrodzenia = dataUrodzenia; // ✅ OK – konstruktor this.imie = imie; } public void ZmienImie(string noweImie) { imie = noweImie; // ✅ OK – zwykłe pole // pesel = "nowy"; // ❌ BŁĄD! Pole readonly } } // Użycie: Osoba jan = new Osoba("12345678901", new DateTime(2000, 1, 15), "Jan"); jan.imie = "Janek"; // ✅ OK // jan.pesel = "99"; // ❌ BŁĄD KOMPILACJI!
Dla danych, które nie powinny się zmieniać po utworzeniu obiektu:
- PESEL, numer dowodu – nie zmieniają się
- Data urodzenia – nie zmieni się
- ID obiektu w bazie danych
- Data utworzenia obiektu
Pola const – stałe wartości
Pole const to stała – wartość znana już podczas kompilacji programu. Nigdy się nie zmienia.
public class Matematyka { // Stałe matematyczne – nigdy się nie zmienią public const double PI = 3.14159265359; public const double E = 2.71828182846; } public class Walidacja { // Stałe ograniczenia public const int MIN_WIEK = 0; public const int MAX_WIEK = 150; public const int MIN_HASLO = 8; public const string DOMYSLNA_WALUTA = "PLN"; } // Użycie – przez NAZWĘ KLASY (const jest automatycznie static) Console.WriteLine(Matematyka.PI); // 3.14159265359 Console.WriteLine(Walidacja.MAX_WIEK); // 150 if (wiek > Walidacja.MAX_WIEK) { Console.WriteLine("Nieprawidłowy wiek!"); }
Wartość const musi być znana podczas kompilacji. Nie możesz użyć:
const DateTime teraz = DateTime.Now;❌const string id = Guid.NewGuid().ToString();❌
Do takich przypadków użyj static readonly.
Stałe const często zapisuje się WIELKIMI_LITERAMI z podkreślnikami:
MAX_WIEK, MIN_HASLO, DOMYSLNA_WALUTA
Porównanie: const vs readonly vs static readonly
| Cecha | const | readonly | static readonly |
|---|---|---|---|
| Kiedy ustawiamy? | Przy kompilacji | W konstruktorze obiektu | W konstruktorze statycznym lub przy deklaracji |
| Można zmienić? | ❌ Nigdy | ❌ Po konstruktorze – nie | ❌ Po konstruktorze – nie |
| Czy static? | ✅ Automatycznie | ❌ Nie (każdy obiekt ma swoją) | ✅ Tak |
| Typy danych | Tylko proste (int, string, double) | Dowolne | Dowolne |
| Przykład użycia | PI, MAX_WIEK | PESEL, dataUrodzenia | DataUruchomienia, Config |
public class Przyklad { // const – wartość znana przy kompilacji public const int MAX = 100; // readonly – każdy obiekt ma swoją, ustawianą w konstruktorze public readonly Guid id; // static readonly – jedna dla całej klasy, może używać DateTime.Now public static readonly DateTime DataUruchomienia = DateTime.Now; public Przyklad() { id = Guid.NewGuid(); // Każdy obiekt dostaje unikalny ID } }
Częste błędy
❌ Błąd 1: Wszystko public
❌ Źle
public class Osoba { public string imie; public int wiek; public string haslo; }
Każdy może zmienić hasło!
✅ Dobrze
public class Osoba { public string imie; private int wiek; private string haslo; }
Wrażliwe dane ukryte.
❌ Błąd 2: const bez wartości
❌ Źle
public const string nazwa; // BŁĄD! const musi mieć wartość
✅ Dobrze
public const string NAZWA = "App";
❌ Błąd 3: Zmiana readonly poza konstruktorem
❌ Źle
public readonly string id; public void Reset() { id = "nowy"; // BŁĄD! }
✅ Dobrze
public readonly string id; public Klasa() { id = "abc"; // OK w konstruktorze }
❌ Błąd 4: Brak typu w static
❌ Źle
public static wiek = 25; // BŁĄD! Brak typu
✅ Dobrze
public static int wiek = 25;
Podsumowanie
Modyfikatory dostępu
| Modyfikator | Kto ma dostęp? | Kiedy używać? |
|---|---|---|
public | Wszyscy | Metody interfejsu, stałe |
private | Tylko ta klasa | Dane wewnętrzne – domyślny wybór! |
protected | Ta klasa + dzieci | Gdy planujesz dziedziczenie |
internal | Ten projekt | Klasy pomocnicze w bibliotece |
Modyfikatory wartości
| Modyfikator | Co oznacza? | Przykład |
|---|---|---|
static | Wspólne dla wszystkich obiektów | Licznik obiektów |
readonly | Można ustawić tylko raz (w konstruktorze) | ID, data utworzenia |
const | Stała znana przy kompilacji | PI, MAX_WIEK |
- Pola = szuflady obiektu – przechowują dane
- private domyślnie – public tylko gdy naprawdę trzeba
- Enkapsulacja = ukrywaj dane, udostępniaj metody
- static = jedno dla wszystkich obiektów
- const = nigdy się nie zmieni
- readonly = można ustawić tylko raz
Zadania praktyczne
📝 Zadanie 1: Klasa Pracownik
Utwórz klasę Pracownik z polami:
imie– publicznenazwisko– publicznepensja– prywatne (z metodami Get/Set i walidacją: min. 0)dataZatrudnienia– readonly (ustawiane w konstruktorze)MINIMALNA_PENSJA– const = 3490 (płaca minimalna)
💡 Podpowiedź: W metodzie SetPensja() sprawdź, czy nowa pensja >= MINIMALNA_PENSJA
📝 Zadanie 2: Licznik produktów
Utwórz klasę Produkt z:
- Polem statycznym
iloscProduktow– liczy ile produktów utworzono - Polem statycznym
nastepneId– generuje kolejne ID - Polem readonly
id– unikalne dla każdego produktu - Polami
nazwaicena
Każdy nowy produkt powinien automatycznie dostać kolejne ID.
💡 Podpowiedź: W konstruktorze: id = nastepneId++;
📝 Zadanie 3: Znajdź i popraw błędy
W tym kodzie jest 5 błędów. Znajdź je i popraw:
public class Test { public const string nazwa; // Błąd 1 public static liczba = 10; // Błąd 2 private readonly int id; public Test() { id = 1; } public void Zmien() { id = 99; // Błąd 3 nazwa = "nowa"; // Błąd 4 } } class Program { static void Main() { Test t = new Test(); Console.WriteLine(t.id); // Błąd 5 } }
💡 Podpowiedź: const musi mieć wartość, static potrzebuje typu, readonly nie można zmieniać poza konstruktorem, private nie jest dostępne z zewnątrz
⭐ Zadanie 4: Konto bankowe z enkapsulacją
Utwórz klasę KontoBankowe z pełną enkapsulacją:
numerKonta– readonly (generowany w konstruktorze)saldo– private (dostęp tylko przez metody)wlasciciel– private z Get/Set- Metody:
Wplata(kwota),Wyplata(kwota),GetSaldo() - Walidacja: nie można wypłacić więcej niż saldo, kwoty muszą być > 0
⭐⭐ Zadanie 5: System konfiguracji
Utwórz klasę Konfiguracja z:
- Stałymi (const):
WERSJA_APLIKACJI,MAX_UZYTKOWNICY - Polami static readonly:
DataUruchomienia,NazwaKomputera - Polami static:
TrybDebug(bool),JezykAplikacji(string) - Metodami statycznymi do zmiany ustawień z walidacją
💡 Podpowiedź: Environment.MachineName zwraca nazwę komputera