Klasy w TypeScript

Co to jest klasa?

Klasa to jeden z podstawowych elementów programowania obiektowego. Można ją uznać za szablon, który definiuje strukturę oraz zachowanie obiektów.

Obiekty to instancje klas, czyli konkretne realizacje tego szablonu.

Struktura klasy

class Student {
  // POLA (zmienne) - przechowują dane
  imie: string;
  nazwisko: string;
  indeks: number;
  
  // KONSTRUKTOR - inicjalizuje obiekt
  constructor(imie: string, nazwisko: string, indeks: number) {
    this.imie = imie;
    this.nazwisko = nazwisko;
    this.indeks = indeks;
  }
  
  // METODY - opisują działania
  przedstawSie(): void {
    console.log(`Jestem ${this.imie} ${this.nazwisko}`);
  }
  
  getIndeks(): number {
    return this.indeks;
  }
}

Pola (Atrybuty)

Pola to zmienne należące do klasy. Każdy obiekt ma własne kopie pól.

class Auto {
  marka: string;        // pole type string
  model: string;        // pole type string
  rocznik: number;      // pole type number
  przebieg: number;     // pole type number
}

Konstruktor

Konstruktor to specjalna metoda wywoływana automatycznie przy tworzeniu obiektu.

class Auto {
  marka: string;
  model: string;
  rocznik: number;
  
  // Konstruktor
  constructor(marka: string, model: string, rocznik: number) {
    this.marka = marka;
    this.model = model;
    this.rocznik = rocznik;
  }
}

Czym jest this?

this odnosi się do konkretnego obiektu. this.marka = pole marka tego obiektu.

// ❌ BŁĄD - nie przypisuje do pola
constructor(marka: string) {
  marka = marka;
}

// ✅ POPRAWNIE - przypisuje do pola
constructor(marka: string) {
  this.marka = marka;
}

Metody

Metody to funkcje należące do klasy. Opisują, co obiekt potrafi robić.

class Kalkulator {
  dodaj(a: number, b: number): number {
    return a + b;
  }
  
  odejmij(a: number, b: number): number {
    return a - b;
  }
  
  wyswietlInfo(): void {
    console.log("To jest kalkulator");
  }
}

Tworzenie i używanie obiektów

Składnia

const <zmienna> = new <Klasa>(<argumenty>);

Przykład

class Pojazd {
  marka: string;
  model: string;
  rocznik: number;
  
  constructor(marka: string, model: string, rocznik: number) {
    this.marka = marka;
    this.model = model;
    this.rocznik = rocznik;
  }
  
  wyswietlInfo(): void {
    console.log(`${this.marka} ${this.model} z ${this.rocznik} roku`);
  }
  
  sprzedaj(): void {
    console.log(`${this.marka} ${this.model} została sprzedana`);
  }
}

// Tworzenie obiektu
const pojazd1 = new Pojazd("Skoda", "Fabia", 2020);
pojazd1.wyswietlInfo();  // Skoda Fabia z 2020 roku
pojazd1.sprzedaj();       // Skoda Fabia została sprzedana

// Inny obiekt
const pojazd2 = new Pojazd("Toyota", "Corolla", 2022);
pojazd2.wyswietlInfo();  // Toyota Corolla z 2022 roku

Ważne: Każdy obiekt ma swoje własne pola!

Modyfikatory dostępu

Czym są modyfikatory dostępu?

Modyfikatory dostępu kontrolują, skąd można odczytywać i zmieniać pola/metody klasy.

Trzy Główne Modyfikatory

public (domyślnie)

Dostęp z wszędzie – z innej klasy, z innego pliku, z globalnego zakresu.

class Osoba {
  public imie: string;  // Dostęp z wszędzie
  
  public przedstawSie(): void {
    console.log(`Jestem ${this.imie}`);
  }
}

const osoba = new Osoba();
osoba.imie = "Jan";           // ✅ OK - zmienić z zewnątrz
osoba.przedstawSie();         // ✅ OK - wywołać z zewnątrz
console.log(osoba.imie);      // ✅ OK - odczytać z zewnątrz

private

Dostęp tylko wewnątrz klasy, z innego miejsca – brak dostępu.

