Konstruktory w C#-„Fabryka obiektów”

Co to jest konstruktor?

Konstruktor to specjalna metoda, która jest wywoływana automatycznie w momencie tworzenia obiektu. Można go porównać do „fabryki” – ustawia początkowy stan obiektu, nadaje mu wartości i przygotowuje do użycia.

Analogia z życia codziennego

Wyobraź sobie fabrykę samochodów:

  • Konstruktor = linia montażowa
  • Parametry konstruktora = instrukcje montażu (kolor, silnik, wyposażenie)
  • Nowy obiekt = gotowy samochód zjezżający z linii

Podstawy konstruktorów

Jak wygląda konstruktor?

public class Samochod
{
    public string Marka;
    public string Model;
    public int Rocznik;
    
    // To jest konstruktor!
    public Samochod()
    {
        // Kod wykonywany przy tworzeniu obiektu
        Marka = "Nieznana";
        Model = "Nieznany";
        Rocznik = 2024;
    }
}

Charakterystyka konstruktora:

  • Ma taką samą nazwę jak klasa
  • Nie ma typu zwracanego (nawet void)
  • Jest wywoływany automatycznie przy new
  • Może przyjmować parametry

Używanie konstruktora

// Tworzenie obiektu - konstruktor wywołuje się automatycznie
Samochod mojeAuto = new Samochod();

Console.WriteLine(mojeAuto.Marka);   // "Nieznana"
Console.WriteLine(mojeAuto.Model);   // "Nieznany"
Console.WriteLine(mojeAuto.Rocznik); // 2024

Konstruktor domyślny (bezparametrowy)

Jeśli nie napiszesz żadnego konstruktora, C# automatycznie tworzy konstruktor domyślny:

public class Osoba
{
    public string Imie;
    public int Wiek;
    
    // C# automatycznie dodaje:
    // public Osoba() { }
}

// Działa, bo istnieje konstruktor domyślny
Osoba osoba = new Osoba();

UWAGA: Jeśli napiszesz własny konstruktor z parametrami, konstruktor domyślny znika!

public class Student
{
    public string Imie;
    public int Wiek;
    
    // Mamy konstruktor z parametrami
    public Student(string imie)
    {
        Imie = imie;
    }
}

// To już NIE DZIAŁA - brak konstruktora bezparametrowego!
// Student student = new Student(); // BŁĄD!

// Musimy użyć konstruktora z parametrem
Student student = new Student("Jan");

Konstruktory z parametrami

Konstruktor z jednym parametrem

public class Ksiazka
{
    public string Tytul;
    public int Strony;
    public string Autor;
    
    public Ksiazka(string tytul)
    {
        Tytul = tytul;
        Strony = 0;        // Wartość domyślna
        Autor = "Nieznany"; // Wartość domyślna
    }
}

// Używanie
Ksiazka ksiazka = new Ksiazka("Hobbit");
Console.WriteLine(ksiazka.Tytul); // "Hobbit"

Konstruktor z wieloma parametrami

public class Prostokat
{
    public double Szerokosc;
    public double Wysokosc;
    public string Kolor;
    
    public Prostokat(double szerokosc, double wysokosc, string kolor)
    {
        Szerokosc = szerokosc;
        Wysokosc = wysokosc;
        Kolor = kolor;
    }
}

// Używanie - parametry w odpowiedniej kolejności!
Prostokat prostokat = new Prostokat(10.5, 7.2, "czerwony");

Przeciążanie konstruktorów

Możesz mieć kilka konstruktorów w jednej klasie – każdy z różnymi parametrami:

public class Telefon
{
    public string Marka;
    public string Model;
    public double Cena;
    
    // Konstruktor 1 - bez parametrów
    public Telefon()
    {
        Marka = "Nieznana";
        Model = "Nieznany";
        Cena = 0.0;
    }
    
    // Konstruktor 2 - tylko marka
    public Telefon(string marka)
    {
        Marka = marka;
        Model = "Nieznany";
        Cena = 0.0;
    }
    
    // Konstruktor 3 - marka i model
    public Telefon(string marka, string model)
    {
        Marka = marka;
        Model = model;
        Cena = 0.0;
    }
    
    // Konstruktor 4 - wszystkie parametry
    public Telefon(string marka, string model, double cena)
    {
        Marka = marka;
        Model = model;
        Cena = cena;
    }
}

Używanie różnych konstruktorów

// Możemy używać dowolnego konstruktora
Telefon telefon1 = new Telefon();                           // konstruktor 1
Telefon telefon2 = new Telefon("Samsung");                  // konstruktor 2
Telefon telefon3 = new Telefon("Apple", "iPhone 15");       // konstruktor 3
Telefon telefon4 = new Telefon("Xiaomi", "Mi 13", 1200.50); // konstruktor 4

