Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Modyfikatory dostępu (public, private, protected)

"Bezpieczeństwo w Parku Jurajskim to kwestia najwyższej wagi," stwierdza stanowczo John Hammond, prezes InGen. "Niektóre dane i procedury muszą być dostępne dla wszystkich pracowników, inne tylko dla osób z odpowiednimi uprawnieniami, a jeszcze inne są tak krytyczne, że dostęp do nich ma wyłącznie najwyższe kierownictwo."

Podobnie jak w Parku Jurajskim, w programowaniu obiektowym kontrola dostępu do właściwości i metod klas jest fundamentalnym aspektem dobrego projektowania. TypeScript oferuje trzy modyfikatory dostępu, które pozwalają precyzyjnie określić, kto i w jakim zakresie może korzystać z poszczególnych elementów klas: public, private i protected.

Wprowadzenie do modyfikatorów dostępu

Modyfikatory dostępu określają, z jakich poziomów kodu można uzyskać dostęp do właściwości i metod klasy:

  1. public - dostęp z dowolnego miejsca w kodzie (domyślny)
  2. private - dostęp tylko wewnątrz danej klasy
  3. protected - dostęp wewnątrz danej klasy i klas dziedziczących

Przyjrzyjmy się, jak te modyfikatory działają w praktyce, implementując system zarządzania dinozaurami w Parku Jurajskim.

Modyfikator public

Elementy oznaczone jako public są dostępne z dowolnego miejsca w kodzie - zarówno wewnątrz klasy, jak i na jej instancjach. Jest to domyślny modyfikator dostępu w TypeScript - jeśli nie określisz żadnego modyfikatora, element zostanie automatycznie oznaczony jako public.

1class Dinozaur {
2  // Właściwości publiczne
3  public nazwa: string;
4  public gatunek: string;
5  public wiek: number;
6
7  constructor(nazwa: string, gatunek: string, wiek: number) {
8    this.nazwa = nazwa;
9    this.gatunek = gatunek;
10    this.wiek = wiek;
11  }
12
13  // Metoda publiczna
14  public wyświetlInformacje(): void {
15    console.log(`${this.nazwa}: ${this.gatunek}, ${this.wiek} lat`);
16  }
17}
18
19// Tworzenie instancji
20const rex = new Dinozaur("Rex", "Tyrannosaurus", 7);
21
22// Dostęp do publicznych właściwości i metod z zewnątrz klasy
23console.log(rex.nazwa);  // "Rex" - dostęp do publicznej właściwości
24rex.wiek = 8;  // Modyfikacja publicznej właściwości
25rex.wyświetlInformacje();  // Wywołanie publicznej metody

Właściwości i metody publiczne są jak ogólnodostępne informacje w parku - na przykład mapa parku, godziny otwarcia, czy lokalizacje toalet. Wszyscy mogą ich używać bez ograniczeń.

Modyfikator private

Elementy oznaczone jako private są dostępne tylko wewnątrz klasy, w której zostały zdefiniowane. Ta hermetyzacja pozwala na ukrycie wewnętrznych szczegółów implementacji i zapobiega przypadkowym modyfikacjom stanu z zewnątrz klasy.

