Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Fabryki dekoratorów

"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ę.

Wprowadzenie do fabryk dekoratorów

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:

  1. Wielokrotne użycie - możemy tworzyć wiele różnych dekoratorów z jednej fabryki
  2. Konfigurowalność - dostosowujemy zachowanie dekoratora do konkretnych potrzeb
  3. Spójność - wszystkie dekoratory z jednej fabryki mają tę samą strukturę i koncepcję
  4. Łatwiejsza konserwacja - zmiana w fabryce aktualizuje wszystkie dekoratory z niej powstałe
  5. Typowanie - możemy definiować parametry fabryki z precyzyjnymi typami

Podstawowa struktura fabryki 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}

Przykłady fabryk dekoratorów w Parku Jurajskim

Zobaczmy, jak możemy wykorzystać fabryki dekoratorów w kontekście zarządzania Parkiem Jurajskim.

Przykład 1: Fabryka dekoratorów logowania dla różnych podsystemów

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.

Przykład 2: Fabryka dekoratorów walidacji danych

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.

Przykład 3: Fabryka dekoratorów uprawnień

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
.

Przykład 4: Fabryka dekoratorów zarządzania zasobami

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
.

Łączenie fabryk dekoratorów

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ń.

Najlepsze praktyki przy tworzeniu fabryk dekoratorów

1. Zachowaj spójność struktury

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) { /* ... */ }

2. Używaj odpowiednich typów dla parametrów

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}

3. Zapewnij sensowne wartości domyślne

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}

4. Twórz uproszczone wersje dla często używanych przypadków

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}

5. Dokumentuj swoje fabryki

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}

6. Unikaj ukrytych zależności

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}

7. Testuj swoje fabryki dekoratorów

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});

Przypadki użycia fabryk dekoratorów

Fabryki dekoratorów są szczególnie przydatne w następujących przypadkach:

  1. Frameworki i biblioteki - tworzenie konfigurowalnych dekoratorów dla użytkowników frameworka
  2. Aspekty przekrojowe - logowanie, walidacja, zabezpieczenia, które muszą być dostosowane do wielu przypadków
  3. Konfiguracja środowiskowa - dostosowanie zachowania dekoratorów do różnych środowisk (rozwój, produkcja)
  4. Zarządzanie zasobami - monitorowanie, śledzenie i kontrolowanie dostępu do zasobów
  5. Zbieranie metryk - konfigurowanie sposobu zbierania metryk na podstawie różnych parametrów
  6. Kontrola dostępu - tworzenie dekoratorów uprawnień dla różnych poziomów i ról

Podsumowanie

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:

  1. Poprawić ponowne wykorzystanie kodu - tworząc konfigurowalne szablony dekoratorów
  2. Zwiększyć czytelność - ukrywając złożoną logikę pod prostym, deklaratywnym interfejsem
  3. Zapewnić spójność - stosując te same wzorce i struktury w całej aplikacji
  4. Zwiększyć elastyczność - dostosowując dekoratory do różnych przypadków użycia

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.

Przejdź do CodeWorlds