"Nadzór nad poszczególnymi operacjami w Parku Jurajskim jest równie ważny, jak zarządzanie całym parkiem," wyjaśnia dr. Henry Wu, główny genetyk InGen. "Podobnie jak monitorujemy każdy aspekt życia dinozaurów - ich żywienie, zachowanie czy wzrost - tak samo musimy kontrolować i zabezpieczać poszczególne operacje w naszym systemie."
W TypeScripcie, po zrozumieniu dekoratorów klas, naturalnym krokiem jest zagłębienie się w dekoratory metod i właściwości. Te dekoratory pozwalają nam na precyzyjne modyfikowanie zachowania konkretnych członków klasy, podobnie jak w Parku Jurajskim, gdzie każda operacja, od karmienia dinozaurów po zabezpieczenia zagród, wymaga specjalnego nadzoru i protokołów.
Dekoratory metod i właściwości pozwalają na modyfikowanie zachowania, dostępu lub wartości poszczególnych członków klasy. Dzięki nim możemy:
Dekoratory metod są funkcjami, które przyjmują trzy parametry:
target - prototyp klasy (dla metod instancji) lub konstruktor klasy (dla metod statycznych)propertyKey - nazwa metodydescriptor - deskryptor właściwości dla metody1function dekoratorMetody(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
2 // Modyfikacja deskryptora metody
3 // Zwrócenie zmodyfikowanego deskryptora lub undefined
4}
5
6class MojaKlasa {
7 @dekoratorMetody
8 mojaMetoda() {
9 // Implementacja metody
10 }
11}Zacznijmy od prostego dekoratora metody, który będzie logował wykonanie operacji w Parku Jurajskim:
1// Dekorator metody logujący wywołania
2function LogujOperację(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
3 // Zapisujemy oryginalną metodę
4 const oryginalnaMetoda = descriptor.value;
5
6 // Nadpisujemy metodę
7 descriptor.value = function(...args: any[]) {
8 console.log(`[${new Date().toISOString()}] Wywołanie ${propertyKey} z argumentami: ${JSON.stringify(args)}`);
9
10 try {
11 // Wywołujemy oryginalną metodę
12 const wynik = oryginalnaMetoda.apply(this, args);
13
14 // Logujemy wynik
15 console.log(`[${new Date().toISOString()}] ${propertyKey} zakończone. Wynik: ${JSON.stringify(wynik)}`);
16
17 return wynik;
18 } catch (błąd) {
19 // Logujemy błąd
20 console.error(`[${new Date().toISOString()}] ${propertyKey} zakończone błędem: ${błąd}`);
21
22 // Rzucamy błąd dalej
23 throw błąd;
24 }
25 };
26
27 return descriptor;
28}
29
30// Zastosowanie dekoratora w klasie zarządzającej zagrodami
31class ZarządzanieZagrodami {
32 constructor(private nazwaPracownika: string) {}
33
34 @LogujOperację
35 otwórzZagrodę(idZagrody: string): boolean {
36 console.log(`${this.nazwaPracownika} otwiera zagrodę: ${idZagrody}`);
37 // Tutaj logika otwierania zagrody
38 return true;
39 }
40
41 @LogujOperację
42 zamknijZagrodę(idZagrody: string): boolean {
43 console.log(`${this.nazwaPracownika} zamyka zagrodę: ${idZagrody}`);
44 // Tutaj logika zamykania zagrody
45 return true;
46 }
47
48 sprawdźStan(idZagrody: string): string {
49 // Ta metoda nie jest logowana
50 return "Zagroda zamknięta";
51 }
52}
53
54// Używamy klasy z dekorowanymi metodami
55const zarządca = new ZarządzanieZagrodami("John");
56zarządca.otwórzZagrodę("A-001");
57zarządca.zamknijZagrodę("A-001");
58zarządca.sprawdźStan("A-001"); // Ta operacja nie będzie logowanaW powyższym przykładzie, dekorator
LogujOperację rejestruje każde wywołanie metody, jej argumenty oraz wynik lub błąd. Metody otwórzZagrodę i zamknijZagrodę są logowane, podczas gdy metoda sprawdźStan nie ma dekoratora, więc nie jest logowana.Możemy również tworzyć dekoratory metod, które przyjmują parametry:
1// Dekorator metody z parametrami
2function WymagaPoziomDostępu(poziom: number) {
3 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
4 // Zapisujemy oryginalną metodę
5 const oryginalnaMetoda = descriptor.value;
6
7 // Nadpisujemy metodę
8 descriptor.value = function(...args: any[]) {
9 // Sprawdzamy, czy obiekt ma właściwość poziomDostępu
10 if (!(this as any).poziomDostępu) {
11 throw new Error("Obiekt nie posiada zdefiniowanego poziomu dostępu!");
12 }
13
14 // Weryfikujemy poziom dostępu
15 if ((this as any).poziomDostępu < poziom) {
16 throw new Error(`Wymagany poziom dostępu: ${poziom}, dostępny: ${(this as any).poziomDostępu}`);
17 }
18
19 // Wywołujemy oryginalną metodę
20 return oryginalnaMetoda.apply(this, args);
21 };
22
23 return descriptor;
24 };
25}
26
27// Klasa reprezentująca laboratorium
28class LaboratoriumGenetyczne {
29 constructor(public nazwaPracownika: string, public poziomDostępu: number) {}
30
31 @WymagaPoziomDostępu(1)
32 odczytajDaneDNA(idDinozaura: string): string {
33 return `DNA dinozaura ${idDinozaura}: ACGT...`;
34 }
35
36 @WymagaPoziomDostępu(2)
37 modyfikujDNA(idDinozaura: string, sekwencja: string): boolean {
38 console.log(`Modyfikacja DNA dinozaura ${idDinozaura}: ${sekwencja}`);
39 return true;
40 }
41
42 @WymagaPoziomDostępu(3)
43 stwórzNowyGatunek(nazwa: string, bazoweDNA: string): string {
44 const nowyGatunekId = `NGT-${Math.floor(Math.random() * 1000)}`;
45 console.log(`Tworzenie nowego gatunku: ${nazwa} (ID: ${nowyGatunekId})`);
46 return nowyGatunekId;
47 }
48}
49
50// Używamy klasy z różnymi poziomami dostępu
51const stażysta = new LaboratoriumGenetyczne("Tim", 1);
52const naukowiec = new LaboratoriumGenetyczne("Dr. Grant", 2);
53const dyrektor = new LaboratoriumGenetyczne("Dr. Wu", 3);
54
55// Testujemy dostęp
56try {
57 console.log(stażysta.odczytajDaneDNA("D-001")); // Zadziała
58 stażysta.modyfikujDNA("D-001", "ACGT"); // Wyrzuci błąd
59} catch (błąd) {
60 console.error(błąd.message);
61}
62
63try {
64 naukowiec.modyfikujDNA("D-001", "ACGT"); // Zadziała
65 naukowiec.stwórzNowyGatunek("Neo-Raptor", "ACGT..."); // Wyrzuci błąd
66} catch (błąd) {
67 console.error(błąd.message);
68}
69
70// Dyrektor ma pełny dostęp
71dyrektor.stwórzNowyGatunek("Indominus Rex", "ACGT...");W tym przykładzie, dekorator
WymagaPoziomDostępu sprawdza, czy osoba wywołująca metodę ma wystarczający poziom dostępu. Różne metody wymagają różnych poziomów dostępu, co odzwierciedla rzeczywiste protokoły bezpieczeństwa, jakie mogłyby istnieć w Parku Jurajskim.Dekoratory właściwości są podobne do dekoratorów metod, ale otrzymują tylko dwa parametry:
target - prototyp klasy (dla właściwości instancji) lub konstruktor klasy (dla właściwości statycznych)propertyKey - nazwa właściwości1function dekoratorWłaściwości(target: any, propertyKey: string) {
2 // Implementacja dekoratora
3}
4
5class MojaKlasa {
6 @dekoratorWłaściwości
7 mojaWłaściwość: string;
8}Ponieważ dekoratory właściwości nie otrzymują deskryptora, często są używane do generowania getterów i setterów lub do stosowania metadanych.
Zaimplementujmy dekorator właściwości, który będzie monitorował zmiany zasobów w Parku Jurajskim:
1// Dekorator właściwości monitorujący zmiany
2function MonitorujZmiany(target: any, propertyKey: string) {
3 // Tworzymy unikalną nazwę dla prywatnej właściwości
4 const prywatnaNazwa = Symbol(propertyKey);
5
6 // Definiujemy getter i setter
7 Object.defineProperty(target, propertyKey, {
8 get: function() {
9 // Zwracamy wartość prywatnej właściwości
10 return this[prywatnaNazwa];
11 },
12 set: function(wartość: any) {
13 // Zapisujemy starą wartość
14 const staraWartość = this[prywatnaNazwa];
15
16 // Logujemy zmianę, jeśli nie jest to pierwsze przypisanie
17 if (this[prywatnaNazwa] !== undefined) {
18 console.log(`[ZMIANA][${this.constructor.name}] ${propertyKey}: ${staraWartość} -> ${wartość}`);
19 }
20
21 // Zapisujemy nową wartość
22 this[prywatnaNazwa] = wartość;
23 },
24 enumerable: true,
25 configurable: true
26 });
27}
28
29// Klasa monitorująca zasoby parku
30class ZasobyParku {
31 @MonitorujZmiany
32 ilośćPożywienia: number = 1000;
33
34 @MonitorujZmiany
35 stanGeneratorów: "Aktywne" | "Awaryjne" | "Wyłączone" = "Aktywne";
36
37 @MonitorujZmiany
38 statusBramy: "Otwarta" | "Zamknięta" = "Zamknięta";
39
40 constructor(public nazwaSekcji: string) {}
41
42 użyjPożywienia(ilość: number): boolean {
43 if (this.ilośćPożywienia >= ilość) {
44 this.ilośćPożywienia -= ilość;
45 return true;
46 }
47 return false;
48 }
49
50 uzupełnijPożywienie(ilość: number): void {
51 this.ilośćPożywienia += ilość;
52 }
53
54 przełączGeneratory(stan: "Aktywne" | "Awaryjne" | "Wyłączone"): void {
55 this.stanGeneratorów = stan;
56 }
57
58 przełączBramę(status: "Otwarta" | "Zamknięta"): void {
59 this.statusBramy = status;
60 }
61}
62
63// Używamy klasy z monitorowanymi właściwościami
64const sekcjaA = new ZasobyParku("Sekcja A");
65
66// Testujemy zmiany właściwości
67sekcjaA.użyjPożywienia(200); // ilośćPożywienia: 1000 -> 800
68sekcjaA.uzupełnijPożywienie(500); // ilośćPożywienia: 800 -> 1300
69sekcjaA.przełączGeneratory("Awaryjne"); // stanGeneratorów: Aktywne -> Awaryjne
70sekcjaA.przełączBramę("Otwarta"); // statusBramy: Zamknięta -> OtwartaW tym przykładzie, dekorator
MonitorujZmiany tworzy getter i setter dla właściwości, które logują każdą zmianę wartości. Dzięki temu, możemy śledzić, kiedy i jakie zmiany zostały wprowadzone, co jest kluczowe dla monitorowania zasobów w parku.Możemy łączyć różne dekoratory metod i właściwości, aby stworzyć bardziej zaawansowane zachowania:
1// Dekorator mierzący czas wykonania metody
2function MierzCzasWykonania(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
3 const oryginalnaMetoda = descriptor.value;
4
5 descriptor.value = function(...args: any[]) {
6 const start = performance.now();
7 const wynik = oryginalnaMetoda.apply(this, args);
8 const koniec = performance.now();
9
10 console.log(`[CZAS] ${propertyKey}: ${(koniec - start).toFixed(2)}ms`);
11
12 return wynik;
13 };
14
15 return descriptor;
16}
17
18// Dekorator walidujący parametry metody
19function WalidujParametry(walidator: (args: any[]) => boolean, komunikatBłędu: string) {
20 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
21 const oryginalnaMetoda = descriptor.value;
22
23 descriptor.value = function(...args: any[]) {
24 if (!walidator(args)) {
25 throw new Error(`[BŁĄD WALIDACJI] ${propertyKey}: ${komunikatBłędu}`);
26 }
27
28 return oryginalnaMetoda.apply(this, args);
29 };
30
31 return descriptor;
32 };
33}
34
35// Dekorator cachujący wyniki metody
36function Cachuj(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
37 const oryginalnaMetoda = descriptor.value;
38 const cache = new Map<string, any>();
39
40 descriptor.value = function(...args: any[]) {
41 const klucz = JSON.stringify(args);
42
43 if (cache.has(klucz)) {
44 console.log(`[CACHE] ${propertyKey}: Użyto zapisanego wyniku dla ${klucz}`);
45 return cache.get(klucz);
46 }
47
48 const wynik = oryginalnaMetoda.apply(this, args);
49 cache.set(klucz, wynik);
50
51 return wynik;
52 };
53
54 return descriptor;
55}
56
57// Dekorator dodający zakres wartości dla właściwości numerycznej
58function Zakres(min: number, max: number) {
59 return function(target: any, propertyKey: string) {
60 // Tworzymy unikalną nazwę dla prywatnej właściwości
61 const prywatnaNazwa = Symbol(propertyKey);
62
63 // Definiujemy getter i setter
64 Object.defineProperty(target, propertyKey, {
65 get: function() {
66 return this[prywatnaNazwa];
67 },
68 set: function(wartość: number) {
69 if (wartość < min) {
70 console.warn(`[ZAKRES] ${propertyKey}: Wartość ${wartość} poniżej minimum ${min}. Ustawiam ${min}.`);
71 this[prywatnaNazwa] = min;
72 } else if (wartość > max) {
73 console.warn(`[ZAKRES] ${propertyKey}: Wartość ${wartość} powyżej maximum ${max}. Ustawiam ${max}.`);
74 this[prywatnaNazwa] = max;
75 } else {
76 this[prywatnaNazwa] = wartość;
77 }
78 },
79 enumerable: true,
80 configurable: true
81 });
82 };
83}
84
85// Klasa zarządzająca systemem ogrodzenia elektrycznego
86class SystemOgrodzeniaElektrycznego {
87 @Zakres(0, 10000)
88 napięcie: number = 5000; // w woltach
89
90 @Zakres(0, 100)
91 zasięg: number = 50; // w metrach
92
93 private statusAktywny: boolean = false;
94
95 @LogujOperację
96 @MierzCzasWykonania
97 @WalidujParametry(
98 args => args[0] >= 0 && args[0] <= 10000,
99 "Nieprawidłowe napięcie. Dozwolony zakres: 0-10000V"
100 )
101 ustawNapięcie(noweNapięcie: number): boolean {
102 // Symulacja długiej operacji dla testowania dekoratora MierzCzasWykonania
103 for (let i = 0; i < 1000000; i++) {
104 // Obciążamy procesor
105 }
106
107 this.napięcie = noweNapięcie;
108 return true;
109 }
110
111 @LogujOperację
112 @Cachuj
113 pobierzStatusSystemu(idSekcji: string): { aktywny: boolean; napięcie: number; zasięg: number } {
114 console.log(`Pobieranie statusu systemu dla sekcji ${idSekcji}...`);
115
116 // Symulacja długiej operacji
117 for (let i = 0; i < 500000; i++) {
118 // Obciążamy procesor
119 }
120
121 return {
122 aktywny: this.statusAktywny,
123 napięcie: this.napięcie,
124 zasięg: this.zasięg
125 };
126 }
127
128 @LogujOperację
129 @MierzCzasWykonania
130 aktywujSystem(): boolean {
131 if (this.statusAktywny) {
132 console.log("System już jest aktywny");
133 return false;
134 }
135
136 console.log(`Aktywacja systemu ogrodzenia elektrycznego. Napięcie: ${this.napięcie}V, Zasięg: ${this.zasięg}m`);
137 this.statusAktywny = true;
138
139 return true;
140 }
141
142 @LogujOperację
143 @MierzCzasWykonania
144 deaktywujSystem(): boolean {
145 if (!this.statusAktywny) {
146 console.log("System już jest nieaktywny");
147 return false;
148 }
149
150 console.log("Deaktywacja systemu ogrodzenia elektrycznego");
151 this.statusAktywny = false;
152
153 return true;
154 }
155}
156
157// Używamy klasy z wieloma dekoratorami
158const systemOgrodzenia = new SystemOgrodzeniaElektrycznego();
159
160// Testujemy różne funkcjonalności
161systemOgrodzenia.ustawNapięcie(7500); // Loguje operację, mierzy czas, waliduje parametr
162systemOgrodzenia.ustawNapięcie(12000); // Wyjdzie poza zakres, zostanie ustawione 10000
163systemOgrodzenia.aktywujSystem(); // Loguje operację, mierzy czas
164
165// Testujemy cachowanie
166console.log(systemOgrodzenia.pobierzStatusSystemu("A1")); // Wykona pełną operację
167console.log(systemOgrodzenia.pobierzStatusSystemu("A1")); // Użyje cache
168console.log(systemOgrodzenia.pobierzStatusSystemu("B2")); // Wykona pełną operację
169console.log(systemOgrodzenia.pobierzStatusSystemu("B2")); // Użyje cache
170
171// Deaktywujemy system
172systemOgrodzenia.deaktywujSystem();W tym przykładzie używamy wielu dekoratorów do stworzenia zaawansowanego systemu ogrodzenia elektrycznego:
@Zakres - zapewnia, że wartości właściwości pozostają w określonym zakresie@LogujOperację - loguje wywołania metod@MierzCzasWykonania - mierzy czas wykonania metod@WalidujParametry - sprawdza poprawność parametrów metod@Cachuj - zapisuje wyniki metod w pamięci podręcznejTypeScript umożliwia również używanie metadanych z dekoratorami, co pozwala na bardziej zaawansowane przypadki użycia. Wymaga to włączenia opcji
emitDecoratorMetadata i experimentalDecorators w pliku tsconfig.json oraz zainstalowania pakietu reflect-metadata.Oto przykład użycia metadanych z dekoratorami:
1// Potrzebujemy importować reflect-metadata na początku aplikacji
2import "reflect-metadata";
3
4// Klucze dla metadanych
5const PARAMETRY_KLUCZ = Symbol("parametry");
6
7// Dekorator parametru
8function Parametr(target: any, propertyKey: string, parameterIndex: number) {
9 // Pobierz istniejące metadane lub stwórz puste
10 const parametry: number[] = Reflect.getOwnMetadata(PARAMETRY_KLUCZ, target, propertyKey) || [];
11
12 // Dodaj indeks parametru do listy
13 parametry.push(parameterIndex);
14
15 // Zapisz metadane
16 Reflect.defineMetadata(PARAMETRY_KLUCZ, parametry, target, propertyKey);
17}
18
19// Dekorator metody walidujący parametry
20function WalidujDane(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
21 const oryginalnaMetoda = descriptor.value;
22
23 descriptor.value = function(...args: any[]) {
24 // Pobierz typy parametrów (wymaga opcji emitDecoratorMetadata)
25 const typy = Reflect.getMetadata("design:paramtypes", target, propertyKey);
26
27 // Pobierz indeksy parametrów oznaczonych dekoratorem @Parametr
28 const oznaczenieParametry: number[] = Reflect.getOwnMetadata(PARAMETRY_KLUCZ, target, propertyKey) || [];
29
30 // Waliduj parametry
31 for (const indeks of oznaczenieParametry) {
32 const wartość = args[indeks];
33 const typ = typy[indeks];
34
35 // Walidacja typu
36 if (wartość === null || wartość === undefined) {
37 throw new Error(`Parametr #${indeks} metody ${propertyKey} nie może być null lub undefined`);
38 }
39
40 // Walidacja dla różnych typów
41 if (typ === String && typeof wartość !== "string") {
42 throw new Error(`Parametr #${indeks} metody ${propertyKey} musi być stringiem`);
43 } else if (typ === Number && typeof wartość !== "number") {
44 throw new Error(`Parametr #${indeks} metody ${propertyKey} musi być liczbą`);
45 } else if (typ === Boolean && typeof wartość !== "boolean") {
46 throw new Error(`Parametr #${indeks} metody ${propertyKey} musi być wartością logiczną`);
47 } else if (typ === Date && !(wartość instanceof Date)) {
48 throw new Error(`Parametr #${indeks} metody ${propertyKey} musi być datą`);
49 }
50 }
51
52 // Wywołaj oryginalną metodę
53 return oryginalnaMetoda.apply(this, args);
54 };
55
56 return descriptor;
57}
58
59// Klasa operacji na dinozaurach z metadanymi
60class OperacjeDinozaurów {
61 @WalidujDane
62 przeprowadźBadanieZdrowia(
63 idDinozaura: string,
64 @Parametr dataBadania: Date,
65 @Parametr temperatura: number,
66 @Parametr waga: number,
67 notatki?: string
68 ): string {
69 console.log(`Badanie dinozaura ${idDinozaura}`);
70 console.log(`Data: ${dataBadania.toLocaleDateString()}`);
71 console.log(`Temperatura: ${temperatura}°C`);
72 console.log(`Waga: ${waga}kg`);
73
74 if (notatki) {
75 console.log(`Notatki: ${notatki}`);
76 }
77
78 return "Badanie zakończone";
79 }
80
81 @WalidujDane
82 przeprowadźSzczepienie(
83 @Parametr idDinozaura: string,
84 @Parametr typSzczepienia: string,
85 @Parametr dataSzczepienia: Date
86 ): boolean {
87 console.log(`Szczepienie dinozaura ${idDinozaura}`);
88 console.log(`Typ szczepienia: ${typSzczepienia}`);
89 console.log(`Data: ${dataSzczepienia.toLocaleDateString()}`);
90
91 return true;
92 }
93}
94
95// Używamy klasy z walidacją parametrów
96const operacje = new OperacjeDinozaurów();
97
98try {
99 // Poprawne wywołanie
100 operacje.przeprowadźBadanieZdrowia(
101 "D-001",
102 new Date(),
103 38.5,
104 1200,
105 "Dinozaur w dobrym stanie"
106 );
107
108 // Niepoprawne wywołanie - temperatura nie jest liczbą
109 operacje.przeprowadźBadanieZdrowia(
110 "D-002",
111 new Date(),
112 "wysoka" as any, // To spowoduje błąd
113 1500
114 );
115} catch (błąd) {
116 console.error(błąd.message);
117}
118
119try {
120 // Poprawne wywołanie
121 operacje.przeprowadźSzczepienie(
122 "D-003",
123 "Szczepionka przeciw pasożytom",
124 new Date()
125 );
126
127 // Niepoprawne wywołanie - data nie jest obiektem Date
128 operacje.przeprowadźSzczepienie(
129 "D-004",
130 "Szczepionka przeciw grypie",
131 "dzisiaj" as any // To spowoduje błąd
132 );
133} catch (błąd) {
134 console.error(błąd.message);
135}W tym przykładzie używamy metadanych i refleksji do walidacji parametrów metod. Dekorator
@Parametr oznacza parametry, które mają być walidowane, a dekorator @WalidujDane sprawdza typy tych parametrów przy każdym wywołaniu metody.Teraz połączmy wszystkie omówione techniki w jednym większym przykładzie - systemie zarządzania badaniami dinozaurów:
1// Dekorator właściwości zapewniający unikalność ID
2function UnikalneID(prefiks: string = "ID") {
3 return function(target: any, propertyKey: string) {
4 const generujID = () => `${prefiks}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
5
6 Object.defineProperty(target, propertyKey, {
7 get: function() {
8 // Jeśli właściwość nie została jeszcze ustawiona, generujemy ID
9 if (!this[`_${propertyKey}`]) {
10 this[`_${propertyKey}`] = generujID();
11 }
12 return this[`_${propertyKey}`];
13 },
14 set: function(wartość: string) {
15 // Pozwalamy na ustawienie tylko raz
16 if (!this[`_${propertyKey}`]) {
17 this[`_${propertyKey}`] = wartość;
18 } else {
19 console.warn(`Właściwość ${propertyKey} już została ustawiona i nie może być zmieniona.`);
20 }
21 },
22 enumerable: true,
23 configurable: true
24 });
25 };
26}
27
28// Dekorator metody do walidacji daty
29function WalidujDataBadania(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
30 const oryginalnaMetoda = descriptor.value;
31
32 descriptor.value = function(...args: any[]) {
33 // Zakładamy, że pierwszy argument to data badania
34 const dataBadania = args[0];
35
36 if (!(dataBadania instanceof Date)) {
37 throw new Error("Pierwszy parametr musi być instancją Date");
38 }
39
40 const teraz = new Date();
41
42 if (dataBadania > teraz) {
43 throw new Error("Data badania nie może być z przyszłości");
44 }
45
46 // Sprawdzamy, czy data nie jest starsza niż 30 dni
47 const trzydzieściDniWstecz = new Date();
48 trzydzieściDniWstecz.setDate(trzydzieściDniWstecz.getDate() - 30);
49
50 if (dataBadania < trzydzieściDniWstecz) {
51 console.warn("Uwaga: Data badania jest starsza niż 30 dni");
52 }
53
54 return oryginalnaMetoda.apply(this, args);
55 };
56
57 return descriptor;
58}
59
60// Dekorator metody do kontroli częstotliwości badań
61function KontrolujCzęstotliwość(minCzasMiędzySzczepieniamiDni: number) {
62 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
63 // Mapa przechowująca daty ostatnich szczepień dla każdego dinozaura
64 const ostatnieSzczepienia = new Map<string, Date>();
65
66 const oryginalnaMetoda = descriptor.value;
67
68 descriptor.value = function(...args: any[]) {
69 // Zakładamy, że pierwszy argument to ID dinozaura, a drugi to data szczepienia
70 const idDinozaura = args[0];
71 const dataSzczepienia = args[1];
72
73 if (ostatnieSzczepienia.has(idDinozaura)) {
74 const ostatniaSzczepionka = ostatnieSzczepienia.get(idDinozaura)!;
75 const różnicaDni = Math.floor((dataSzczepienia.getTime() - ostatniaSzczepionka.getTime()) / (1000 * 60 * 60 * 24));
76
77 if (różnicaDni < minCzasMiędzySzczepieniamiDni) {
78 throw new Error(`Za wcześnie na kolejne szczepienie. Minimalny odstęp to ${minCzasMiędzySzczepieniamiDni} dni. Pozostało ${minCzasMiędzySzczepieniamiDni - różnicaDni} dni.`);
79 }
80 }
81
82 // Zapisujemy datę szczepienia
83 ostatnieSzczepienia.set(idDinozaura, dataSzczepienia);
84
85 return oryginalnaMetoda.apply(this, args);
86 };
87
88 return descriptor;
89 };
90}
91
92// Interfejs wyników badania
93interface WynikiBadania {
94 temperatura: number; // w stopniach Celsjusza
95 waga: number; // w kilogramach
96 długość: number; // w metrach
97 stanZdrowia: "Zdrowy" | "Lekkie objawy" | "Chory" | "Krytyczny";
98 uwagi?: string;
99}
100
101// Klasa badania dinozaura
102class BadanieDinozaura {
103 @UnikalneID("BAD")
104 idBadania: string;
105
106 constructor(
107 public idDinozaura: string,
108 public dataBadania: Date,
109 public wyniki: WynikiBadania,
110 public przeprowadzającyBadanie: string
111 ) {}
112
113 @LogujOperację
114 pobierzRaport(): string {
115 return `
116 === RAPORT BADANIA ===
117 ID Badania: ${this.idBadania}
118 ID Dinozaura: ${this.idDinozaura}
119 Data: ${this.dataBadania.toLocaleString()}
120 Przeprowadzający: ${this.przeprowadzającyBadanie}
121
122 WYNIKI:
123 - Temperatura: ${this.wyniki.temperatura}°C
124 - Waga: ${this.wyniki.waga} kg
125 - Długość: ${this.wyniki.długość} m
126 - Stan zdrowia: ${this.wyniki.stanZdrowia}
127 ${this.wyniki.uwagi ? `- Uwagi: ${this.wyniki.uwagi}` : ""}
128 `;
129 }
130}
131
132// Klasa szczepienia dinozaura
133class SzczepienieDinozaura {
134 @UnikalneID("SZCZ")
135 idSzczepienia: string;
136
137 constructor(
138 public idDinozaura: string,
139 public dataSzczepienia: Date,
140 public rodzajSzczepionki: string,
141 public dawka: number,
142 public przeprowadzającySzczepienie: string
143 ) {}
144
145 @LogujOperację
146 pobierzRaport(): string {
147 return `
148 === RAPORT SZCZEPIENIA ===
149 ID Szczepienia: ${this.idSzczepienia}
150 ID Dinozaura: ${this.idDinozaura}
151 Data: ${this.dataSzczepienia.toLocaleString()}
152 Rodzaj szczepionki: ${this.rodzajSzczepionki}
153 Dawka: ${this.dawka} ml
154 Przeprowadzający: ${this.przeprowadzającySzczepienie}
155 `;
156 }
157}
158
159// Rejestr badań i szczepień
160class RejestrMedyczny {
161 private badania: BadanieDinozaura[] = [];
162 private szczepienia: SzczepienieDinozaura[] = [];
163
164 @LogujOperację
165 @WalidujDataBadania
166 zarejestrujBadanie(
167 dataBadania: Date,
168 idDinozaura: string,
169 temperatura: number,
170 waga: number,
171 długość: number,
172 stanZdrowia: "Zdrowy" | "Lekkie objawy" | "Chory" | "Krytyczny",
173 uwagi?: string,
174 przeprowadzającyBadanie: string = "Dr. Harding"
175 ): BadanieDinozaura {
176 const wyniki: WynikiBadania = {
177 temperatura,
178 waga,
179 długość,
180 stanZdrowia,
181 uwagi
182 };
183
184 const badanie = new BadanieDinozaura(idDinozaura, dataBadania, wyniki, przeprowadzającyBadanie);
185 this.badania.push(badanie);
186
187 console.log(`Zarejestrowano badanie: ${badanie.idBadania} dla dinozaura ${idDinozaura}`);
188
189 return badanie;
190 }
191
192 @LogujOperację
193 @KontrolujCzęstotliwość(90) // Minimum 90 dni między szczepieniami
194 zarejestrujSzczepienie(
195 idDinozaura: string,
196 dataSzczepienia: Date,
197 rodzajSzczepionki: string,
198 dawka: number,
199 przeprowadzającySzczepienie: string = "Dr. Harding"
200 ): SzczepienieDinozaura {
201 const szczepienie = new SzczepienieDinozaura(
202 idDinozaura,
203 dataSzczepienia,
204 rodzajSzczepionki,
205 dawka,
206 przeprowadzającySzczepienie
207 );
208
209 this.szczepienia.push(szczepienie);
210
211 console.log(`Zarejestrowano szczepienie: ${szczepienie.idSzczepienia} dla dinozaura ${idDinozaura}`);
212
213 return szczepienie;
214 }
215
216 @Cachuj
217 @LogujOperację
218 pobierzHistorięBadań(idDinozaura: string): BadanieDinozaura[] {
219 console.log(`Pobieranie historii badań dla dinozaura ${idDinozaura}...`);
220
221 // Symulacja długiej operacji
222 for (let i = 0; i < 500000; i++) {
223 // Obciążamy procesor
224 }
225
226 return this.badania.filter(badanie => badanie.idDinozaura === idDinozaura)
227 .sort((a, b) => b.dataBadania.getTime() - a.dataBadania.getTime());
228 }
229
230 @Cachuj
231 @LogujOperację
232 pobierzHistorięSzczepień(idDinozaura: string): SzczepienieDinozaura[] {
233 console.log(`Pobieranie historii szczepień dla dinozaura ${idDinozaura}...`);
234
235 // Symulacja długiej operacji
236 for (let i = 0; i < 500000; i++) {
237 // Obciążamy procesor
238 }
239
240 return this.szczepienia.filter(szczepienie => szczepienie.idDinozaura === idDinozaura)
241 .sort((a, b) => b.dataSzczepienia.getTime() - a.dataSzczepienia.getTime());
242 }
243
244 @MierzCzasWykonania
245 @LogujOperację
246 generujRaportMedyczny(idDinozaura: string): string {
247 const badania = this.pobierzHistorięBadań(idDinozaura);
248 const szczepienia = this.pobierzHistorięSzczepień(idDinozaura);
249
250 let raport = `=== RAPORT MEDYCZNY DINOZAURA ${idDinozaura} ===
251
252`;
253
254 raport += `Liczba badań: ${badania.length}
255`;
256 raport += `Liczba szczepień: ${szczepienia.length}
257
258`;
259
260 if (badania.length > 0) {
261 const ostatnieBadanie = badania[0];
262 raport += `OSTATNIE BADANIE:
263`;
264 raport += `Data: ${ostatnieBadanie.dataBadania.toLocaleString()}
265`;
266 raport += `Stan zdrowia: ${ostatnieBadanie.wyniki.stanZdrowia}
267`;
268 raport += `Temperatura: ${ostatnieBadanie.wyniki.temperatura}°C
269`;
270 raport += `Waga: ${ostatnieBadanie.wyniki.waga} kg
271`;
272 raport += `Długość: ${ostatnieBadanie.wyniki.długość} m
273`;
274 if (ostatnieBadanie.wyniki.uwagi) {
275 raport += `Uwagi: ${ostatnieBadanie.wyniki.uwagi}
276`;
277 }
278 raport += "
279";
280 }
281
282 if (szczepienia.length > 0) {
283 const ostatnieSzczepienie = szczepienia[0];
284 raport += `OSTATNIE SZCZEPIENIE:
285`;
286 raport += `Data: ${ostatnieSzczepienie.dataSzczepienia.toLocaleString()}
287`;
288 raport += `Rodzaj szczepionki: ${ostatnieSzczepienie.rodzajSzczepionki}
289`;
290 raport += `Dawka: ${ostatnieSzczepienie.dawka} ml
291
292`;
293 }
294
295 raport += `HISTORIA BADAŃ:
296`;
297 badania.forEach((badanie, indeks) => {
298 raport += `${indeks + 1}. ${badanie.dataBadania.toLocaleDateString()} - ${badanie.wyniki.stanZdrowia}
299`;
300 });
301
302 raport += `
303HISTORIA SZCZEPIEŃ:
304`;
305 szczepienia.forEach((szczepienie, indeks) => {
306 raport += `${indeks + 1}. ${szczepienie.dataSzczepienia.toLocaleDateString()} - ${szczepienie.rodzajSzczepionki}
307`;
308 });
309
310 return raport;
311 }
312}
313
314// Testujemy system
315const rejestrMedyczny = new RejestrMedyczny();
316
317// Rejestrujemy badania i szczepienia
318try {
319 // Badanie z przeszłości - OK
320 const badanie1 = rejestrMedyczny.zarejestrujBadanie(
321 new Date("2023-09-01"),
322 "TREX-001",
323 38.2,
324 7500,
325 12.5,
326 "Zdrowy",
327 "Badanie rutynowe",
328 "Dr. Harding"
329 );
330
331 // Badanie z przyszłości - Błąd
332 const badanie2 = rejestrMedyczny.zarejestrujBadanie(
333 new Date("2025-01-01"), // przyszłość
334 "TREX-001",
335 39.1,
336 7450,
337 12.5,
338 "Lekkie objawy",
339 "Lekka gorączka"
340 );
341} catch (błąd) {
342 console.error(błąd.message);
343}
344
345// Rejestrujemy szczepienia
346try {
347 // Pierwsze szczepienie
348 const szczepienie1 = rejestrMedyczny.zarejestrujSzczepienie(
349 "TREX-001",
350 new Date("2023-01-15"),
351 "Szczepionka przeciw grypie dinozaurów",
352 50,
353 "Dr. Sattler"
354 );
355
356 // Drugie szczepienie - za wcześnie, powinien być błąd
357 const szczepienie2 = rejestrMedyczny.zarejestrujSzczepienie(
358 "TREX-001",
359 new Date("2023-03-15"), // Tylko 60 dni później, a minimum to 90 dni
360 "Szczepionka przeciw pasożytom",
361 30
362 );
363} catch (błąd) {
364 console.error(błąd.message);
365}
366
367// Testujemy pobieranie danych
368console.log(rejestrMedyczny.pobierzHistorięBadań("TREX-001")); // Wykonuje pełne pobieranie
369console.log(rejestrMedyczny.pobierzHistorięBadań("TREX-001")); // Użyje cache
370
371// Generujemy raport medyczny
372console.log(rejestrMedyczny.generujRaportMedyczny("TREX-001"));W tym rozbudowanym przykładzie stworzyliśmy kompleksowy system zarządzania badaniami i szczepieniami dinozaurów w Parku Jurajskim, wykorzystując różne dekoratory:
@UnikalneID - zapewnia, że każde badanie i każde szczepienie ma unikalne ID@LogujOperację - loguje wywołania metod@WalidujDataBadania - sprawdza poprawność daty badania@KontrolujCzęstotliwość - zapewnia odpowiedni odstęp między szczepieniami@Cachuj - cachuje wyniki długotrwałych operacji@MierzCzasWykonania - mierzy czas generowania raportówSystem ten pokazuje, jak dekoratory metod i właściwości mogą być wykorzystane do tworzenia zaawansowanych funkcjonalności biznesowych, zapewniając jednocześnie spójność kodu, bezpieczeństwo danych i wydajność.
Dekoratory metod i właściwości są szczególnie przydatne:
Podobnie jak dekoratory klas, dekoratory metod i właściwości mają swoje ograniczenia i problemy:
as any)Dekoratory metod i właściwości w TypeScripcie to potężne narzędzia, które pozwalają na dodanie dodatkowej funkcjonalności do metod i właściwości klas w sposób deklaratywny. Podobnie jak protokoły operacyjne w Parku Jurajskim pomagają kontrolować poszczególne działania, dekoratory metod i właściwości pomagają kontrolować i rozszerzać zachowanie poszczególnych elementów klas.
Dzięki dekoratorom, możemy tworzyć bardziej modularne, czytelne i łatwiejsze w utrzymaniu aplikacje, izolując funkcjonalności przekrojowe takie jak logowanie, walidacja czy zabezpieczenia. W dużych projektach, dobrze zaprojektowane dekoratory mogą znacząco poprawić jakość kodu i zmniejszyć jego redundancję.
Jednakże, jak każde zaawansowane narzędzie, dekoratory metod i właściwości powinny być używane z rozwagą. Nadmierne wykorzystanie dekoratorów może prowadzić do kodu, który jest trudny do zrozumienia, debugowania i testowania. Najlepszą praktyką jest używanie ich tam, gdzie rzeczywiście przynoszą korzyści, a nie tylko dla samego faktu ich użycia.