"Standaryzacja i wielokrotne użycie to klucz do sukcesu w parku," podkreśla Simon Masrani, nowy właściciel Parku Jurajskiego. "Potrzebujemy systemów, które można łatwo dostosować do różnych gatunków dinozaurów, różnych części parku i różnych poziomów bezpieczeństwa, ale wszystko zgodnie z jednolitymi standardami."
W świecie TypeScripta, podobnie jak w zarządzaniu Parkiem Jurajskim, często potrzebujemy tworzyć podobne, ale nieco różniące się dekoratory dla różnych przypadków użycia. Fabryki dekoratorów pozwalają nam tworzyć wysoce konfigurowalne dekoratory, które możemy dostosować do konkretnych potrzeb, zachowując jednocześnie spójną strukturę i logikę.
Fabryka dekoratorów to po prostu funkcja, która zwraca dekorator, przyjmując parametry konfiguracyjne. Pozwala to na tworzenie dekoratorów "na żądanie" z różnymi wariantami zachowania, bazując na tym samym szablonie.
Główne zalety fabryk dekoratorów:
Fabryka dekoratorów ma zazwyczaj następującą strukturę:
1// Fabryka dekoratorów klasy
2function fabrykaDekoratoraKlasy(opcje: OpcjeFabryki) {
3 // Zwraca dekorator klasy
4 return function<T extends new (...args: any[]) => any>(target: T) {
5 // Implementacja dekoratora użyłwająca opcji
6 //
7 return target; // lub zmodyfikowana klasa
8 };
9}
10
11// Fabryka dekoratorów metod
12function fabrykaDekoratoraMetody(opcje: OpcjeFabryki) {
13 // Zwraca dekorator metody
14 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
15 // Implementacja dekoratora używająca opcji
16 //
17 return descriptor;
18 };
19}
20
21// Fabryka dekoratorów właściwości
22function fabrykaDekoratoraWłaściwości(opcje: OpcjeFabryki) {
23 // Zwraca dekorator właściwości
24 return function(target: any, propertyKey: string) {
25 // Implementacja dekoratora używająca opcji
26 //
27 };
28}Zobaczmy, jak możemy wykorzystać fabryki dekoratorów w kontekście zarządzania Parkiem Jurajskim.
1// Typy podsystemów parku
2type PodsystemParku = "Bezpieczeństwo" | "Zagrody" | "Laboratorium" | "Transport" | "Zasilanie";
3
4// Poziomy logowania
5type PoziomLogowania = "Debug" | "Info" | "Ostrzeżenie" | "Błąd" | "Krytyczny";
6
7// Interfejs opcji dla fabryki dekoratorów logowania
8interface OpcjeLogowania {
9 podsystem: PodsystemParku;
10 poziom: PoziomLogowania;
11 rejestrujKolejnośćWywołań?: boolean;
12 czasWykonania?: boolean;
13}
14
15// Fabryka dekoratorów logowania
16function LogowanieSystemu(opcje: OpcjeLogowania) {
17 const { podsystem, poziom, rejestrujKolejnośćWywołań = false, czasWykonania = false } = opcje;
18
19 // Licznik wywołań dla każdej metody
20 const licznikWywołań: Record<string, number> = {};
21
22 // Zwracamy dekorator metody
23 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
24 // Zapisujemy oryginalną metodę
25 const oryginalnaMetoda = descriptor.value;
26
27 // Definiujemy nową metodę
28 descriptor.value = function(...args: any[]) {
29 // Rejestrowanie kolejności wywołania jeśli potrzebne
30 if (rejestrujKolejnośćWywołań) {
31 if (!licznikWywołań[propertyKey]) {
32 licznikWywołań[propertyKey] = 0;
33 }
34 licznikWywołań[propertyKey]++;
35 console.log(`[${podsystem}] [Wywołanie #${licznikWywołań[propertyKey]}] ${propertyKey}`);
36 }
37
38 // Logowanie początkowe
39 console.log(`[${poziom}] [${podsystem}] Wywołanie ${propertyKey}(${JSON.stringify(args)})`);
40
41 // Pomiar czasu jeśli potrzebny
42 const startTime = czasWykonania ? performance.now() : 0;
43
44 try {
45 // Wywołanie oryginalnej metody
46 const wynik = oryginalnaMetoda.apply(this, args);
47
48 // Logowanie po pomyślnym wykonaniu
49 console.log(`[${poziom}] [${podsystem}] Zakończono ${propertyKey} - Status: Sukces`);
50
51 // Logowanie czasu wykonania jeśli potrzebne
52 if (czasWykonania) {
53 const endTime = performance.now();
54 console.log(`[${podsystem}] Czas wykonania ${propertyKey}: ${(endTime - startTime).toFixed(2)}ms`);
55 }
56
57 return wynik;
58 } catch (błąd) {
59 // Logowanie przy błędzie
60 console.error(`[${poziom}] [${podsystem}] Błąd w ${propertyKey}: ${błąd.message}`);
61
62 // Propagujemy błąd dalej
63 throw błąd;
64 }
65 };
66
67 return descriptor;
68 };
69}
70
71// Klasa zarządzająca systemem zagród
72class SystemZagród {
73 // Używamy fabryki do tworzenia różnych dekoratorów logowania
74 @LogowanieSystemu({ podsystem: "Zagrody", poziom: "Info" })
75 otwórzZagrodę(idZagrody: string): boolean {
76 console.log(`Otwieranie zagrody ${idZagrody}`);
77 return true;
78 }
79
80 @LogowanieSystemu({ podsystem: "Zagrody", poziom: "Ostrzeżenie", czasWykonania: true })
81 zamknijZagrodę(idZagrody: string): boolean {
82 console.log(`Zamykanie zagrody ${idZagrody}`);
83
84 // Symulujemy dłuższe wykonanie
85 for (let i = 0; i < 1000000; i++) {
86 // Obciążenie procesora
87 }
88
89 return true;
90 }
91
92 @LogowanieSystemu({ podsystem: "Zagrody", poziom: "Krytyczny", rejestrujKolejnośćWywołań: true, czasWykonania: true })
93 tryBezpieczeństwa(idZagrody: string, powód: string): boolean {
94 console.log(`Aktywacja trybu bezpieczeństwa dla zagrody ${idZagrody}. Powód: ${powód}`);
95
96 // Symulujemy złożoną operację
97 for (let i = 0; i < 2000000; i++) {
98 // Obciążenie procesora
99 }
100
101 return true;
102 }
103}
104
105// Klasa zarządzająca systemem bezpieczeństwa
106class SystemBezpieczeństwa {
107 @LogowanieSystemu({ podsystem: "Bezpieczeństwo", poziom: "Info" })
108 sprawdźStatus(): { status: string, aktywneAlarmy: number } {
109 return { status: "Normalny", aktywneAlarmy: 0 };
110 }
111
112 @LogowanieSystemu({ podsystem: "Bezpieczeństwo", poziom: "Ostrzeżenie", czasWykonania: true })
113 aktywujAlarm(obszar: string, poziom: number): boolean {
114 console.log(`Aktywacja alarmu w obszarze ${obszar} na poziomie ${poziom}`);
115 return true;
116 }
117
118 @LogowanieSystemu({ podsystem: "Bezpieczeństwo", poziom: "Krytyczny", rejestrujKolejnośćWywołań: true })
119 ewakuacja(obszar: string): boolean {
120 console.log(`Protokół ewakuacji dla obszaru ${obszar}`);
121 return true;
122 }
123}
124
125// Użycie klas z dekoratorami
126const systemZagród = new SystemZagród();
127const systemBezpieczeństwa = new SystemBezpieczeństwa();
128
129// Testowanie
130systemZagród.otwórzZagrodę("A-001");
131systemZagród.zamknijZagrodę("A-001");
132systemZagród.tryBezpieczeństwa("A-001", "Test protokołu");
133systemZagród.tryBezpieczeństwa("A-001", "Ponowny test"); // Pokaże, że to drugie wywołanie
134
135systemBezpieczeństwa.sprawdźStatus();
136systemBezpieczeństwa.aktywujAlarm("Sektor B", 2);
137systemBezpieczeństwa.ewakuacja("Sektor B");W tym przykładzie, fabryka dekoratorów
LogowanieSystemu tworzy dekoratory do logowania metod, które można dostosować do różnych podsystemów, poziomów logowania oraz dodatkowych opcji, takich jak rejestrowanie kolejności wywołań czy mierzenie czasu wykonania.1// Interfejs opcji dla fabryki dekoratorów walidacji
2interface OpcjeWalidacji<T> {
3 walidator: (wartość: T) => boolean;
4 komunikatBłędu: string;
5 wyjątek?: typeof Error;
6 transformacja?: (wartość: T) => T;
7}
8
9// Fabryka dekoratorów walidacji parametrów metody
10function WalidujParametr<T>(opcje: OpcjeWalidacji<T>) {
11 const { walidator, komunikatBłędu, wyjątek = Error, transformacja } = opcje;
12
13 // Zwracamy dekorator parametru metody
14 return function(target: Object, propertyKey: string, parameterIndex: number) {
15 // Klucz dla metadanych
16 const kluczWalidacji = `walidacja_${propertyKey}_param_${parameterIndex}`;
17
18 // Zapisujemy metadane walidacji
19 const walidacje = Reflect.getOwnMetadata(kluczWalidacji, target) || [];
20
21 // Dodajemy nową walidację
22 walidacje.push({
23 indeks: parameterIndex,
24 walidator,
25 komunikatBłędu,
26 wyjątek,
27 transformacja
28 });
29
30 // Zapisujemy zaktualizowane metadane
31 Reflect.defineMetadata(kluczWalidacji, walidacje, target);
32
33 // Patrzymy, czy już istnieje dekorator metody
34 if (!Reflect.hasMetadata(`walidacja_${propertyKey}_decorated`, target)) {
35 // Oznaczamy metodę jako już dekorowaną
36 Reflect.defineMetadata(`walidacja_${propertyKey}_decorated`, true, target);
37
38 // Pobieramy deskryptor metody
39 const oryginalnaMetoda = target[propertyKey];
40
41 // Przeładowujemy metodę
42 target[propertyKey] = function(...args: any[]) {
43 // Pobieramy wszystkie walidacje dla tej metody
44 const walidacje = Reflect.getOwnMetadata(kluczWalidacji, target) || [];
45
46 // Stosujemy walidacje
47 for (const walidacja of walidacje) {
48 const { indeks, walidator, komunikatBłędu, wyjątek, transformacja } = walidacja;
49
50 // Jeśli wartość jest dostarczana, walidujemy ją
51 if (indeks < args.length) {
52 const wartość = args[indeks];
53
54 // Sprawdzamy walidację
55 if (!walidator(wartość)) {
56 throw new wyjątek(`Błąd walidacji parametru ${indeks} w metodzie ${propertyKey}: ${komunikatBłędu}`);
57 }
58
59 // Stosujemy transformację, jeśli jest dostarczana
60 if (transformacja) {
61 args[indeks] = transformacja(wartość);
62 }
63 }
64 }
65
66 // Wywołujemy oryginalną metodę
67 return oryginalnaMetoda.apply(this, args);
68 };
69 }
70 };
71}
72
73// Zestaw predefiniowanych walidatorów
74// Walidator niepustego stringa
75function NiePusty() {
76 return WalidujParametr<string>({
77 walidator: (str) => str !== null && str !== undefined && str.trim() !== '',
78 komunikatBłędu: "Wartość nie może być pusta"
79 });
80}
81
82// Walidator minimalnej długości
83function MinDługość(długość: number) {
84 return WalidujParametr<string>({
85 walidator: (str) => str !== null && str !== undefined && str.length >= długość,
86 komunikatBłędu: `Wartość musi mieć co najmniej ${długość} znaków`
87 });
88}
89
90// Walidator zakresu liczbowego
91function Zakres(min: number, max: number) {
92 return WalidujParametr<number>({
93 walidator: (num) => num >= min && num <= max,
94 komunikatBłędu: `Wartość musi być w zakresie od ${min} do ${max}`,
95 // Opcjonalnie możemy ograniczyć wartość do zakresu
96 transformacja: (num) => Math.min(Math.max(num, min), max)
97 });
98}
99
100// Walidator pozytywnej liczby
101function Pozytywna() {
102 return WalidujParametr<number>({
103 walidator: (num) => num > 0,
104 komunikatBłędu: "Wartość musi być dodatnia"
105 });
106}
107
108// Walidator formatu daty
109function FormatDaty(format: RegExp = /^d{4}-d{2}-d{2}$/) {
110 return WalidujParametr<string>({
111 walidator: (str) => format.test(str),
112 komunikatBłędu: "Wartość musi być w formacie daty YYYY-MM-DD"
113 });
114}
115
116// Klasa do zarządzania danymi dinozaurów
117class ZarządzanieDinozaurami {
118 // Utworzenie nowego dinozaura
119 dodajDinozaura(
120 @NiePusty() @MinDługość(3) nazwa: string,
121 @NiePusty() gatunek: string,
122 @Pozytywna() @Zakres(0, 100) wiek: number,
123 @Pozytywna() waga: number,
124 @FormatDaty() dataDodania: string
125 ): string {
126 const idDinozaura = `DINO-${Date.now()}`;
127
128 console.log(`Dodano nowego dinozaura:`);
129 console.log(`- ID: ${idDinozaura}`);
130 console.log(`- Nazwa: ${nazwa}`);
131 console.log(`- Gatunek: ${gatunek}`);
132 console.log(`- Wiek: ${wiek} lat`);
133 console.log(`- Waga: ${waga} kg`);
134 console.log(`- Data dodania: ${dataDodania}`);
135
136 return idDinozaura;
137 }
138
139 // Aktualizacja danych dinozaura
140 aktualizujDinozaura(
141 @NiePusty() idDinozaura: string,
142 @Zakres(0, 100) wiek?: number,
143 @Pozytywna() waga?: number
144 ): boolean {
145 console.log(`Aktualizacja dinozaura ${idDinozaura}:`);
146
147 if (wiek !== undefined) {
148 console.log(`- Nowy wiek: ${wiek} lat`);
149 }
150
151 if (waga !== undefined) {
152 console.log(`- Nowa waga: ${waga} kg`);
153 }
154
155 return true;
156 }
157
158 // Przeniesienie dinozaura do nowej zagrody
159 przenieśDinozaura(
160 @NiePusty() idDinozaura: string,
161 @NiePusty() doZagrody: string,
162 @FormatDaty() dataPrzeniesienia: string
163 ): boolean {
164 console.log(`Przeniesienie dinozaura ${idDinozaura} do zagrody ${doZagrody} w dniu ${dataPrzeniesienia}`);
165 return true;
166 }
167}
168
169// Testowanie klasy z walidacją parametrów
170const zarządzanieDino = new ZarządzanieDinozaurami();
171
172try {
173 // Poprawne wywołanie
174 zarządzanieDino.dodajDinozaura(
175 "Blue",
176 "Velociraptor",
177 5,
178 150,
179 "2023-09-15"
180 );
181
182 // Błędne wywołanie - puste imię
183 zarządzanieDino.dodajDinozaura(
184 "", // Puste imię
185 "Tyrannosaurus",
186 8,
187 7500,
188 "2023-09-15"
189 );
190} catch (błąd) {
191 console.error(błąd.message);
192}
193
194try {
195 // Poprawne wywołanie
196 zarządzanieDino.aktualizujDinozaura("DINO-123", 6, 160);
197
198 // Błędne wywołanie - ujemna waga
199 zarządzanieDino.aktualizujDinozaura("DINO-123", 6, -100);
200} catch (błąd) {
201 console.error(błąd.message);
202}
203
204try {
205 // Poprawne wywołanie
206 zarządzanieDino.przenieśDinozaura("DINO-123", "B-002", "2023-09-20");
207
208 // Błędne wywołanie - nieprawidłowy format daty
209 zarządzanieDino.przenieśDinozaura("DINO-123", "B-003", "20/09/2023");
210} catch (błąd) {
211 console.error(błąd.message);
212}W tym przykładzie stworzyliśmy fabrykę dekoratorów walidacji parametrów, która pozwala na tworzenie różnych walidatorów, takich jak
NiePusty, MinDługość, Zakres, Pozytywna i FormatDaty. Dzięki temu możemy łatwo zastosować walidację do parametrów metod w sposób deklaratywny.1// Poziomy uprawnień w parku
2enum PoziomUprawnień {
3 Gość = 0,
4 Pracownik = 1,
5 Kierownik = 2,
6 Administrator = 3
7}
8
9// Akcje w systemie
10type AkcjaSystemu = "odczyt" | "zapis" | "usuwanie" | "administracja";
11
12// Mapowanie poziomów uprawnień na dozwolone akcje
13const mapaUprawnień: Record<PoziomUprawnień, AkcjaSystemu[]> = {
14 [PoziomUprawnień.Gość]: ["odczyt"],
15 [PoziomUprawnień.Pracownik]: ["odczyt", "zapis"],
16 [PoziomUprawnień.Kierownik]: ["odczyt", "zapis", "usuwanie"],
17 [PoziomUprawnień.Administrator]: ["odczyt", "zapis", "usuwanie", "administracja"]
18};
19
20// Interfejs dla kontekstu bezpieczeństwa
21interface KontekstBezpieczeństwa {
22 użytkownik: string;
23 poziomUprawnień: PoziomUprawnień;
24 tokeny?: string[];
25}
26
27// Błąd braku uprawnień
28class BłądUprawnień extends Error {
29 constructor(akcja: string, wymaganyPoziom: PoziomUprawnień, aktualnyPoziom: PoziomUprawnień) {
30 super(`Brak uprawnień do akcji ${akcja}. Wymagany poziom: ${PoziomUprawnień[wymaganyPoziom]}, posiadany: ${PoziomUprawnień[aktualnyPoziom]}`);
31 this.name = "BłądUprawnień";
32 }
33}
34
35// Fabryka dekoratorów uprawnień
36function WymagaUprawnienia(wymaganaAkcja: AkcjaSystemu, komunikatBłędu?: string) {
37 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
38 const oryginalnaMetoda = descriptor.value;
39
40 descriptor.value = function(this: { kontekstBezpieczeństwa?: KontekstBezpieczeństwa }, ...args: any[]) {
41 // Sprawdzamy, czy mamy kontekst bezpieczeństwa
42 if (!this.kontekstBezpieczeństwa) {
43 throw new Error("Brak kontekstu bezpieczeństwa");
44 }
45
46 const { poziomUprawnień } = this.kontekstBezpieczeństwa;
47
48 // Sprawdzamy, czy użytkownik ma akcję dla swojego poziomu uprawnień
49 const dozwoloneAkcje = mapaUprawnień[poziomUprawnień] || [];
50
51 if (!dozwoloneAkcje.includes(wymaganaAkcja)) {
52 // Znajdujemy minimalny poziom uprawnień potrzebny dla tej akcji
53 let minimalnyPoziom: PoziomUprawnień = PoziomUprawnień.Administrator;
54
55 for (const [poziom, akcje] of Object.entries(mapaUprawnień)) {
56 if (akcje.includes(wymaganaAkcja) && Number(poziom) < minimalnyPoziom) {
57 minimalnyPoziom = Number(poziom) as PoziomUprawnień;
58 }
59 }
60
61 throw new BłądUprawnień(
62 wymaganaAkcja,
63 minimalnyPoziom,
64 poziomUprawnień
65 );
66 }
67
68 // Wywołujemy oryginalną metodę
69 return oryginalnaMetoda.apply(this, args);
70 };
71
72 return descriptor;
73 };
74}
75
76// Uproszczone wersje dla często używanych poziomów
77function DlaGości() {
78 return WymagaUprawnienia("odczyt");
79}
80
81function DlaPracowników() {
82 return WymagaUprawnienia("zapis");
83}
84
85function DlaKierowników() {
86 return WymagaUprawnienia("usuwanie");
87}
88
89function DlaAdministratorów() {
90 return WymagaUprawnienia("administracja");
91}
92
93// Klasa systemu zarządzania parkiem
94class SystemZarządzaniaParkiem {
95 kontekstBezpieczeństwa?: KontekstBezpieczeństwa;
96
97 constructor() {
98 // Domyślnie brak kontekstu bezpieczeństwa
99 }
100
101 // Metoda do ustawiania kontekstu bezpieczeństwa
102 zaloguj(użytkownik: string, poziomUprawnień: PoziomUprawnień): void {
103 this.kontekstBezpieczeństwa = { użytkownik, poziomUprawnień };
104 console.log(`Zalogowano użytkownika ${użytkownik} z poziomem uprawnień: ${PoziomUprawnień[poziomUprawnień]}`);
105 }
106
107 // Metoda dostępna dla wszystkich
108 @DlaGości()
109 pobierzInformacjePubliczne(): string {
110 return "Informacje publiczne o parku";
111 }
112
113 // Metoda dostępna dla pracowników i wyżej
114 @DlaPracowników()
115 aktualizujDaneDinozaura(idDinozaura: string, dane: any): boolean {
116 console.log(`Aktualizacja danych dinozaura ${idDinozaura}`);
117 return true;
118 }
119
120 // Metoda dostępna dla kierowników i wyżej
121 @DlaKierowników()
122 usuńDinozaura(idDinozaura: string): boolean {
123 console.log(`Usunięcie dinozaura ${idDinozaura}`);
124 return true;
125 }
126
127 // Metoda dostępna tylko dla administratorów
128 @DlaAdministratorów()
129 zmianaKonfiguracji(klucz: string, wartość: any): boolean {
130 console.log(`Zmiana konfiguracji: ${klucz} = ${wartość}`);
131 return true;
132 }
133
134 // Metoda z bezpośrednim użyciem fabryki
135 @WymagaUprawnienia("administracja", "Tylko administratorzy mogą restartować system")
136 restartSystemu(): boolean {
137 console.log("Restartowanie systemu...");
138 return true;
139 }
140}
141
142// Testowanie systemu uprawnień
143const system = new SystemZarządzaniaParkiem();
144
145// Test dla gościa
146console.log("--- Test dla gościa ---");
147system.zaloguj("JohnVisitor", PoziomUprawnień.Gość);
148
149try {
150 console.log(system.pobierzInformacjePubliczne()); // Powinno działać
151 system.aktualizujDaneDinozaura("DINO-123", { wiek: 5 }); // Powinno dać błąd
152} catch (błąd) {
153 console.error(błąd.message);
154}
155
156// Test dla pracownika
157console.log("
158--- Test dla pracownika ---");
159system.zaloguj("JaneEmployee", PoziomUprawnień.Pracownik);
160
161try {
162 console.log(system.pobierzInformacjePubliczne()); // Powinno działać
163 system.aktualizujDaneDinozaura("DINO-123", { wiek: 5 }); // Powinno działać
164 system.usuńDinozaura("DINO-123"); // Powinno dać błąd
165} catch (błąd) {
166 console.error(błąd.message);
167}
168
169// Test dla kierownika
170console.log("
171--- Test dla kierownika ---");
172system.zaloguj("BobManager", PoziomUprawnień.Kierownik);
173
174try {
175 system.usuńDinozaura("DINO-123"); // Powinno działać
176 system.zmianaKonfiguracji("maxDino", 100); // Powinno dać błąd
177} catch (błąd) {
178 console.error(błąd.message);
179}
180
181// Test dla administratora
182console.log("
183--- Test dla administratora ---");
184system.zaloguj("AliceAdmin", PoziomUprawnień.Administrator);
185
186try {
187 system.zmianaKonfiguracji("maxDino", 100); // Powinno działać
188 system.restartSystemu(); // Powinno działać
189} catch (błąd) {
190 console.error(błąd.message);
191}W tym przykładzie stworzyliśmy fabrykę dekoratorów uprawnień, która umożliwia kontrolowanie dostępu do metod na podstawie poziomów uprawnień użytkowników. Na jej podstawie stworzyliśmy uproszczone dekoratory dla często używanych poziomów uprawnień, takie jak
DlaGości, DlaPracowników, DlaKierowników i DlaAdministratorów.1// Typy zasobów parku
2type TypZasobu = "dinozaur" | "zagroda" | "budynek" | "pojazd" | "sprzęt";
3
4// Interfejs śledzenia wykorzystania zasobów
5interface ŚledzioneWykorzystanie {
6 zasobId: string;
7 typ: TypZasobu;
8 czasRozpoczęcia: Date;
9 czasZakończenia?: Date;
10 użytkownik: string;
11 status: "wUżyciu" | "zakończone" | "anulowane";
12}
13
14// Globalny rejestr wykorzystania zasobów
15class RejestrWykorzystaniaZasobów {
16 private static instancja: RejestrWykorzystaniaZasobów;
17 private historia: ŚledzioneWykorzystanie[] = [];
18 private aktywneWykorzystanie: Map<string, ŚledzioneWykorzystanie> = new Map();
19
20 private constructor() {}
21
22 // Metoda do pobrania singletona
23 static pobierzInstancję(): RejestrWykorzystaniaZasobów {
24 if (!RejestrWykorzystaniaZasobów.instancja) {
25 RejestrWykorzystaniaZasobów.instancja = new RejestrWykorzystaniaZasobów();
26 }
27 return RejestrWykorzystaniaZasobów.instancja;
28 }
29
30 // Metoda do rozpoczynania śledzenia
31 rozpocznijŚledzenie(zasobId: string, typ: TypZasobu, użytkownik: string): string {
32 const idUżycia = `USE-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
33
34 const śledzenie: ŚledzioneWykorzystanie = {
35 zasobId,
36 typ,
37 czasRozpoczęcia: new Date(),
38 użytkownik,
39 status: "wUżyciu"
40 };
41
42 this.aktywneWykorzystanie.set(idUżycia, śledzenie);
43
44 console.log(`Rozpoczęto śledzenie zasobu ${typ} ${zasobId} przez ${użytkownik}`);
45
46 return idUżycia;
47 }
48
49 // Metoda do zakończenia śledzenia
50 zakończŚledzenie(idUżycia: string, sukces: boolean = true): boolean {
51 if (!this.aktywneWykorzystanie.has(idUżycia)) {
52 console.warn(`Nie znaleziono aktywnego śledzenia dla ID: ${idUżycia}`);
53 return false;
54 }
55
56 const śledzenie = this.aktywneWykorzystanie.get(idUżycia)!;
57 śledzenie.czasZakończenia = new Date();
58 śledzenie.status = sukces ? "zakończone" : "anulowane";
59
60 this.historia.push(śledzenie);
61 this.aktywneWykorzystanie.delete(idUżycia);
62
63 console.log(`Zakończono śledzenie zasobu ${śledzenie.typ} ${śledzenie.zasobId} przez ${śledzenie.użytkownik} ze statusem: ${śledzenie.status}`);
64
65 return true;
66 }
67
68 // Metoda do sprawdzania, czy zasób jest aktualnie używany
69 czyZasóbWUżyciu(zasobId: string, typ: TypZasobu): boolean {
70 for (const [idUżycia, śledzenie] of this.aktywneWykorzystanie.entries()) {
71 if (śledzenie.zasobId === zasobId && śledzenie.typ === typ) {
72 return true;
73 }
74 }
75 return false;
76 }
77
78 // Metoda do pobierania historii użycia zasobu
79 pobierzHistorięZasobu(zasobId: string, typ: TypZasobu): ŚledzioneWykorzystanie[] {
80 return this.historia.filter(śledzenie =>
81 śledzenie.zasobId === zasobId && śledzenie.typ === typ
82 );
83 }
84
85 // Metoda do pobierania liczby aktywnych użyć
86 liczbaAktywnychUżyć(): number {
87 return this.aktywneWykorzystanie.size;
88 }
89}
90
91// Interfejs opcji dla fabryki dekoratorów zasobów
92interface OpcjeŚledzeniaZasobów {
93 typZasobu: TypZasobu;
94 sprawdźDostępność?: boolean;
95 indeksIdZasobu?: number;
96 komunikatNiedostępności?: string;
97}
98
99// Fabryka dekoratorów śledzenia zasobów
100function ŚledzioneWykorzystanieZasobu(opcje: OpcjeŚledzeniaZasobów) {
101 const {
102 typZasobu,
103 sprawdźDostępność = false,
104 indeksIdZasobu = 0,
105 komunikatNiedostępności = "Zasób jest obecnie niedostępny"
106 } = opcje;
107
108 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
109 const oryginalnaMetoda = descriptor.value;
110
111 descriptor.value = function(...args: any[]) {
112 // Pobieramy ID zasobu z parametrów
113 const zasobId = args[indeksIdZasobu];
114
115 if (!zasobId) {
116 throw new Error(`Nie podano ID zasobu (parametr #${indeksIdZasobu})`);
117 }
118
119 // Pobieramy rejestr
120 const rejestr = RejestrWykorzystaniaZasobów.pobierzInstancję();
121
122 // Jeśli trzeba sprawdzić dostępność
123 if (sprawdźDostępność && rejestr.czyZasóbWUżyciu(zasobId, typZasobu)) {
124 throw new Error(`${komunikatNiedostępności} (${typZasobu} ${zasobId})`);
125 }
126
127 // Pobieramy nazwę użytkownika z obiektu, jeśli dostępna
128 const użytkownik = this.użytkownik || "system";
129
130 // Rozpoczynamy śledzenie
131 const idUżycia = rejestr.rozpocznijŚledzenie(zasobId, typZasobu, użytkownik);
132
133 try {
134 // Wywołujemy oryginalną metodę
135 const wynik = oryginalnaMetoda.apply(this, args);
136
137 // Kończymy śledzenie z sukcesem
138 rejestr.zakończŚledzenie(idUżycia, true);
139
140 return wynik;
141 } catch (błąd) {
142 // Kończymy śledzenie z błędem
143 rejestr.zakończŚledzenie(idUżycia, false);
144
145 // Propagujemy błąd dalej
146 throw błąd;
147 }
148 };
149
150 return descriptor;
151 };
152}
153
154// Funkcje pomocnicze dla często używanych typów zasobów
155function ŚledzioneWykorzystanieDinozaura(opcje: Omit<OpcjeŚledzeniaZasobów, "typZasobu"> = {}) {
156 return ŚledzioneWykorzystanieZasobu({ ...opcje, typZasobu: "dinozaur" });
157}
158
159function ŚledzioneWykorzystanieZagrody(opcje: Omit<OpcjeŚledzeniaZasobów, "typZasobu"> = {}) {
160 return ŚledzioneWykorzystanieZasobu({ ...opcje, typZasobu: "zagroda" });
161}
162
163function ŚledzioneWykorzystaniePojazdu(opcje: Omit<OpcjeŚledzeniaZasobów, "typZasobu"> = {}) {
164 return ŚledzioneWykorzystanieZasobu({
165 ...opcje,
166 typZasobu: "pojazd",
167 sprawdźDostępność: true,
168 komunikatNiedostępności: "Pojazd jest obecnie w użyciu"
169 });
170}
171
172function ŚledzioneWykorzystanieSprzętu(opcje: Omit<OpcjeŚledzeniaZasobów, "typZasobu"> = {}) {
173 return ŚledzioneWykorzystanieZasobu({
174 ...opcje,
175 typZasobu: "sprzęt",
176 sprawdźDostępność: true,
177 komunikatNiedostępności: "Sprzęt jest obecnie w użyciu"
178 });
179}
180
181// Klasa zarządzania zasobami parku
182class ZarządzanieZasobamiParku {
183 użytkownik: string;
184
185 constructor(użytkownik: string) {
186 this.użytkownik = użytkownik;
187 }
188
189 // Śledzenie badania dinozaura
190 @ŚledzioneWykorzystanieDinozaura()
191 przeprowadźBadanieDinozaura(idDinozaura: string): { status: string, temperatura: number } {
192 console.log(`Przeprowadzanie badania dinozaura ${idDinozaura}`);
193
194 // Symulacja długiego procesu
195 for (let i = 0; i < 1000000; i++) {
196 // Obciążenie procesora
197 }
198
199 return { status: "zdrowy", temperatura: 38.5 };
200 }
201
202 // Śledzenie karmienia dinozaura
203 @ŚledzioneWykorzystanieDinozaura()
204 nakarmDinozaura(idDinozaura: string, typPokarmu: string, ilość: number): boolean {
205 console.log(`Karmienie dinozaura ${idDinozaura} pokarmem ${typPokarmu} (${ilość} kg)`);
206
207 // Symulacja procesu
208 for (let i = 0; i < 500000; i++) {
209 // Obciążenie procesora
210 }
211
212 return true;
213 }
214
215 // Śledzenie kontroli zagrody
216 @ŚledzioneWykorzystanieZagrody()
217 skontrolujZagrodę(idZagrody: string): { status: string, problemy: string[] } {
218 console.log(`Kontrola zagrody ${idZagrody}`);
219
220 // Symulacja kontroli
221 for (let i = 0; i < 800000; i++) {
222 // Obciążenie procesora
223 }
224
225 return { status: "OK", problemy: [] };
226 }
227
228 // Śledzenie wykorzystania pojazdu
229 @ŚledzioneWykorzystaniePojazdu()
230 użyjPojazdu(idPojazdu: string, cel: string): boolean {
231 console.log(`Użycie pojazdu ${idPojazdu} do celu: ${cel}`);
232
233 // Symulacja jazdy
234 for (let i = 0; i < 1500000; i++) {
235 // Obciążenie procesora
236 }
237
238 return true;
239 }
240
241 // Śledzenie wykorzystania sprzętu
242 @ŚledzioneWykorzystanieSprzętu({ indeksIdZasobu: 1 })
243 użyjSprzętu(cel: string, idSprzętu: string): boolean {
244 console.log(`Użycie sprzętu ${idSprzętu} do celu: ${cel}`);
245
246 // Symulacja użycia
247 for (let i = 0; i < 700000; i++) {
248 // Obciążenie procesora
249 }
250
251 return true;
252 }
253
254 // Bezpośrednie użycie fabryki z niestandardowymi opcjami
255 @ŚledzioneWykorzystanieZasobu({
256 typZasobu: "budynek",
257 sprawdźDostępność: true,
258 indeksIdZasobu: 0,
259 komunikatNiedostępności: "Budynek jest obecnie w konserwacji"
260 })
261 przeprowadźKonserwacjęBudynku(idBudynku: string, typKonserwacji: string): boolean {
262 console.log(`Konserwacja budynku ${idBudynku}: ${typKonserwacji}`);
263
264 // Symulacja konserwacji
265 for (let i = 0; i < 2000000; i++) {
266 // Obciążenie procesora
267 }
268
269 return true;
270 }
271}
272
273// Testowanie systemu śledzenia zasobów
274async function testŚledzeniaZasobów() {
275 const pracownik1 = new ZarządzanieZasobamiParku("John");
276 const pracownik2 = new ZarządzanieZasobamiParku("Alice");
277
278 // Test badania dinozaura
279 console.log("--- Test badania dinozaura ---");
280 await pracownik1.przeprowadźBadanieDinozaura("DINO-001");
281
282 // Test karmienia dinozaura
283 console.log("
284--- Test karmienia dinozaura ---");
285 await pracownik2.nakarmDinozaura("DINO-001", "mięso", 50);
286
287 // Test kontroli zagrody
288 console.log("
289--- Test kontroli zagrody ---");
290 await pracownik1.skontrolujZagrodę("ZAG-001");
291
292 // Test użycia pojazdu
293 console.log("
294--- Test użycia pojazdu ---");
295
296 try {
297 // Pierwszy pracownik używa pojazdu
298 console.log("Pracownik 1 używa pojazdu...");
299 const obietnica1 = pracownik1.użyjPojazdu("JEEP-001", "Obchód parku");
300
301 // Próbujemy użyć tego samego pojazdu przez drugiego pracownika
302 console.log("Próba użycia tego samego pojazdu przez Pracownika 2...");
303 const obietnica2 = pracownik2.użyjPojazdu("JEEP-001", "Transport");
304
305 await Promise.all([obietnica1, obietnica2]);
306 } catch (błąd) {
307 console.error(`Błąd: ${błąd.message}`);
308 }
309
310 // Test użycia sprzętu
311 console.log("
312--- Test użycia sprzętu ---");
313 await pracownik1.użyjSprzętu("Badanie", "EQUIP-001");
314
315 // Test konserwacji budynku
316 console.log("
317--- Test konserwacji budynku ---");
318 await pracownik2.przeprowadźKonserwacjęBudynku("BLD-001", "Elektryka");
319
320 // Sprawdzamy liczbę aktywnych użyć
321 const rejestr = RejestrWykorzystaniaZasobów.pobierzInstancję();
322 console.log(`
323Liczba aktywnych użyć: ${rejestr.liczbaAktywnychUżyć()}`);
324
325 // Sprawdzamy historię zasobu
326 console.log("
327Historia dinozaura DINO-001:");
328 const historiaDino = rejestr.pobierzHistorięZasobu("DINO-001", "dinozaur");
329 console.log(`Liczba wpisów: ${historiaDino.length}`);
330 historiaDino.forEach((wpis, indeks) => {
331 console.log(`[${indeks + 1}] ${wpis.użytkownik}: ${new Date(wpis.czasRozpoczęcia).toLocaleTimeString()} - ${wpis.czasZakończenia ? new Date(wpis.czasZakończenia).toLocaleTimeString() : 'w toku'}`);
332 });
333}
334
335// Uruchomienie testu
336testŚledzeniaZasobów();W tym przykładzie stworzyliśmy fabrykę dekoratorów do śledzenia wykorzystania zasobów w Parku Jurajskim. Fabryka
ŚledzioneWykorzystanieZasobu tworzy dekoratory, które automatycznie rejestrują rozpoczęcie i zakończenie korzystania z zasobu, a także mogą sprawdzać jego dostępność. Na jej podstawie stworzyliśmy uproszczone dekoratory dla często używanych typów zasobów, takie jak ŚledzioneWykorzystanieDinozaura, ŚledzioneWykorzystanieZagrody, ŚledzioneWykorzystaniePojazdu i ŚledzioneWykorzystanieSprzętu.Jedną z największych zalet fabryk dekoratorów jest możliwość ich łączenia, co pozwala na tworzenie bardziej złożonych i elastycznych rozwiązań.
1// Łączenie fabryk dekoratorów
2function PełneŚledzenie(typZasobu: TypZasobu, poziomLogowania: PoziomLogowania = "Info") {
3 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
4 // Najpierw zastosuj logowanie
5 LogowanieSystemu({
6 podsystem: typZasobu === "dinozaur" ? "Dinozaury" :
7 typZasobu === "zagroda" ? "Zagrody" :
8 typZasobu === "pojazd" ? "Transport" :
9 typZasobu === "sprzęt" ? "Wyposażenie" : "System",
10 poziom: poziomLogowania,
11 czasWykonania: true
12 })(target, propertyKey, descriptor);
13
14 // Następnie zastosuj śledzenie zasobów
15 return ŚledzioneWykorzystanieZasobu({
16 typZasobu,
17 sprawdźDostępność: typZasobu === "pojazd" || typZasobu === "sprzęt"
18 })(target, propertyKey, descriptor);
19 };
20}
21
22// Przykład użycia połączonych fabryk
23class ZarządzanieParkiemV2 {
24 użytkownik: string;
25 kontekstBezpieczeństwa?: KontekstBezpieczeństwa;
26
27 constructor(użytkownik: string, poziomUprawnień: PoziomUprawnień) {
28 this.użytkownik = użytkownik;
29 this.kontekstBezpieczeństwa = { użytkownik, poziomUprawnień };
30 }
31
32 // Łączymy śledzenie zasobów, logowanie i uprawnienia
33 @DlaPracowników()
34 @PełneŚledzenie("dinozaur", "Info")
35 opiekujSięDinozaurem(idDinozaura: string): boolean {
36 console.log(`Opieka nad dinozaurem ${idDinozaura}`);
37 return true;
38 }
39
40 // Łączymy śledzenie zasobów, logowanie i uprawnienia z wyższym poziomem
41 @DlaKierowników()
42 @PełneŚledzenie("zagroda", "Ostrzeżenie")
43 modyfikujUstawieniaZagrody(idZagrody: string, ustawienia: any): boolean {
44 console.log(`Modyfikacja ustawień zagrody ${idZagrody}`);
45 return true;
46 }
47
48 // Łączymy wszystkie potrzebne dekoratory dla krytycznej funkcji
49 @DlaAdministratorów()
50 @PełneŚledzenie("sprzęt", "Krytyczny")
51 konfigurujSystemyBezpieczeństwa(idSprzętu: string, parametry: any): boolean {
52 console.log(`Konfiguracja systemów bezpieczeństwa za pomocą ${idSprzętu}`);
53 return true;
54 }
55}
56
57// Test łączenia fabryk dekoratorów
58function testŁączeniaFabryk() {
59 console.log("=== Test łączenia fabryk dekoratorów ===");
60
61 const pracownik = new ZarządzanieParkiemV2("Mary", PoziomUprawnień.Pracownik);
62 const kierownik = new ZarządzanieParkiemV2("Bob", PoziomUprawnień.Kierownik);
63 const admin = new ZarządzanieParkiemV2("Alice", PoziomUprawnień.Administrator);
64
65 // Test pracownika
66 console.log("
67--- Test pracownika ---");
68 try {
69 pracownik.opiekujSięDinozaurem("DINO-002"); // Powinno działać
70 pracownik.modyfikujUstawieniaZagrody("ZAG-002", { temperatura: 30 }); // Powinno dać błąd
71 } catch (błąd) {
72 console.error(błąd.message);
73 }
74
75 // Test kierownika
76 console.log("
77--- Test kierownika ---");
78 try {
79 kierownik.modyfikujUstawieniaZagrody("ZAG-002", { temperatura: 30 }); // Powinno działać
80 kierownik.konfigurujSystemyBezpieczeństwa("SEC-001", { poziom: "wysoki" }); // Powinno dać błąd
81 } catch (błąd) {
82 console.error(błąd.message);
83 }
84
85 // Test administratora
86 console.log("
87--- Test administratora ---");
88 try {
89 admin.konfigurujSystemyBezpieczeństwa("SEC-001", { poziom: "wysoki" }); // Powinno działać
90 } catch (błąd) {
91 console.error(błąd.message);
92 }
93}
94
95// Uruchomienie testu
96testŁączeniaFabryk();W tym przykładzie pokazujemy, jak można łączyć różne fabryki dekoratorów, tworząc kompleksowe rozwiązania. Funkcja
PełneŚledzenie łączy dekoratory logowania i śledzenia zasobów, a następnie jest używana razem z dekoratorami uprawnień.Staraj się, aby wszystkie fabryki dekoratorów miały podobną strukturę i konwencję nazewnictwa. Dzięki temu, kod będzie bardziej czytelny i przewidywalny.
1// Spójna struktura fabryk dekoratorów
2function LogowanieDanych(opcje: OpcjeLogowania) { /* ... */ }
3function WalidacjaDanych(opcje: OpcjeWalidacji) { /* ... */ }
4function ŚledzenieDanych(opcje: OpcjeŚledzenia) { /* ... */ }Zawsze określaj dokładne typy dla parametrów fabryki, aby zapewnić bezpieczeństwo typów i wsparcie IDE.
1// Dobre typowanie
2interface OpcjeWalidacji<T> {
3 walidator: (wartość: T) => boolean;
4 komunikat: string;
5}
6
7function Waliduj<T>(opcje: OpcjeWalidacji<T>) {
8 //
9}Fabryki dekoratorów powinny mieć rozsądne wartości domyślne dla opcji, które nie są krytyczne.
1function LogujOperację(opcje: Partial<OpcjeLogowania> = {}) {
2 const {
3 poziom = "Info",
4 szczegółowy = false,
5 czasWykonania = false
6 } = opcje;
7
8 //
9}Dla często używanych konfiguracji, twórz specjalizowane fabryki, które ułatwią używanie dekoratorów.
1// Specjalizowane wersje dla różnych poziomów logowania
2function LogujDebug(szczegółowy: boolean = false) {
3 return LogujOperację({ poziom: "Debug", szczegółowy });
4}
5
6function LogujInfo() {
7 return LogujOperację({ poziom: "Info" });
8}
9
10function LogujOstrzeżenie() {
11 return LogujOperację({ poziom: "Ostrzeżenie", czasWykonania: true });
12}
13
14function LogujBłąd() {
15 return LogujOperację({ poziom: "Błąd", szczegółowy: true, czasWykonania: true });
16}Dobrze udokumentowane fabryki dekoratorów ułatwiają ich używanie.
1/**
2 * Tworzy dekorator, który waliduje parametr metody.
3 *
4 * @param opcje - Opcje walidacji
5 * @param opcje.walidator - Funkcja sprawdzająca poprawność wartości
6 * @param opcje.komunikat - Komunikat błędu w przypadku niepoprawnej wartości
7 * @param opcje.transformacja - Opcjonalna funkcja przekształcająca wartość
8 *
9 * @returns Dekorator parametru metody
10 *
11 * @example
12 * class Przykład {
13 * metoda(@Waliduj({ walidator: (x) => x > 0, komunikat: "Musi być dodatnia" }) liczba: number) {
14 * //
15 * }
16 * }
17 */
18function Waliduj<T>(opcje: OpcjeWalidacji<T>) {
19 //
20}Fabryki dekoratorów powinny być jawne w swoich zależnościach.
1// Źle - ukryta zależność
2function LogujDoPliku(ścieżkaPliku: string) {
3 // Bezpośrednio używa systemu plików
4 const fs = require('fs');
5 //
6}
7
8// Dobrze - jawna zależność
9function LogujDoPliku(logger: FileLogger) {
10 // Logger jest przekazywany jako parametr
11 //
12}Pisz testy jednostkowe dla swoich fabryk dekoratorów, aby upewnić się, że działają jak oczekiwano.
1describe('WalidujZakres', () => {
2 it('powinien przepuścić wartość w zakresie', () => {
3 //
4 });
5
6 it('powinien zgłosić błąd dla wartości poza zakresem', () => {
7 //
8 });
9
10 it('powinien ograniczyć wartość do zakresu, jeśli transformacja włączona', () => {
11 //
12 });
13});Fabryki dekoratorów są szczególnie przydatne w następujących przypadkach:
Fabryki dekoratorów w TypeScript są potężnym narzędziem, które pozwala na tworzenie elastycznych, konfigurowalnych i wielokrotnego użytku dekoratorów. Podobnie jak systemy w Parku Jurajskim muszą być dostosowane do różnych potrzeb i wymagań, ale zachowywać jednolite standardy bezpieczeństwa, fabryki dekoratorów umożliwiają tworzenie spójnych, ale dostosowanych do konkretnych przypadków użycia dekoratorów.
Dzięki fabrykom dekoratorów możemy:
Fabryki dekoratorów to zaawansowana technika w TypeScripcie, która wymaga dobrego zrozumienia dekoratorów, ale oferuje ogromne korzyści w zakresie organizacji kodu, jego ponownego wykorzystania i czytelności. Gdy zostaną dobrze zaprojektowane, stają się niezwykle wartościowymi narzędziami w twoim arsenale programistycznym.