class Konto {
  private saldo: number;  // Tylko wewnątrz klasy!
  
  constructor(saldo: number) {
    this.saldo = saldo;
  }
  
  // Metoda wewnątrz klasy - MOŻE uzyskać dostęp
  wypłać(kwota: number): void {
    if (kwota <= this.saldo) {
      this.saldo -= kwota;
      console.log(`Wypłacono ${kwota} zł`);
    }
  }
}

const konto = new Konto(1000);
konto.wypłać(100);        // ✅ OK - metoda publiczna
// konto.saldo = 5000;    // ❌ BŁĄD - saldo to private!
// console.log(konto.saldo); // ❌ BŁĄD - nie mogę uzyskać dostępu

Dlaczego private?

  • Bezpieczeństwo – nikt nie może zmienić saldo wprost
  • Kontrola – zmiana tylko przez publiczne metody
  • Logika biznesowa – np. nie można mieć ujemnego salda

protected (zaawansowane)

Dostęp wewnątrz klasy. (Będzie ważne z dziedziczeniem w przyszłości)

class Osoba {
  protected wiek: number;  // Dostęp w Osoba, ale nie z zewnątrz
  
  constructor(wiek: number) {
    this.wiek = wiek;
  }
}

const osoba = new Osoba(25);
// osoba.wiek = 30;  // ❌ BŁĄD - protected

Porównanie

class Pracownik {
  public imie: string;        // Wszyscy mogą czytać i zmieniać
  private wynagrodzenie: number; // Tylko Pracownik zna wynagrodzenie
  protected stanowisko: string;  // Dostęp w klasie
  
  constructor(imie: string, wynagrodzenie: number, stanowisko: string) {
    this.imie = imie;
    this.wynagrodzenie = wynagrodzenie;
    this.stanowisko = stanowisko;
  }
  
  // Metoda publiczna - każdy może wywołać
  pracuj(): void {
    console.log(`${this.imie} pracuje`);
  }
  
  // Metoda prywatna - tylko wewnątrz klasy
  private obliczBonusDoWynagrodzeina(): number {
    return this.wynagrodzenie * 0.1;
  }
}

GETTER I SETTER – dostęp do prywatnych pól

Problem

Pola private nie mogą być dostępne z zewnątrz, ale czasami chcemy udostępnić dostęp kontrolowany.

Rozwiązanie: Getter i Setter

Getter (pobieranie wartości)

class Konto {
  private saldo: number;
  
  constructor(saldo: number) {
    this.saldo = saldo;
  }
  
  // Getter - pobiera wartość
  get getSaldo(): number {
    return this.saldo;
  }
}

const konto = new Konto(1000);
console.log(konto.getSaldo);  // 1000 (bez nawiasów!)

Setter (przypisywanie wartości)

class Osoba {
  private wiek: number;
  
  constructor(wiek: number) {
    this.wiek = wiek;
  }
  
  // Setter - ustawia wartość z walidacją
  set setWiek(nowyWiek: number) {
    if (nowyWiek > 0 && nowyWiek < 150) {
      this.wiek = nowyWiek;
    } else {
      console.log("Nieprawidłowy wiek!");
    }
  }
  
  // Getter - pobiera wartość
  get getWiek(): number {
    return this.wiek;
  }
}

const osoba = new Osoba(25);
osoba.setWiek = 30;           // ✅ OK - setter
console.log(osoba.getWiek);   // 30 - getter
osoba.setWiek = 200;          // ❌ Nieprawidłowy wiek!

Praktyczny Przykład

class Produktu {
  private nazwa: string;
  private cena: number;
  
  constructor(nazwa: string, cena: number) {
    this.nazwa = nazwa;
    this.cena = cena;
  }
  
  // Getter dla ceny
  get getCena(): number {
    return this.cena;
  }
  
  // Setter dla ceny ze zniżką
  set setCenaZeZnizka(procentZnizki: number) {
    if (procentZnizki > 0 && procentZnizki < 100) {
      this.cena = this.cena * (1 - procentZnizki / 100);
    }
  }
  
  // Getter dla nazwy
  get getNazwa(): string {
    return this.nazwa;
  }
}