1class DiagnozaZdrowia {
2  // Właściwości prywatne
3  private id: string;
4  private wynikiBadań: Record<string, number>;
5  private uwagi: string;
6  public statusZdrowia: "zdrowy" | "chory" | "krytyczny";
7
8  constructor(id: string) {
9    this.id = id;
10    this.wynikiBadań = {};
11    this.uwagi = "";
12    this.statusZdrowia = "zdrowy";
13  }
14
15  // Prywatna metoda - używana tylko wewnątrz klasy
16  private analizujWyniki(): "zdrowy" | "chory" | "krytyczny" {
17    // Złożona logika analizy wyników badań
18    const temperaturaCiała = this.wynikiBadań["temperaturaCiała"] || 37;
19    const poziomLeukocytów = this.wynikiBadań["poziomLeukocytów"] || 7000;
20
21    if (temperaturaCiała > 40 || poziomLeukocytów > 15000) {
22      return "krytyczny";
23    } else if (temperaturaCiała > 38.5 || poziomLeukocytów > 11000) {
24      return "chory";
25    } else {
26      return "zdrowy";
27    }
28  }
29
30  // Publiczne metody korzystające z prywatnych właściwości
31  public dodajWynikBadania(nazwa: string, wartość: number): void {
32    this.wynikiBadań[nazwa] = wartość;
33    this.statusZdrowia = this.analizujWyniki();  // Wywołanie prywatnej metody
34  }
35
36  public dodajUwagę(uwaga: string): void {
37    this.uwagi += uwaga + "\n";
38  }
39
40  public pobierzRaport(): string {
41    return `
42      ID: ${this.id}
43      Status zdrowia: ${this.statusZdrowia}
44      Uwagi: ${this.uwagi || "Brak"}
45    `;
46  }
47}
48
49// Tworzenie instancji
50const diagnozaRaptora = new DiagnozaZdrowia("VL-001");
51
52// Używanie publicznego API
53diagnozaRaptora.dodajWynikBadania("temperaturaCiała", 39.2);
54diagnozaRaptora.dodajWynikBadania("poziomLeukocytów", 12500);
55diagnozaRaptora.dodajUwagę("Objawy infekcji wirusowej");
56
57console.log(diagnozaRaptora.statusZdrowia);  // "chory" - dostęp do publicznej właściwości
58console.log(diagnozaRaptora.pobierzRaport());  // Wywołanie publicznej metody
59
60// Próba dostępu do prywatnych elementów
61// console.log(diagnozaRaptora.id);  // Błąd: Property 'id' is private
62// console.log(diagnozaRaptora.wynikiBadań);  // Błąd: Property 'wynikiBadań' is private
63// diagnozaRaptora.analizujWyniki();  // Błąd: Property 'analizujWyniki' is private

Prywatne właściwości i metody są jak zastrzeżone procedury laboratorium genetycznego - tylko upoważniony personel laboratorium (metody klasy) ma do nich dostęp, a dla reszty personelu są one ukryte ze względów bezpieczeństwa.

Modyfikator protected

Elementy oznaczone jako protected są dostępne wewnątrz klasy, w której zostały zdefiniowane, oraz w klasach dziedziczących. Pozwala to na tworzenie hierarchii klas, gdzie klasy pochodne mają dostęp do chronionych elementów klasy bazowej, ale elementy te pozostają niedostępne z zewnątrz hierarchii klas.

