Wstęp do OOP – klasy i obiekty od podstaw
Poznasz różnicę między programowaniem strukturalnym a obiektowym, nauczysz się tworzyć klasy i obiekty, oraz zrozumiesz dlaczego OOP ułatwia życie programisty!
Jak pisaliśmy dotychczas? (programowanie strukturalne)
Do tej pory pisałeś programy używając zmiennych i funkcji. To podejście nazywamy programowaniem strukturalnym – dane i logika są rozdzielone.
// Programowanie strukturalne – zmienne i funkcje osobno class Program { static void Main() { // DANE – zmienne opisujące samochód string marka = "Toyota"; string model = "Corolla"; int rok = 2020; double przebieg = 45000; // LOGIKA – funkcja operująca na danych WypiszSamochod(marka, model, rok, przebieg); } static void WypiszSamochod(string marka, string model, int rok, double przebieg) { Console.WriteLine($"Samochód: {marka} {model}"); Console.WriteLine($"Rok: {rok}, Przebieg: {przebieg} km"); } }
To podejście działa dla małych programów. Ale co jeśli mamy więcej samochodów?
↑ Dane i funkcje są ODDZIELNE – funkcja przyjmuje dane jako parametry
Problem – kod wymyka się spod kontroli
Wyobraź sobie, że piszesz program dla komisu samochodowego z setkami aut. Zobaczmy, jak wygląda programowanie strukturalne przy większej skali:
// 😰 Trzy samochody = 12 zmiennych! // Samochód 1 string samochod1Marka = "Toyota"; string samochod1Model = "Corolla"; int samochod1Rok = 2020; double samochod1Przebieg = 45000; // Samochód 2 string samochod2Marka = "Ford"; string samochod2Model = "Focus"; int samochod2Rok = 2019; double samochod2Przebieg = 62000; // Samochód 3 string samochod3Marka = "BMW"; string samochod3Model = "320i"; int samochod3Rok = 2021; double samochod3Przebieg = 28000; // A co jeśli mamy 100 samochodów? 400 zmiennych! // A co jeśli chcemy dodać nowe pole "kolor"? Zmiana w 100 miejscach! WypiszSamochod(samochod1Marka, samochod1Model, samochod1Rok, samochod1Przebieg); WypiszSamochod(samochod2Marka, samochod2Model, samochod2Rok, samochod2Przebieg); WypiszSamochod(samochod3Marka, samochod3Model, samochod3Rok, samochod3Przebieg);
- Eksplozja zmiennych – każdy samochód = 4+ zmienne, 100 aut = 400+ zmiennych!
- Brak powiązania – skąd wiadomo, że
samochod1Markaisamochod1Roknależą do tego samego auta? - Łatwo o pomyłkę – możesz przypadkowo użyć
samochod2Modelzsamochod1Rok - Trudne zmiany – dodanie pola „kolor” wymaga zmiany każdej funkcji i każdego wywołania
- Brak struktury – kod jest płaski, trudny do zrozumienia
Wyobraź sobie, że prowadzisz komis samochodowy używając luźnych karteczek:
- Na jednej karteczce piszesz „Toyota”
- Na drugiej „Corolla”
- Na trzeciej „2020”
- Na czwartej „45000 km”
Teraz masz 400 karteczek w szufladzie… Powodzenia w znalezieniu wszystkich danych o jednym aucie! 😅
Potrzebujesz teczek – każda teczka = jeden samochód, wszystkie dane razem.
Rozwiązanie – programowanie obiektowe (OOP)
Programowanie obiektowe (Object-Oriented Programming, OOP) grupuje dane i funkcje w jedną całość zwaną obiektem. Zamiast luźnych zmiennych, mamy uporządkowane „teczki” z wszystkimi informacjami.
W firmie każdy pracownik ma teczkę osobową:
- Wszystkie dane pracownika są razem (imię, nazwisko, stanowisko, pensja)
- Każdy pracownik ma swoją teczkę (obiekt)
- Wszystkie teczki mają ten sam układ (klasa)
W OOP: klasa = wzór teczki, obiekt = konkretna teczka pracownika.
marka, model, rok…
marka, model, rok…
marka, model, rok…
↑ Jedna klasa, wiele obiektów – każdy z własnymi danymi
Zalety programowania obiektowego
| Zaleta | Co to znaczy? |
|---|---|
| Organizacja | Dane i funkcje razem w jednym miejscu |
| Reużywalność | Raz napisana klasa → wiele obiektów |
| Łatwe zmiany | Zmiana w klasie → automatycznie we wszystkich obiektach |
| Czytelność | Kod odzwierciedla rzeczywistość (samochód ma markę) |
| Bezpieczeństwo | Można ukryć wewnętrzne szczegóły (enkapsulacja) |
Czym jest klasa?
Klasa to szablon (wzorzec, przepis), który opisuje jakie dane i zachowania będą miały obiekty. Klasa sama w sobie nie przechowuje danych – jest tylko definicją.
Pomyśl o klasie jak o foremce do ciastek:
- Foremka (klasa) – definiuje kształt ciastka, ale sama nie jest ciastkiem
- Ciastka (obiekty) – konkretne ciastka wycięte foremką, każde może mieć inną polewę
Jedna foremka → wiele ciastek. Jedna klasa → wiele obiektów.
Inna analogia:
- Projekt domu (klasa) – rysunki techniczne, opis materiałów, układ pokoi
- Zbudowane domy (obiekty) – konkretne budynki według projektu, każdy pod innym adresem, z innym kolorem ścian
Architekt rysuje projekt RAZ, a potem można zbudować wiele domów!
Klasa definiuje:
Co obiekt wie?
marka, model, rok
Co obiekt robi?
Jedz(), Hamuj()
Jak powstaje obiekt?
new Samochod(…)
Czym jest obiekt?
Obiekt to konkretna instancja klasy – rzeczywisty „byt” w pamięci komputera z własnymi danymi. Obiekt jest tworzony na podstawie klasy.
- Instancja = obiekt (te słowa oznaczają to samo)
- Instancjonowanie = tworzenie obiektu
- new = słowo kluczowe do tworzenia obiektów
Klasa vs Obiekt – porównanie
Klasa (szablon)
- Jeden raz w kodzie
- Definiuje JAKIE pola
- Nie ma konkretnych wartości
- Nie zajmuje pamięci na dane
- Jak przepis w książce kucharskiej
Obiekt (instancja)
- Może być wiele obiektów
- MA konkretne wartości pól
- Każdy obiekt ma własne dane
- Zajmuje pamięć
- Jak ugotowane danie
// KLASA – definicja (szablon) class Samochod { public string Marka; // Jakie pole? – nie ma wartości! public string Model; } // OBIEKTY – konkretne instancje z danymi Samochod auto1 = new Samochod(); // Obiekt 1 auto1.Marka = "Toyota"; // Konkretna wartość! auto1.Model = "Corolla"; Samochod auto2 = new Samochod(); // Obiekt 2 – osobny! auto2.Marka = "Ford"; // Inne wartości! auto2.Model = "Focus"; // Każdy obiekt ma WŁASNE dane: Console.WriteLine(auto1.Marka); // Toyota Console.WriteLine(auto2.Marka); // Ford
Model: „Corolla”
Model: „Focus”
↑ Każdy obiekt zajmuje osobne miejsce w pamięci
Anatomia klasy w C#
Zobaczmy jak wygląda definicja klasy w C# i co oznaczają poszczególne elementy.
// ↓ modyfikator dostępu ↓ nazwa klasy public class Samochod { // ← początek definicji klasy // ═══════════════════════════════════════════════════ // POLA – dane przechowywane przez obiekt // ═══════════════════════════════════════════════════ public string Marka; public string Model; public int Rok; public double Przebieg; // ═══════════════════════════════════════════════════ // KONSTRUKTOR – specjalna metoda tworząca obiekt // ═══════════════════════════════════════════════════ public Samochod(string marka, string model) { Marka = marka; Model = model; Rok = 2024; // Wartość domyślna Przebieg = 0; // Nowy samochód } // ═══════════════════════════════════════════════════ // METODY – zachowania, akcje obiektu // ═══════════════════════════════════════════════════ public void WyswietlInfo() { Console.WriteLine($"🚗 {Marka} {Model} ({Rok})"); Console.WriteLine($" Przebieg: {Przebieg} km"); } public void Jedz(double kilometry) { Przebieg += kilometry; Console.WriteLine($"Przejechano {kilometry} km"); } } // ← koniec definicji klasy
| Element | Co to jest? | Przykład |
|---|---|---|
public class | Deklaracja klasy publicznej | public class Samochod |
| Pola | Zmienne przechowujące dane | public string Marka; |
| Konstruktor | Metoda tworząca obiekt (nazwa = nazwa klasy) | public Samochod(...) |
| Metody | Funkcje definiujące zachowania | public void Jedz(...) |
Słowo public oznacza, że element jest dostępny z zewnątrz klasy. Na razie używaj public wszędzie – o modyfikatorach dostępu (private, protected) dowiesz się później.
Tworzenie obiektów – słowo kluczowe new
Aby utworzyć obiekt (instancję klasy), używamy słowa kluczowego new. To „buduje” obiekt w pamięci komputera.
// typ zmiennej nazwa new konstruktor // ↓ ↓ ↓ ↓ Samochod mojAuto = new Samochod("Toyota", "Corolla"); // Co się dzieje? // 1. new Samochod(...) – tworzy nowy obiekt w pamięci // 2. Wywołuje konstruktor z parametrami "Toyota", "Corolla" // 3. Zwraca "adres" do obiektu // 4. Zmienna mojAuto przechowuje ten adres (referencję)
// Tworzenie obiektu Samochod mojAuto = new Samochod("Toyota", "Corolla"); // Dostęp do PÓL – używamy kropki Console.WriteLine(mojAuto.Marka); // Toyota Console.WriteLine(mojAuto.Model); // Corolla Console.WriteLine(mojAuto.Przebieg); // 0 // Wywołanie METOD – też używamy kropki mojAuto.WyswietlInfo(); // 🚗 Toyota Corolla (2024) mojAuto.Jedz(150); // Przejechano 150 km mojAuto.WyswietlInfo(); // Przebieg: 150 km // Zmiana wartości pola mojAuto.Rok = 2023; // Zmiana roku
Kropka to „wejście do środka” obiektu:
obiekt.pole– dostęp do pola (zmiennej)obiekt.Metoda()– wywołanie metody (funkcji)
Czytaj to jako: „mojAuto kropka Marka” = „Marka tego konkretnego auta”
Konstruktor – jak powstają obiekty?
Konstruktor to specjalna metoda wywoływana automatycznie podczas tworzenia obiektu (new). Służy do inicjalizacji pól obiektu.
Cechy konstruktora
| Cecha | Opis |
|---|---|
| Nazwa | Taka sama jak nazwa klasy |
| Brak typu zwracanego | Nie piszemy void ani innego typu! |
| Wywoływany automatycznie | Przy użyciu new |
| Może mieć parametry | Do przekazania początkowych wartości |
public class Samochod { public string Marka; public string Model; public int Rok; // KONSTRUKTOR BEZPARAMETROWY – domyślny public Samochod() { Marka = "Nieznana"; Model = "Nieznany"; Rok = 2024; } // KONSTRUKTOR Z PARAMETRAMI public Samochod(string marka, string model) { Marka = marka; Model = model; Rok = 2024; } // KONSTRUKTOR Z WSZYSTKIMI PARAMETRAMI public Samochod(string marka, string model, int rok) { Marka = marka; Model = model; Rok = rok; } } // Użycie różnych konstruktorów: Samochod auto1 = new Samochod(); // Bezparametrowy Samochod auto2 = new Samochod("Toyota", "Corolla"); // 2 parametry Samochod auto3 = new Samochod("BMW", "320i", 2021); // 3 parametry
Jeśli nie napiszesz żadnego konstruktora, C# automatycznie doda pusty konstruktor bezparametrowy. Ale jeśli napiszesz jakikolwiek konstruktor, ten domyślny znika!
Model = „Corolla”
Metody – zachowania obiektów
Metody to funkcje zdefiniowane wewnątrz klasy. Opisują co obiekt potrafi robić. Metody mają dostęp do pól obiektu.
public class KontoBankowe { public string Wlasciciel; public decimal Saldo; public KontoBankowe(string wlasciciel) { Wlasciciel = wlasciciel; Saldo = 0; } // Metoda bez wartości zwracanej (void) public void Wplac(decimal kwota) { Saldo += kwota; // Metoda ma dostęp do pola Saldo! Console.WriteLine($"Wpłacono {kwota:C}. Saldo: {Saldo:C}"); } // Metoda zwracająca wartość (bool) public bool Wyplac(decimal kwota) { if (kwota > Saldo) { Console.WriteLine("Brak wystarczających środków!"); return false; } Saldo -= kwota; Console.WriteLine($"Wypłacono {kwota:C}. Saldo: {Saldo:C}"); return true; } // Metoda zwracająca dane (string) public string PobierzPodsumowanie() { return $"Konto: {Wlasciciel}, Saldo: {Saldo:C}"; } } // Użycie: KontoBankowe konto = new KontoBankowe("Jan Kowalski"); konto.Wplac(1000); // Wpłacono 1 000,00 zł konto.Wyplac(200); // Wypłacono 200,00 zł Console.WriteLine(konto.PobierzPodsumowanie());
Wewnątrz metody możesz używać pól klasy (np. Saldo) bez żadnych parametrów – metoda „wie”, do którego obiektu należy.
Ewolucja kodu – przed i po OOP
Zobaczmy pełne porównanie tego samego programu napisanego strukturalnie i obiektowo.
❌ PRZED – programowanie strukturalne
// 😰 Komis samochodowy – wersja strukturalna // Samochód 1 string auto1Marka = "Toyota"; string auto1Model = "Corolla"; int auto1Rok = 2020; double auto1Przebieg = 45000; decimal auto1Cena = 75000; // Samochód 2 string auto2Marka = "Ford"; string auto2Model = "Focus"; int auto2Rok = 2019; double auto2Przebieg = 62000; decimal auto2Cena = 55000; // Samochód 3 string auto3Marka = "BMW"; string auto3Model = "320i"; int auto3Rok = 2021; double auto3Przebieg = 28000; decimal auto3Cena = 120000; // Funkcje – muszą przyjmować WSZYSTKIE dane jako parametry static void WypiszSamochod(string marka, string model, int rok, double przebieg, decimal cena) { Console.WriteLine($"{marka} {model} ({rok})"); Console.WriteLine($"Przebieg: {przebieg} km, Cena: {cena:C}"); } // Wywołanie – łatwo o pomyłkę! WypiszSamochod(auto1Marka, auto1Model, auto1Rok, auto1Przebieg, auto1Cena); WypiszSamochod(auto2Marka, auto2Model, auto2Rok, auto2Przebieg, auto2Cena); // 15 zmiennych dla 3 samochodów! A co jeśli będzie 100 aut?
✅ PO – programowanie obiektowe
// 😊 Komis samochodowy – wersja obiektowa // Klasa – definiujemy RAZ public class Samochod { public string Marka; public string Model; public int Rok; public double Przebieg; public decimal Cena; public Samochod(string marka, string model, int rok, double przebieg, decimal cena) { Marka = marka; Model = model; Rok = rok; Przebieg = przebieg; Cena = cena; } // Metoda WEWNĄTRZ klasy – ma dostęp do pól public void WypiszInfo() { Console.WriteLine($"🚗 {Marka} {Model} ({Rok})"); Console.WriteLine($" Przebieg: {Przebieg} km, Cena: {Cena:C}"); } } // Tworzenie obiektów – czytelne i zwięzłe! Samochod auto1 = new Samochod("Toyota", "Corolla", 2020, 45000, 75000); Samochod auto2 = new Samochod("Ford", "Focus", 2019, 62000, 55000); Samochod auto3 = new Samochod("BMW", "320i", 2021, 28000, 120000); // Wywołanie – proste i bezpieczne! auto1.WypiszInfo(); auto2.WypiszInfo(); auto3.WypiszInfo(); // 3 zmienne dla 3 samochodów! Każdy obiekt przechowuje swoje dane
❌ Strukturalne
- 15 zmiennych dla 3 aut
- 100 aut = 500 zmiennych!
- Funkcja z 5 parametrami
- Łatwo pomylić kolejność
- Dodanie pola = zmiana wszędzie
✅ Obiektowe
- 3 zmienne dla 3 aut
- 100 aut = 100 zmiennych
- Metoda bez parametrów
- Nie można pomylić
- Dodanie pola = zmiana w klasie
Wiele obiektów jednej klasy
Główna siła OOP: jedna klasa → nieograniczona liczba obiektów. Możesz też przechowywać obiekty w tablicach i listach!
// Lista samochodów w komisie List<Samochod> komis = new List<Samochod>(); // Dodawanie samochodów komis.Add(new Samochod("Toyota", "Corolla", 2020, 45000, 75000)); komis.Add(new Samochod("Ford", "Focus", 2019, 62000, 55000)); komis.Add(new Samochod("BMW", "320i", 2021, 28000, 120000)); komis.Add(new Samochod("Audi", "A4", 2018, 89000, 68000)); komis.Add(new Samochod("Volkswagen", "Golf", 2022, 15000, 95000)); // Wyświetlenie wszystkich Console.WriteLine("=== KOMIS SAMOCHODOWY ==="); foreach (Samochod auto in komis) { auto.WypiszInfo(); Console.WriteLine(); } // Łatwe filtrowanie! Console.WriteLine("=== SAMOCHODY DO 70 000 zł ==="); foreach (Samochod auto in komis) { if (auto.Cena <= 70000) { Console.WriteLine($"{auto.Marka} {auto.Model}: {auto.Cena:C}"); } } // Statystyki decimal suma = 0; foreach (Samochod auto in komis) { suma += auto.Cena; } Console.WriteLine($"Wartość komisu: {suma:C}");
Możesz przechowywać obiekty w:
Samochod[] auta– tablica (stały rozmiar)List<Samochod> auta– lista (dynamiczna)
To potężna technika – zamiast 500 zmiennych masz jedną listę!
Częste błędy początkujących
❌ Błąd 1: Zapomnienie o new
❌ Źle
Samochod auto; auto.Marka = "Toyota"; // BŁĄD! NullReferenceException // auto jest null – nie ma obiektu!
✅ Dobrze
Samochod auto = new Samochod(); auto.Marka = "Toyota"; // OK! Obiekt istnieje
❌ Błąd 2: Mylenie klasy z obiektem
❌ Źle
Samochod.Marka = "Toyota"; // BŁĄD! Klasa nie ma wartości // – to tylko szablon!
✅ Dobrze
Samochod auto = new Samochod(); auto.Marka = "Toyota"; // OK! Ustawiamy pole OBIEKTU
❌ Błąd 3: Konstruktor z typem zwracanym
❌ Źle
public void Samochod() // void!
{
// To NIE jest konstruktor
// – to zwykła metoda!
}
✅ Dobrze
public Samochod() // Bez typu!
{
// To jest konstruktor
}
❌ Błąd 4: Zapomnienie o nawiasach przy new
❌ Źle
Samochod auto = new Samochod; // BŁĄD składni – brak ()
✅ Dobrze
Samochod auto = new Samochod(); // OK – nawiasy wywołują konstruktor
❌ Błąd 5: Nazwa konstruktora ≠ nazwa klasy
❌ Źle
public class Samochod
{
public Auto() // Inna nazwa!
{ }
}
// BŁĄD – to nie jest konstruktor!
✅ Dobrze
public class Samochod
{
public Samochod() // Ta sama nazwa!
{ }
}
// OK!
Podsumowanie
- OOP – Programowanie Obiektowe (Object-Oriented Programming)
- Klasa – szablon/wzorzec definiujący pola i metody
- Obiekt (instancja) – konkretny „byt” utworzony na podstawie klasy
- Pole – zmienna wewnątrz klasy (dane)
- Metoda – funkcja wewnątrz klasy (zachowanie)
- Konstruktor – specjalna metoda tworząca obiekt
- new – słowo kluczowe tworzące obiekt
Porównanie: Strukturalne vs Obiektowe
| Strukturalne | Obiektowe | |
|---|---|---|
| Dane | Luźne zmienne | Pola w klasie |
| Logika | Funkcje zewnętrzne | Metody w klasie |
| Powiązanie | Brak (trzeba pamiętać) | Automatyczne (obiekt) |
| Nowy element | Wiele nowych zmiennych | Nowy obiekt |
| Zmiana struktury | Wszędzie | Tylko w klasie |
Schemat składni
// DEFINICJA KLASY public class NazwaKlasy { // Pola public typ NazwaPola; // Konstruktor public NazwaKlasy(typ parametr) { NazwaPola = parametr; } // Metoda public void NazwaMetody() { // użycie pola: NazwaPola } } // TWORZENIE I UŻYWANIE OBIEKTU NazwaKlasy obiekt = new NazwaKlasy(wartość); obiekt.NazwaPola = nowaWartość; obiekt.NazwaMetody();
Zadania praktyczne
📝 Zadanie 1: Klasa Książka
Utwórz klasę Ksiazka z polami:
- Tytul (string)
- Autor (string)
- LiczbaStron (int)
- CzyPrzeczytana (bool)
Dodaj konstruktor i metodę WyswietlInfo(). Utwórz 3 obiekty książek i wyświetl ich informacje.
💡 Podpowiedź: CzyPrzeczytana może mieć domyślną wartość false
📝 Zadanie 2: Klasa Prostokąt
Utwórz klasę Prostokat z polami:
- Szerokosc (double)
- Wysokosc (double)
Dodaj metody: ObliczPole() i ObliczObwod() zwracające wyniki obliczeń.
💡 Podpowiedź: Pole = a × b, Obwód = 2 × (a + b)
📝 Zadanie 3: Klasa Uczeń
Utwórz klasę Uczen z polami: Imie, Nazwisko, Klasa, SredniaOcen.
Dodaj metody:
WyswietlDane()– wyświetla wszystkie informacjeCzyZdal()– zwraca true jeśli średnia >= 2.0
Utwórz listę uczniów i wyświetl tylko tych, którzy zdali.
💡 Podpowiedź: Użyj List<Uczen> i pętli foreach z if
⭐ Zadanie 4: Klasa Telefon
Utwórz klasę Telefon z polami: Marka, Model, PoziomBaterii (0-100).
Dodaj metody:
Zadzwon(int minuty)– zmniejsza baterię o minuty × 2Naladuj(int procent)– zwiększa baterię (max 100)WyswietlStatus()– pokazuje stan telefonu
💡 Podpowiedź: Sprawdzaj czy bateria nie spadnie poniżej 0 i nie przekroczy 100
⭐⭐ Zadanie 5: System biblioteki
Utwórz dwie klasy:
- Ksiazka: Tytul, Autor, ISBN, CzyWypozyczona
- Biblioteka: Nazwa, ListaKsiazek (List<Ksiazka>)
W klasie Biblioteka dodaj metody:
DodajKsiazke(Ksiazka k)WypozyczKsiazke(string tytul)– zmienia CzyWypozyczona na trueZwrocKsiazke(string tytul)WyswietlDostepne()– pokazuje tylko niewypożyczone
💡 Podpowiedź: W metodach szukaj książki po tytule używając pętli foreach