const produkt = new Produktu("Laptop", 3000);
console.log(produkt.getCena);      // 3000
produkt.setCenaZeZnizka = 10;      // 10% zniżki
console.log(produkt.getCena);      // 2700

Modyfikatory dostępu – porady

Reguła Złota

❌ Nie rób nic public jeśli nie musi być
✅ Domyślnie rób private
✅ Udostępnij przez gettery/settery jeśli potrzeba

Przykład: Budowanie Klasy

// ZŁYLE - wszystko public
class ZleKonto {
  public saldo: number = 1000;
}
const zlekonto = new ZleKonto();
zlekonto.saldo = -9999;  // 😱 Ujemne saldo!

// DOBRZE - saldo private, dostęp przez getter
class DobreKonto {
  private saldo: number = 1000;
  
  // Getter - tylko do odczytania
  get Saldo(): number {
    return this.saldo;
  }
  
  // Metoda publiczna - kontrolowana zmiana
  wyplac(kwota: number): boolean {
    if (kwota > 0 && kwota <= this.saldo) {
      this.saldo -= kwota;
      return true;
    }
    return false;
  }
}

Organizacja kodu – klasy w osobnych plikach

Problem: Wszystko w Jednym Pliku

Jeśli cały kod jest w jednym pliku – chaos!

app.ts (1000+ linii)
├─ Klasa Student
├─ Klasa Nauczyciel
├─ Klasa Szkoła
├─ Kod główny
└─ ... wszystko razem

Rozwiązanie: Klasy w Osobnych Plikach

src/
├─ models/
│  ├─ Student.ts
│  ├─ Nauczyciel.ts
│  └─ Szkoła.ts
├─ utils/
│  └─ helpers.ts
└─ app.ts (główny plik)

Krok 1: Stwórz Plik dla Klasy

Plik: src/models/Student.ts

export class Student {
  private imie: string;
  private nazwisko: string;
  private indeks: number;
  
  constructor(imie: string, nazwisko: string, indeks: number) {
    this.imie = imie;
    this.nazwisko = nazwisko;
    this.indeks = indeks;
  }
  
  get Imie(): string {
    return this.imie;
  }
  
  przedstawSie(): void {
    console.log(`Jestem ${this.imie} ${this.nazwisko}`);
  }
}

Ważne: export – pozwala innym plikom używać tej klasy!

Krok 2: Zaimportuj Klasę

Plik: src/app.ts

import { Student } from './models/Student';

// Teraz możesz używać Student
const student = new Student("Jan", "Kowalski", 123456);
student.przedstawSie();
console.log(student.Imie);

Import: import { Student } from './models/Student';

Zadania:

ZADANIE 1: Klasa Osoba

Stwórz klasę Osoba z:

  • Polami private: imie, nazwisko, wiek
  • Konstruktorem
  • Getterami dla wszystkich pól
  • Setterem dla wieku z walidacją (0-120)
  • Metodą predstavSie() – wypisuje „Jestem [imie] [nazwisko]”

ZADANIE 2: Klasa Kalkulator

Stwórz klasę Kalkulator z metodami:

  • dodaj(a, b): number
  • odejmij(a, b): number
  • pomnoz(a, b): number
  • podziel(a, b): number (sprawdź dzielenie przez 0)
  • potega(a, b): number

ZADANIE 3: Klasa Auto

Stwórz klasę Auto z:

  • Polami private: marka, model, rocznik, przebieg
  • Konstruktorem
  • Getterami dla wszystkich pól
  • Metodą jazda(km: number) – zwiększa przebieg
  • Metodą wyswietlInfo() – wypisuje dane auta

ZADANIE 4: Projekt w Osobnych Plikach

Stwórz projekt z npm init:

  • Klasa Student w src/models/Student.ts
  • Klasa Nauczyciel w src/models/Nauczyciel.ts
  • Główny plik src/app.ts
  • Kompiluj i uruchamiaj

ZADANIE 5: System Biblioteczny

Stwórz klasy:

  • Ksiazka (titulo, autor, rok, dostępna)
  • Uzytkownik (imie, nazwisko, wypożyczone)
  • Metody: wypożycz(), zwróć(), wyswietlInfo()