1// Klasa bazowa
2class Zwierzę {
3  // Właściwości chronione
4  protected id: string;
5  protected gatunek: string;
6  protected waga: number;
7  protected zdrowie: number;  // 0-100
8
9  constructor(id: string, gatunek: string, waga: number) {
10    this.id = id;
11    this.gatunek = gatunek;
12    this.waga = waga;
13    this.zdrowie = 100;
14  }
15
16  // Chroniona metoda
17  protected zmodyfikujZdrowie(zmiana: number): void {
18    this.zdrowie = Math.max(0, Math.min(100, this.zdrowie + zmiana));
19  }
20
21  // Publiczna metoda
22  public wyświetlStatus(): string {
23    return `${this.gatunek} (${this.id}): Waga: ${this.waga}kg, Zdrowie: ${this.zdrowie}%`;
24  }
25}
26
27// Klasa dziedzicząca
28class Dinozaur extends Zwierzę {
29  public imię: string;
30  private poziomAgresji: number;  // 0-10
31
32  constructor(id: string, gatunek: string, imię: string, waga: number) {
33    super(id, gatunek, waga);
34    this.imię = imię;
35    this.poziomAgresji = 5;
36  }
37
38  // Metoda wykorzystująca chronione elementy klasy bazowej
39  public nakarm(ilość: number): void {
40    this.zmodyfikujZdrowie(ilość * 5);  // Dostęp do chronionej metody z klasy bazowej
41    this.poziomAgresji = Math.max(0, this.poziomAgresji - 1);
42    console.log(`${this.imię} został nakarmiony. Nowy poziom zdrowia: ${this.zdrowie}%`);
43  }
44
45  public doznajObrażeń(ilość: number): void {
46    this.zmodyfikujZdrowie(-ilość);  // Dostęp do chronionej metody z klasy bazowej
47    this.poziomAgresji = Math.min(10, this.poziomAgresji + 2);
48    console.log(`${this.imię} doznał obrażeń. Nowy poziom zdrowia: ${this.zdrowie}%`);
49  }
50
51  // Nadpisanie metody bazowej z wykorzystaniem chronionych właściwości
52  public wyświetlStatus(): string {
53    return `${this.imię} (${this.gatunek}): Waga: ${this.waga}kg, Zdrowie: ${this.zdrowie}%, Agresja: ${this.poziomAgresji}/10`;
54  }
55}
56
57// Klasa dziedzicząca z modyfikacją chronionej właściwości
58class DinozaurMorski extends Zwierzę {
59  private głębokośćNurkowania: number;
60
61  constructor(id: string, gatunek: string, waga: number, głębokośćNurkowania: number) {
62    super(id, gatunek, waga);
63    this.głębokośćNurkowania = głębokośćNurkowania;
64
65    // Modyfikacja chronionej właściwości w konstruktorze
66    this.zdrowie = 90;  // Morskie dinozaury zaczynają z mniejszym zdrowiem
67  }
68
69  public pływaj(czas: number): void {
70    // Używanie chronionej metody
71    this.zmodyfikujZdrowie(czas > 10 ? -10 : 5);
72
73    console.log(`Dinozaur morski ${this.id} pływał przez ${czas} minut na głębokości ${this.głębokośćNurkowania}m`);
74    console.log(`Nowy poziom zdrowia: ${this.zdrowie}%`);
75  }
76}
77
78// Tworzenie instancji
79const velociraptor = new Dinozaur("VL-002", "Velociraptor", "Blue", 150);
80const mosasaur = new DinozaurMorski("MS-001", "Mosasaurus", 15000, 50);
81
82// Używanie publicznych metod
83velociraptor.nakarm(2);
84velociraptor.doznajObrażeń(15);
85console.log(velociraptor.wyświetlStatus());
86
87mosasaur.pływaj(15);
88console.log(mosasaur.wyświetlStatus());
89
90// Próba dostępu do chronionych elementów
91// console.log(velociraptor.id);  // Błąd: Property 'id' is protected
92// console.log(mosasaur.zdrowie);  // Błąd: Property 'zdrowie' is protected
93// velociraptor.zmodyfikujZdrowie(50);  // Błąd: Property 'zmodyfikujZdrowie' is protected

Chronione właściwości i metody są jak procedury bezpieczeństwa parku - dostęp do nich mają tylko upoważnieni pracownicy i ich podwładni (klasa bazowa i klasy dziedziczące), ale nie goście parku (kod zewnętrzny).

Konstruktor i modyfikatory dostępu

TypeScript oferuje specjalną składnię dla konstruktorów, która pozwala na bezpośrednie deklarowanie właściwości z modyfikatorami dostępu w parametrach konstruktora. Jest to skrótowy zapis, który pozwala uniknąć powtarzania kodu.