Słowo kluczowe this

W konstruktorach możesz użyć this, żeby odwołać się do obecnego obiektu:

Problem z nazwami

public class Auto
{
    public string marka;
    public string model;
    
    // Parametry mają inne nazwy niż pola - OK
    public Auto(string markaSamochodu, string modelSamochodu)
    {
        marka = markaSamochodu;
        model = modelSamochodu;
    }
    
    // Parametry mają takie same nazwy - potrzebne 'this'
    public Auto(string marka, string model)
    {
        this.marka = marka; // this.marka = pole klasy
        this.model = model; // marka = parametr
    }
}

Wywołanie innego konstruktora

public class Punkt
{
    public int X;
    public int Y;
    
    // Konstruktor główny
    public Punkt(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    // Konstruktor wywołujący inny konstruktor
    public Punkt() : this(0, 0)  // Wywołaj konstruktor z parametrami
    {
        // Dodatkowy kod (opcjonalny)
    }
}

Konstruktor kopiujący

Konstruktor kopiujący to specjalny konstruktor, który tworzy nowy obiekt na podstawie już istniejącego obiektu tego samego typu. W C# nie ma automatycznego konstruktora kopiującego – musimy go napisać sami.

Po co potrzebujemy konstruktora kopiującego?

Pamiętasz z tematu o referencjach? Gdy kopiujemy obiekty, kopiujemy tylko referencje:

public class Osoba
{
    public string Imie;
    public int Wiek;
    
    public Osoba(string imie, int wiek)
    {
        Imie = imie;
        Wiek = wiek;
    }
}

// Problem - to nie tworzy kopii obiektu!
Osoba osoba1 = new Osoba("Jan", 25);
Osoba osoba2 = osoba1; // Tylko kopia referencji!

osoba2.Wiek = 30;
Console.WriteLine(osoba1.Wiek); // 30 - zmienione! To ten sam obiekt!

Jak napisać konstruktor kopiujący

public class Osoba
{
    public string Imie;
    public int Wiek;
    
    // Konstruktor normalny
    public Osoba(string imie, int wiek)
    {
        Imie = imie;
        Wiek = wiek;
    }
    
    // Konstruktor kopiujący - jako parametr obiekt tego samego typu
    public Osoba(Osoba inna)
    {
        if (inna != null) // Sprawdzamy, czy obiekt istnieje
        {
            this.Imie = inna.Imie;
            this.Wiek = inna.Wiek;
        }
        else
        {
            Imie = "Nieznane";
            Wiek = 0;
        }
    }
}

Używanie konstruktora kopiującego

// Tworzymy pierwszy obiekt
Osoba osoba1 = new Osoba("Jan", 25);

// Tworzymy kopię - używamy konstruktora kopiującego
Osoba osoba2 = new Osoba(osoba1); // Nowy obiekt!

// Teraz to są różne obiekty
osoba2.Wiek = 30;
Console.WriteLine(osoba1.Wiek); // 25 - nie zmieniło się!
Console.WriteLine(osoba2.Wiek); // 30

Konstruktor kopiujący dla bardziej złożonych klas

public class Adres
{
    public string Ulica;
    public string Miasto;
    public string KodPocztowy;
    
    public Adres(string ulica, string miasto, string kod)
    {
        Ulica = ulica;
        Miasto = miasto;
        KodPocztowy = kod;
    }
    
    // Konstruktor kopiujący dla Adresu
    public Adres(Adres inny)
    {
        if (inny != null)
        {
            Ulica = inny.Ulica;
            Miasto = inny.Miasto;
            KodPocztowy = inny.KodPocztowy;
        }
    }
}

public class Pracownik
{
    public string Imie;
    public double Pensja;
    public Adres AdresZamieszkania; // Obiekt innej klasy!
    
    public Pracownik(string imie, double pensja, Adres adres)
    {
        Imie = imie;
        Pensja = pensja;
        AdresZamieszkania = adres;
    }
    
    // Konstruktor kopiujący - uwaga na obiekt w obiekcie!
    public Pracownik(Pracownik inny)
    {
        if (inny != null)
        {
            Imie = inny.Imie;
            Pensja = inny.Pensja;
            
            // WAŻNE! Tworzymy kopię adresu, nie kopiujemy referencji
            if (inny.AdresZamieszkania != null)
            {
                AdresZamieszkania = new Adres(inny.AdresZamieszkania);
            }
        }
    }
}

Praktyczne przykłady

Przykład 1: Klasa Uczeń

public class Uczen
{
    public string Imie;
    public string Nazwisko;
    public string Klasa;
    public double SredniaOcen;
    