1// Tradycyjny zapis
2class PracownikParku {
3  private id: string;
4  public imię: string;
5  public nazwisko: string;
6  protected poziomDostępu: number;
7
8  constructor(id: string, imię: string, nazwisko: string, poziomDostępu: number) {
9    this.id = id;
10    this.imię = imię;
11    this.nazwisko = nazwisko;
12    this.poziomDostępu = poziomDostępu;
13  }
14}
15
16// Skrócony zapis z modyfikatorami w parametrach konstruktora
17class PracownikParkuSkrócony {
18  constructor(
19    private id: string,
20    public imię: string,
21    public nazwisko: string,
22    protected poziomDostępu: number
23  ) {}  // Puste ciało konstruktora - wszystko już zostało zainicjalizowane
24}
25
26// Oba zapisy działają identycznie
27const pracownik1 = new PracownikParku("EMP-001", "John", "Hammond", 10);
28const pracownik2 = new PracownikParkuSkrócony("EMP-002", "Ellie", "Sattler", 8);
29
30console.log(pracownik1.imię, pracownik1.nazwisko);  // "John Hammond"
31console.log(pracownik2.imię, pracownik2.nazwisko);  // "Ellie Sattler"
32
33// console.log(pracownik1.id);  // Błąd: Property 'id' is private
34// console.log(pracownik2.id);  // Błąd: Property 'id' is private

Ta skrócona składnia jest szczególnie przydatna w prostych klasach z wieloma właściwościami, ponieważ redukuje ilość powtarzającego się kodu.

Hermetyzacja i wzorzec dostępowy (gettery i settery)

Jednym z głównych powodów stosowania modyfikatorów dostępu jest hermetyzacja - ukrywanie szczegółów implementacji i udostępnianie tylko kontrolowanego interfejsu do interakcji z klasą. Częścią tego podejścia jest stosowanie getterów i setterów - specjalnych metod kontrolujących dostęp do właściwości.

1class EnclosureSecurity {
2  // Prywatne właściwości
3  private _statusOgrodzenia: "aktywne" | "nieaktywne" | "w_naprawie";
4  private _poziomNaładowaniaBaterii: number;  // 0-100
5  private _ostatniaPrzegląd: Date;
6  private _logZdarzeń: string[];
7
8  constructor() {
9    this._statusOgrodzenia = "aktywne";
10    this._poziomNaładowaniaBaterii = 100;
11    this._ostatniaPrzegląd = new Date();
12    this._logZdarzeń = ["System uruchomiony: " + new Date().toISOString()];
13  }
14
15  // Gettery - kontrolowany dostęp do odczytu prywatnych właściwości
16  get statusOgrodzenia(): "aktywne" | "nieaktywne" | "w_naprawie" {
17    this.dodajLog("Odczytano status ogrodzenia");
18    return this._statusOgrodzenia;
19  }
20
21  get poziomNaładowaniaBaterii(): number {
22    this.dodajLog("Odczytano poziom naładowania baterii");
23    return this._poziomNaładowaniaBaterii;
24  }
25
26  get ostatniaPrzegląd(): Date {
27    return new Date(this._ostatniaPrzegląd.getTime());  // Defensywna kopia
28  }
29
30  get raportBezpieczeństwa(): string {
31    this.dodajLog("Wygenerowano raport bezpieczeństwa");
32    return `
33      Status ogrodzenia: ${this._statusOgrodzenia}
34      Poziom naładowania baterii: ${this._poziomNaładowaniaBaterii}%
35      Ostatni przegląd: ${this._ostatniaPrzegląd.toLocaleDateString()}
36      Liczba zdarzeń w logu: ${this._logZdarzeń.length}
37    `;
38  }
39
40  // Settery - kontrolowany dostęp do modyfikacji prywatnych właściwości
41  set statusOgrodzenia(nowyStatus: "aktywne" | "nieaktywne" | "w_naprawie") {
42    this.dodajLog(`Zmieniono status ogrodzenia z ${this._statusOgrodzenia} na ${nowyStatus}`);
43    this._statusOgrodzenia = nowyStatus;
44
45    if (nowyStatus === "nieaktywne") {
46      this.uruchomProcedurySecurity();
47    }
48  }
49
50  // Ten setter zawiera logikę walidacyjną
51  set poziomNaładowaniaBaterii(nowyPoziom: number) {
52    if (nowyPoziom < 0 || nowyPoziom > 100) {
53      throw new Error("Poziom naładowania baterii musi być między 0 a 100");
54    }
55
56    const staraWartość = this._poziomNaładowaniaBaterii;
57    this._poziomNaładowaniaBaterii = nowyPoziom;
58    this.dodajLog(`Zmieniono poziom naładowania baterii z ${staraWartość}% na ${nowyPoziom}%`);
59
60    if (nowyPoziom < 20) {
61      this.dodajLog("UWAGA: Niski poziom baterii!");
62    }
63  }
64
65  // Prywatna metoda pomocnicza
66  private dodajLog(wiadomość: string): void {
67    this._logZdarzeń.push(`${new Date().toISOString()}: ${wiadomość}`);
68  }
69
70  // Prywatna metoda biznesowa
71  private uruchomProcedurySecurity(): void {
72    this.dodajLog("ALERT: Uruchomiono procedury awaryjne!");
73    // Logika obsługi sytuacji awaryjnej
74  }
75
76  // Publiczna metoda
77  public przeprowadźPrzegląd(): void {
78    this._ostatniaPrzegląd = new Date();
79    this.dodajLog("Przeprowadzono przegląd ogrodzenia");
80    this._poziomNaładowaniaBaterii = 100;
81    this._statusOgrodzenia = "aktywne";
82  }
83
84  // Publiczna metoda
85  public pobierzLogZdarzeń(): string[] {
86    // Zwracamy kopię, aby uniemożliwić modyfikację oryginalnego logu
87    return [...this._logZdarzeń];
88  }
89}
90
91// Użycie klasy
92const security = new EnclosureSecurity();
93
94// Używanie getterów
95console.log("Status ogrodzenia:", security.statusOgrodzenia);
96console.log("Poziom baterii:", security.poziomNaładowaniaBaterii);
97
98// Używanie setterów
99security.statusOgrodzenia = "w_naprawie";
100security.poziomNaładowaniaBaterii = 85;
101
102// Używanie publicznych metod
103security.przeprowadźPrzegląd();
104
105// Próba ustawienia nieprawidłowej wartości
106try {
107  security.poziomNaładowaniaBaterii = 110;  // Wywoła błąd
108} catch (e) {
109  console.error(e.message);
110}
111
112// Odczyt raportu i logów
113console.log(security.raportBezpieczeństwa);
114console.log("Historia zdarzeń:");
115security.pobierzLogZdarzeń().forEach((log, i) => {
116  console.log(`${i + 1}. ${log}`);
117});