    // Konstruktor pełny
    public Uczen(string imie, string nazwisko, string klasa)
    {
        Imie = imie;
        Nazwisko = nazwisko;
        Klasa = klasa;
        SredniaOcen = 0.0; // Domyślnie brak ocen
    }
    
    // Konstruktor z średnią
    public Uczen(string imie, string nazwisko, string klasa, double srednia)
    {
        Imie = imie;
        Nazwisko = nazwisko;
        Klasa = klasa;
        SredniaOcen = srednia;
    }
}

// Używanie
Uczen uczen1 = new Uczen("Jan", "Kowalski", "3Ti");
Uczen uczen2 = new Uczen("Anna", "Nowak", "3Ti", 4.5);

Przykład 2: Klasa Konto bankowe

public class KontoBankowe
{
    public string NumerKonta;
    public string Wlasciciel;
    public double Saldo;
    
    // Konstruktor - nowe konto zawsze zaczyna od 0 zł
    public KontoBankowe(string numer, string wlasciciel)
    {
        NumerKonta = numer;
        Wlasciciel = wlasciciel;
        Saldo = 0.0; // Nowe konto ma 0 zł
    }
    
    // Konstruktor z początkowym saldem
    public KontoBankowe(string numer, string wlasciciel, double poczatkoweSaldo)
    {
        NumerKonta = numer;
        Wlasciciel = wlasciciel;
        Saldo = poczatkoweSaldo;
    }
}

Najczęstsze błędy

Błąd 1: Konstruktor z typem zwracanym

// ŹLE - konstruktor nie może mieć typu zwracanego
public void Samochod() 
{
    // To nie jest konstruktor, to zwykła metoda!
}

// DOBRZE
public Samochod()
{
    // To jest konstruktor
}

Błąd 2: Inna nazwa niż klasa

public class Auto
{
    // ŹLE - nazwa konstruktora musi być taka sama jak klasa
    public Samochod()
    {
    }
    
    // DOBRZE
    public Auto()
    {
    }
}

Błąd 3: Zapomnienie o konstruktorze domyślnym

public class Osoba
{
    public string Imie;
    
    // Mamy tylko konstruktor z parametrem
    public Osoba(string imie)
    {
        Imie = imie;
    }
}

// To nie zadziała - brak konstruktora bezparametrowego!
// Osoba osoba = new Osoba(); // BŁĄD!

// Rozwiązanie - dodaj konstruktor domyślny:
public class Osoba
{
    public string Imie;
    
    public Osoba() // Konstruktor domyślny
    {
        Imie = "Nieznane";
    }
    
    public Osoba(string imie)
    {
        Imie = imie;
    }
}

Kluczowe wnioski

  1. Konstruktor = specjalna metoda wywoływana przy tworzeniu obiektu
  2. Nazwa konstruktora = nazwa klasy
  3. Brak typu zwracanego w konstruktorze
  4. Konstruktor domyślny znika, gdy dodasz własny konstruktor z parametrami
  5. Przeciążanie – możesz mieć wiele konstruktorów z różnymi parametrami
  6. this – odwołanie do obecnego obiektu
  7. Konstruktor kopiujący – tworzy kopię obiektu, nie kopię referencji

Zadania do przećwiczenia

Zadanie 1: Utwórz klasę Filmv

Napisz klasę Film z polami:

  • Tytul (string)
  • Rezyser (string)
  • RokProdukcji (int)
  • CzasTrwania (int, w minutach)

Utwórz konstruktory:

  1. Bezparametrowy (domyślne wartości)
  2. Z tytułem i reżyserem
  3. Z wszystkimi parametrami

Zadanie 2: Klasa Komputer

Napisz klasę Komputer z polami:

  • Procesor (string)
  • Ram (int, GB)
  • Cena (double)

Stwórz różne konstruktory i przetestuj je.

Zadanie 3: Znajdź błędy

public class Zwierze
{
    public string Gatunek;
    public int Wiek;
    
    // Czy ten konstruktor jest poprawny?
    public int Zwierze(string gatunek)
    {
        Gatunek = gatunek;
        return 0;
    }
}

Zadanie 4: Konstruktor z walidacją

public class Student
{
    public string Imie;
    public int Wiek;
    
    public Student(string imie, int wiek)
    {
        // Dodaj sprawdzenie:
        // - imie nie może być puste
        // - wiek musi być między 16 a 100
    }
}