Używanie getterów i setterów pozwala na większą kontrolę nad dostępem do danych:

  • Możemy dodać logikę walidacji przed zmianą wartości
  • Możemy wykonać dodatkowe operacje przy dostępie do danych
  • Możemy zwracać defensywne kopie obiektów, aby zapobiec przypadkowym modyfikacjom
  • Możemy logować dostęp do danych

Praktyczny przykład: System zarządzania dinozaurami

Poniżej przedstawiamy kompleksowy przykład systemu zarządzania dinozaurami, który wykorzystuje wszystkie omówione modyfikatory dostępu dla stworzenia dobrze zorganizowanej architektury obiektowej:

1// Typy i interfejsy
2type StatusDinozaura = "zdrowy" | "chory" | "w leczeniu" | "uśpiony" | "zmarły";
3type TypDiety = "mięsożerny" | "roślinożerny" | "wszystkożerny";
4
5interface IMonitorowania {
6  rozpocznijMonitorowanie(): void;
7  zakończMonitorowanie(): void;
8  pobierzStatusMonitorowania(): string;
9}
10
11// Abstrakcyjna klasa bazowa
12abstract class ZwierzęParku {
13  // Właściwości z różnymi modyfikatorami dostępu
14  private _id: string;
15  protected _gatunek: string;
16  protected _wiek: number;
17  protected _waga: number;
18  protected _status: StatusDinozaura;
19  private _dataUtworzenia: Date;
20  private _ostatniaAktualizacja: Date;
21
22  constructor(id: string, gatunek: string, wiek: number, waga: number) {
23    this._id = id;
24    this._gatunek = gatunek;
25    this._wiek = wiek;
26    this._waga = waga;
27    this._status = "zdrowy";
28    this._dataUtworzenia = new Date();
29    this._ostatniaAktualizacja = new Date();
30  }
31
32  // Gettery
33  get id(): string {
34    return this._id;
35  }
36
37  get gatunek(): string {
38    return this._gatunek;
39  }
40
41  get wiek(): number {
42    return this._wiek;
43  }
44
45  get waga(): number {
46    return this._waga;
47  }
48
49  get status(): StatusDinozaura {
50    return this._status;
51  }
52
53  // Setter ze sprawdzeniem wartości i aktualizacją daty
54  set status(nowyStatus: StatusDinozaura) {
55    if (this._status !== nowyStatus) {
56      this._status = nowyStatus;
57      this._ostatniaAktualizacja = new Date();
58      this.logZmianyStatusu(nowyStatus);
59    }
60  }
61
62  // Chroniona metoda dostępna dla klas dziedziczących
63  protected aktualizujWagę(nowaWaga: number): void {
64    if (nowaWaga > 0) {
65      this._waga = nowaWaga;
66      this._ostatniaAktualizacja = new Date();
67    } else {
68      throw new Error("Waga musi być większa od zera.");
69    }
70  }
71
72  // Prywatna metoda dostępna tylko wewnątrz tej klasy
73  private logZmianyStatusu(nowyStatus: StatusDinozaura): void {
74    console.log(`[${new Date().toISOString()}] Zmiana statusu ${this._id}: ${nowyStatus}`);
75  }
76
77  // Abstrakcyjna metoda - każda klasa dziedzicząca musi ją zaimplementować
78  abstract pobierzOpisKliniczny(): string;
79
80  // Publiczna metoda
81  public pobierzPodstawoweDane(): string {
82    return `ID: ${this._id} | Gatunek: ${this._gatunek} | Wiek: ${this._wiek} lat | Waga: ${this._waga} kg | Status: ${this._status}`;
83  }
84}
85
86// Klasa dziedzicząca
87class Dinozaur extends ZwierzęParku implements IMonitorowania {
88  // Prywatne właściwości specyficzne dla dinozaurów
89  private _dieta: TypDiety;
90  private _poziomAgresji: number; // 1-10
91  private _czyMonitorowany: boolean;
92  private _ostatniPosiłek: Date | null;
93  private _przypisanyWybieg: string | null;
94
95  constructor(
96    id: string,
97    gatunek: string,
98    wiek: number,
99    waga: number,
100    dieta: TypDiety,
101    poziomAgresji: number
102  ) {
103    super(id, gatunek, wiek, waga);
104    this._dieta = dieta;
105    this._poziomAgresji = this.walidujPoziomAgresji(poziomAgresji);
106    this._czyMonitorowany = false;
107    this._ostatniPosiłek = null;
108    this._przypisanyWybieg = null;
109  }
110
111  // Implementacja metody abstrakcyjnej
112  pobierzOpisKliniczny(): string {
113    let opis = `Dinozaur ${this.gatunek} (ID: ${this.id})\n`;
114    opis += `Status zdrowotny: ${this.status}\n`;
115    opis += `Wiek: ${this.wiek} lat, Waga: ${this.waga} kg\n`;
116    opis += `Dieta: ${this._dieta}\n`;
117    opis += `Poziom agresji: ${this._poziomAgresji}/10\n`;
118    opis += `Ostatni posiłek: ${this._ostatniPosiłek ? this._ostatniPosiłek.toLocaleString() : "Brak danych"}\n`;
119
120    return opis;
121  }
122
123  // Implementacja interfejsu IMonitorowania
124  rozpocznijMonitorowanie(): void {
125    this._czyMonitorowany = true;
126    console.log(`Rozpoczęto monitorowanie dinozaura ${this.id}`);
127  }
128
129  zakończMonitorowanie(): void {
130    this._czyMonitorowany = false;
131    console.log(`Zakończono monitorowanie dinozaura ${this.id}`);
132  }
133
134  pobierzStatusMonitorowania(): string {
135    return this._czyMonitorowany ? "Aktywny" : "Nieaktywny";
136  }
137
138  // Publiczne metody specyficzne dla dinozaurów
139  public nakarm(ilość: number): void {
140    if (ilość <= 0) {
141      throw new Error("Ilość pokarmu musi być większa od zera.");
142    }
143
144    const przyrostWagi = this._dieta === "mięsożerny" ? ilość * 0.3 : ilość * 0.2;
145    this.aktualizujWagę(this.waga + przyrostWagi);
146
147    this._ostatniPosiłek = new Date();
148    this._poziomAgresji = this.walidujPoziomAgresji(this._poziomAgresji - 1);
149
150    console.log(`Dinozaur ${this.id} został nakarmiony. Nowa waga: ${this.waga} kg`);
151  }
152
153  public przydzielDoWybiegu(kodWybiegu: string): void {
154    this._przypisanyWybieg = kodWybiegu;
155    console.log(`Dinozaur ${this.id} został przydzielony do wybiegu ${kodWybiegu}`);
156  }
157
158  public uśpij(): void {
159    this.status = "uśpiony";
160    this._poziomAgresji = 1;
161    console.log(`Dinozaur ${this.id} został uśpiony`);
162  }
163
164  public obudź(): void {
165    if (this.status === "uśpiony") {
166      this.status = "zdrowy";
167      this._poziomAgresji = this.walidujPoziomAgresji(this._poziomAgresji + 2);
168      console.log(`Dinozaur ${this.id} został obudzony`);
169    }
170  }
171
172  // Gettery i settery
173  get dieta(): TypDiety {
174    return this._dieta;
175  }
176
177  get poziomAgresji(): number {
178    return this._poziomAgresji;
179  }
180
181  get ostatniPosiłek(): Date | null {
182    return this._ostatniPosiłek ? new Date(this._ostatniPosiłek.getTime()) : null;
183  }
184
185  get przypisanyWybieg(): string | null {
186    return this._przypisanyWybieg;
187  }
188
189  set poziomAgresji(poziom: number) {
190    this._poziomAgresji = this.walidujPoziomAgresji(poziom);
191  }
192
193  // Prywatna metoda pomocnicza
194  private walidujPoziomAgresji(poziom: number): number {
195    return Math.max(1, Math.min(10, poziom));
196  }
197}
198
199// Użycie systemu
200function testSystemuZarządzaniaDinozaurami() {
201  // Tworzenie dinozaurów
202  const trex = new Dinozaur("T-REX-01", "Tyrannosaurus Rex", 8, 7500, "mięsożerny", 9);
203  const stegozaur = new Dinozaur("STEG-01", "Stegosaurus", 12, 5000, "roślinożerny", 4);
204
205  // Wyświetlanie podstawowych informacji
206  console.log("=== DANE PODSTAWOWE ===");
207  console.log(trex.pobierzPodstawoweDane());
208  console.log(stegozaur.pobierzPodstawoweDane());
209
210  // Operacje na dinozaurach
211  trex.nakarm(50);
212  trex.przydzielDoWybiegu("CARNIVORE-A");
213  trex.rozpocznijMonitorowanie();
214
215  stegozaur.nakarm(80);
216  stegozaur.przydzielDoWybiegu("HERBIVORE-B");
217
218  // Zmiana statusu
219  trex.status = "w leczeniu";
220  stegozaur.uśpij();
221
222  // Pobieranie raportów klinicznych
223  console.log("\n=== RAPORTY KLINICZNE ===");
224  console.log(trex.pobierzOpisKliniczny());
225  console.log(stegozaur.pobierzOpisKliniczny());
226
227  // Status monitorowania
228  console.log("\n=== STATUS MONITOROWANIA ===");
229  console.log(`T-Rex monitoring: ${trex.pobierzStatusMonitorowania()}`);
230  console.log(`Stegozaur monitoring: ${stegozaur.pobierzStatusMonitorowania()}`);
231
232  // Próba dostępu do prywatnych/chronionych elementów
233  // Poniższe linie spowodowałyby błąd kompilacji:
234  // console.log(trex._id);  // Błąd: Property '_id' is private
235  // console.log(trex._gatunek);  // Błąd: Property '_gatunek' is protected
236  // trex.logZmianyStatusu("zdrowy");  // Błąd: Property 'logZmianyStatusu' is private
237  // trex.walidujPoziomAgresji(5);  // Błąd: Property 'walidujPoziomAgresji' is private
238}
239
240// Uruchomienie testu
241testSystemuZarządzaniaDinozaurami();

W powyższym przykładzie:

  1. Abstrakcyjna klasa bazowa (ZwierzęParku) definiuje wspólne właściwości i zachowania dla wszystkich zwierząt w parku
  2. Prywatne właściwości (_id, _dataUtworzenia, _ostatniaAktualizacja) są dostępne tylko wewnątrz klasy
  3. Chronione właściwości (_gatunek, _wiek, _waga, _status) są dostępne w klasie bazowej i klasach dziedziczących
  4. Publiczne gettery i settery zapewniają kontrolowany dostęp do właściwości
  5. Prywatne metody (logZmianyStatusu, walidujPoziomAgresji) są używane jako pomocnicze implementacje dostępne tylko wewnątrz klasy
  6. Chronione metody (aktualizujWagę) są dostępne dla klas dziedziczących
  7. Abstrakcyjna metoda (pobierzOpisKliniczny) wymusza implementację w klasach dziedziczących
  8. Interface (IMonitorowania) definiuje kontrakt, który klasa Dinozaur musi implementować

Kiedy i jak stosować modyfikatory dostępu

Wybór odpowiedniego modyfikatora dostępu zależy od konkretnego przypadku użycia, ale istnieje kilka ogólnych zasad:

  1. Używaj private dla:

    • Wewnętrznych szczegółów implementacji
    • Danych, które nigdy nie powinny być dostępne poza klasą
    • Metod pomocniczych, które nie są częścią publicznego API
    • Właściwości, które muszą być modyfikowane tylko przez kontrolowane settery
  2. Używaj protected dla:

    • Właściwości i metod, które muszą być dostępne w klasach dziedziczących
    • Implementacji, które mogą być rozszerzone lub nadpisane w klasach pochodnych
    • Danych, które są wspólne dla całej hierarchii klas, ale nie powinny być dostępne z zewnątrz
  3. Używaj public dla:

    • Elementów, które tworzą publiczny interfejs API klasy
    • Metod, które muszą być dostępne dla wszystkich użytkowników klasy
    • Właściwości, które mogą być bezpiecznie odczytywane lub modyfikowane z zewnątrz

Podsumowanie

Modyfikatory dostępu w TypeScript są kluczowym narzędziem do implementacji zasad dobrego projektowania obiektowego, takich jak enkapsulacja, dziedziczenie i polimorfizm. Pozwalają na precyzyjną kontrolę dostępu do właściwości i metod klas, co sprzyja tworzeniu bezpiecznego, modularnego i łatwego w utrzymaniu kodu.

Podobnie jak w Parku Jurajskim, gdzie system bezpieczeństwa wymaga różnych poziomów dostępu, dobrze zaprojektowany system obiektowy wykorzystuje modyfikatory dostępu, aby zapewnić, że każdy element jest dostępny tylko dla tych, którzy rzeczywiście powinni mieć do niego dostęp.

John Hammond podsumowałby to krótko: "W Parku Jurajskim i w programowaniu - bezpieczeństwo jest najważniejsze. Modyfikatory dostępu są jak strefy bezpieczeństwa w parku - publiczne dla wszystkich, chronione dla specjalistów, a prywatne tylko dla najwyższego szczebla zarządzania. To klucz do utrzymania porządku i zapobiegania... niespodziankom."

Ir a CodeWorlds