"Oversight of individual operations in Jurassic Park is just as important as managing the entire park," explains Dr. Henry Wu, InGen's chief geneticist. "Just as we monitor every aspect of dinosaur life - their feeding, behavior, and growth - we must also control and secure individual operations in our system."
In TypeScript, after understanding class decorators, the natural next step is to delve into method and property decorators. These decorators allow us to precisely modify the behavior of specific class members, just as in Jurassic Park, where every operation, from feeding dinosaurs to securing enclosures, requires special oversight and protocols.
Method and property decorators allow modifying the behavior, access, or value of individual class members. Thanks to them, we can:
Method decorators are functions that take three parameters:
target - the class prototype (for instance methods) or the class constructor (for static methods)propertyKey - the method namedescriptor - the property descriptor for the method1function dekoratorMetody(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
2 // Modification of the method descriptor
3 // Return the modified descriptor or undefined
4}
5
6class MojaKlasa {
7 @dekoratorMetody
8 mojaMetoda() {
9 // Method implementation
10 }
11}Let's start with a simple method decorator that will log operation execution in Jurassic Park:
1// Method decorator that logs calls
2function LogOperation(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
3 // Save the original method
4 const oryginalnaMetoda = descriptor.value;
5
6 // Override the method
7 descriptor.value = function(...args: any[]) {
8 console.log(`[${new Date().toISOString()}] Calling ${propertyKey} with arguments: ${JSON.stringify(args)}`);
9
10 try {
11 // Call the original method
12 const wynik = oryginalnaMetoda.apply(this, args);
13
14 // Log the result
15 console.log(`[${new Date().toISOString()}] ${propertyKey} completed. Result: ${JSON.stringify(wynik)}`);
16
17 return wynik;
18 } catch (error) {
19 // Log the error
20 console.error(`[${new Date().toISOString()}] ${propertyKey} failed with error: ${error}`);
21
22 // Re-throw the error
23 throw error;
24 }
25 };
26
27 return descriptor;
28}
29
30// Applying the decorator in the enclosure management class
31class EnclosureManagement {
32 constructor(private nazwaPracownika: string) {}
33
34 @LogOperation
35 openEnclosure(idZagrody: string): boolean {
36 console.log(`${this.nazwaPracownika} opens enclosure: ${idZagrody}`);
37 // Enclosure opening logic here
38 return true;
39 }
40
41 @LogOperation
42 closeEnclosure(idZagrody: string): boolean {
43 console.log(`${this.nazwaPracownika} closes enclosure: ${idZagrody}`);
44 // Enclosure closing logic here
45 return true;
46 }
47
48 checkStatus(idZagrody: string): string {
49 // This method is not logged
50 return "Enclosure closed";
51 }
52}
53
54// Using the class with decorated methods
55const manager = new EnclosureManagement("John");
56manager.openEnclosure("A-001");
57manager.closeEnclosure("A-001");
58manager.checkStatus("A-001"); // This operation will not be loggedIn the example above, the
LogOperation decorator records every method call, its arguments, and the result or error. The openEnclosure and closeEnclosure methods are logged, while the checkStatus method has no decorator, so it is not logged.We can also create method decorators that accept parameters:
1// Method decorator with parameters
2function RequireAccessLevel(poziom: number) {
3 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
4 // Save the original method
5 const oryginalnaMetoda = descriptor.value;
6
7 // Override the method
8 descriptor.value = function(...args: any[]) {
9 // Check if the object has an access level property
10 if (!(this as any).accessLevel) {
11 throw new Error("Object does not have a defined access level!");
12 }
13
14 // Verify the access level
15 if ((this as any).accessLevel < poziom) {
16 throw new Error(`Required access level: ${poziom}, available: ${(this as any).accessLevel}`);
17 }
18
19 // Call the original method
20 return oryginalnaMetoda.apply(this, args);
21 };
22
23 return descriptor;
24 };
25}
26
27// Class representing a laboratory
28class LaboratoriumGenetyczne {
29 constructor(public nazwaPracownika: string, public accessLevel: number) {}
30
31 @RequireAccessLevel(1)
32 odczytajDaneDNA(idDinosaura: string): string {
33 return `Dinosaur DNA ${idDinosaura}: ACGT...`;
34 }
35
36 @RequireAccessLevel(2)
37 modyfikujDNA(idDinosaura: string, sequence: string): boolean {
38 console.log(`DNA modification of dinosaur ${idDinosaura}: ${sequence}`);
39 return true;
40 }
41
42 @RequireAccessLevel(3)
43 createNewSpecies(nazwa: string, bazoweDNA: string): string {
44 const nowySpeciesId = `NGT-${Math.floor(Math.random() * 1000)}`;
45 console.log(`Creating new species: ${nazwa} (ID: ${nowySpeciesId})`);
46 return nowySpeciesId;
47 }
48}
49
50// Using the class with different access levels
51const intern = new LaboratoriumGenetyczne("Tim", 1);
52const scientist = new LaboratoriumGenetyczne("Dr. Grant", 2);
53const dyrektor = new LaboratoriumGenetyczne("Dr. Wu", 3);
54
55// Testing access
56try {
57 console.log(intern.odczytajDaneDNA("D-001")); // Will work
58 intern.modyfikujDNA("D-001", "ACGT"); // Will throw error
59} catch (error) {
60 console.error(error.message);
61}
62
63try {
64 scientist.modyfikujDNA("D-001", "ACGT"); // Will work
65 scientist.createNewSpecies("Neo-Raptor", "ACGT..."); // Will throw error
66} catch (error) {
67 console.error(error.message);
68}
69
70// Director has full access
71dyrektor.createNewSpecies("Indominus Rex", "ACGT...");In this example, the
RequireAccessLevel decorator checks whether the person calling the method has a sufficient access level. Different methods require different access levels, reflecting real security protocols that could exist in Jurassic Park.Property decorators are similar to method decorators but receive only two parameters:
target - the class prototype (for instance properties) or the class constructor (for static properties)propertyKey - the property name1function propertyDecorator(target: any, propertyKey: string) {
2 // Decorator implementation
3}
4
5class MojaKlasa {
6 @propertyDecorator
7 myProperty: string;
8}Since property decorators don't receive a descriptor, they are often used to generate getters and setters or to apply metadata.
Let's implement a property decorator that will monitor resource changes in Jurassic Park:
1// Property decorator that monitors changes
2function MonitorujZmiany(target: any, propertyKey: string) {
3 // Create a unique name for the private property
4 const prywatnaName = Symbol(propertyKey);
5
6 // Define getter and setter
7 Object.defineProperty(target, propertyKey, {
8 get: function() {
9 // Return the private property value
10 return this[prywatnaName];
11 },
12 set: function(value: any) {
13 // Save the old value
14 const oldValue = this[prywatnaName];
15
16 // Log the change if it's not the first assignment
17 if (this[prywatnaName] !== undefined) {
18 console.log(`[CHANGE][${this.constructor.name}] ${propertyKey}: ${oldValue} -> ${value}`);
19 }
20
21 // Save the new value
22 this[prywatnaName] = value;
23 },
24 enumerable: true,
25 configurable: true
26 });
27}
28
29// Class monitoring park resources
30class ZasobyParku {
31 @MonitorujZmiany
32 foodAmount: number = 1000;
33
34 @MonitorujZmiany
35 generatorsState: "Aktywne" | "Awaryjne" | "Disabled" = "Aktywne";
36
37 @MonitorujZmiany
38 statusBramy: "Otwarta" | "Closed" = "Closed";
39
40 constructor(public nazwaSekcji: string) {}
41
42 useFood(amount: number): boolean {
43 if (this.foodAmount >= amount) {
44 this.foodAmount -= amount;
45 return true;
46 }
47 return false;
48 }
49
50 refillFood(amount: number): void {
51 this.foodAmount += amount;
52 }
53
54 toggleGenerators(stan: "Aktywne" | "Awaryjne" | "Disabled"): void {
55 this.generatorsState = stan;
56 }
57
58 toggleGate(status: "Otwarta" | "Closed"): void {
59 this.statusBramy = status;
60 }
61}
62
63// Using the class with monitored properties
64const sekcjaA = new ZasobyParku("Section A");
65
66// Testing property changes
67sekcjaA.useFood(200); // foodAmount: 1000 -> 800
68sekcjaA.refillFood(500); // foodAmount: 800 -> 1300
69sekcjaA.toggleGenerators("Awaryjne"); // generatorsState: Aktywne -> Awaryjne
70sekcjaA.toggleGate("Otwarta"); // statusBramy: Closed -> OtwartaIn this example, the
MonitorujZmiany decorator creates a getter and setter for properties that log every value change. This way, we can track when and what changes were made, which is crucial for monitoring park resources.We can combine different method and property decorators to create more advanced behaviors:
1// Decorator measuring method execution time
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(`[TIME] ${propertyKey}: ${(koniec - start).toFixed(2)}ms`);
11
12 return wynik;
13 };
14
15 return descriptor;
16}
17
18// Decorator validating method parameters
19function WalidujParametry(walidator: (args: any[]) => boolean, errorMessage: 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(`[VALIDATION ERROR] ${propertyKey}: ${errorMessage}`);
26 }
27
28 return oryginalnaMetoda.apply(this, args);
29 };
30
31 return descriptor;
32 };
33}
34
35// Decorator caching method results
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}: Used cached result for ${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// Decorator adding a value range for a numeric property
58function Zakres(min: number, max: number) {
59 return function(target: any, propertyKey: string) {
60 // Create a unique name for the private property
61 const prywatnaName = Symbol(propertyKey);
62
63 // Define getter and setter
64 Object.defineProperty(target, propertyKey, {
65 get: function() {
66 return this[prywatnaName];
67 },
68 set: function(value: number) {
69 if (value < min) {
70 console.warn(`[RANGE] ${propertyKey}: Value ${value} below minimum ${min}. Setting ${min}.`);
71 this[prywatnaName] = min;
72 } else if (value > max) {
73 console.warn(`[RANGE] ${propertyKey}: Value ${value} above maximum ${max}. Setting ${max}.`);
74 this[prywatnaName] = max;
75 } else {
76 this[prywatnaName] = value;
77 }
78 },
79 enumerable: true,
80 configurable: true
81 });
82 };
83}
84
85// Class managing the electric fence system
86class ElectricFenceSystem {
87 @Zakres(0, 10000)
88 voltage: number = 5000; // in volts
89
90 @Zakres(0, 100)
91 range: number = 50; // in meters
92
93 private statusActive: boolean = false;
94
95 @LogOperation
96 @MierzCzasWykonania
97 @WalidujParametry(
98 args => args[0] >= 0 && args[0] <= 10000,
99 "Invalid voltage. Allowed range: 0-10000V"
100 )
101 setVoltage(newVoltage: number): boolean {
102 // Simulating a long operation for testing the MierzCzasWykonania decorator
103 for (let i = 0; i < 1000000; i++) {
104 // CPU load
105 }
106
107 this.voltage = newVoltage;
108 return true;
109 }
110
111 @LogOperation
112 @Cachuj
113 pobierzStatusSystemu(idSekcji: string): { aktywny: boolean; voltage: number; range: number } {
114 console.log(`Fetching system status for section ${idSekcji}...`);
115
116 // Simulating a long operation
117 for (let i = 0; i < 500000; i++) {
118 // CPU load
119 }
120
121 return {
122 aktywny: this.statusActive,
123 voltage: this.voltage,
124 range: this.range
125 };
126 }
127
128 @LogOperation
129 @MierzCzasWykonania
130 aktywujSystem(): boolean {
131 if (this.statusActive) {
132 console.log("System already active");
133 return false;
134 }
135
136 console.log(`Activating electric fence system. Voltage: ${this.voltage}V, Range: ${this.range}m`);
137 this.statusActive = true;
138
139 return true;
140 }
141
142 @LogOperation
143 @MierzCzasWykonania
144 deaktywujSystem(): boolean {
145 if (!this.statusActive) {
146 console.log("System already inactive");
147 return false;
148 }
149
150 console.log("Deactivating electric fence system");
151 this.statusActive = false;
152
153 return true;
154 }
155}
156
157// Using the class with multiple decorators
158const systemOgrodzenia = new ElectricFenceSystem();
159
160// Testing various functionalities
161systemOgrodzenia.setVoltage(7500); // Logs operation, measures time, validates parameter
162systemOgrodzenia.setVoltage(12000); // Will exceed range, will be set to 10000
163systemOgrodzenia.aktywujSystem(); // Logs operation, measures time
164
165// Testing caching
166console.log(systemOgrodzenia.pobierzStatusSystemu("A1")); // Full operation
167console.log(systemOgrodzenia.pobierzStatusSystemu("A1")); // Uses cache
168console.log(systemOgrodzenia.pobierzStatusSystemu("B2")); // Full operation
169console.log(systemOgrodzenia.pobierzStatusSystemu("B2")); // Uses cache
170
171// Deactivating the system
172systemOgrodzenia.deaktywujSystem();In this example, we use multiple decorators to create an advanced electric fence system:
@Zakres - ensures property values stay within the specified range@LogOperation - logs method calls@MierzCzasWykonania - measures method execution time@WalidujParametry - checks method parameter validity@Cachuj - caches method results in memoryTypeScript also allows using metadata with decorators, enabling more advanced use cases. This requires enabling the
emitDecoratorMetadata and experimentalDecorators options in the tsconfig.json file and installing the reflect-metadata package.Here is an example of using metadata with decorators:
1// We need to import reflect-metadata at the start of the application
2import "reflect-metadata";
3
4// Keys for metadata
5const PARAMETRY_KLUCZ = Symbol("parametery");
6
7// Parameter decorator
8function Parametr(target: any, propertyKey: string, parameterIndex: number) {
9 // Get existing metadata or create empty
10 const parametery: number[] = Reflect.getOwnMetadata(PARAMETRY_KLUCZ, target, propertyKey) || [];
11
12 // Add parameter index to the list
13 parametery.push(parameterIndex);
14
15 // Save metadata
16 Reflect.defineMetadata(PARAMETRY_KLUCZ, parametery, target, propertyKey);
17}
18
19// Method decorator validating parameters
20function WalidujDane(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
21 const oryginalnaMetoda = descriptor.value;
22
23 descriptor.value = function(...args: any[]) {
24 // Get parameter types (requires emitDecoratorMetadata option)
25 const typy = Reflect.getMetadata("design:paramtypes", target, propertyKey);
26
27 // Get parameter indices marked with the @Parametr decorator
28 const oznaczenieParametry: number[] = Reflect.getOwnMetadata(PARAMETRY_KLUCZ, target, propertyKey) || [];
29
30 // Validate parameters
31 for (const indeks of oznaczenieParametry) {
32 const value = args[indeks];
33 const typ = typy[indeks];
34
35 // Type validation
36 if (value === null || value === undefined) {
37 throw new Error(`Parameter #${indeks} of method ${propertyKey} cannot be null or undefined`);
38 }
39
40 // Validation for different types
41 if (typ === String && typeof value !== "string") {
42 throw new Error(`Parameter #${indeks} of method ${propertyKey} must be a string`);
43 } else if (typ === Number && typeof value !== "number") {
44 throw new Error(`Parameter #${indeks} of method ${propertyKey} must be a number`);
45 } else if (typ === Boolean && typeof value !== "boolean") {
46 throw new Error(`Parameter #${indeks} of method ${propertyKey} must be a boolean`);
47 } else if (typ === Date && !(value instanceof Date)) {
48 throw new Error(`Parameter #${indeks} of method ${propertyKey} must be a Date`);
49 }
50 }
51
52 // Call the original method
53 return oryginalnaMetoda.apply(this, args);
54 };
55
56 return descriptor;
57}
58
59// Dinosaur operations class with metadata
60class OperacjeDinosaurs {
61 @WalidujDane
62 conductResearchZdrowia(
63 idDinosaura: string,
64 @Parametr dataBadania: Date,
65 @Parametr temperatura: number,
66 @Parametr weight: number,
67 notatki?: string
68 ): string {
69 console.log(`Health examination of dinosaur ${idDinosaura}`);
70 console.log(`Date: ${dataBadania.toLocaleDateString()}`);
71 console.log(`Temperature: ${temperatura}°C`);
72 console.log(`Weight: ${weight}kg`);
73
74 if (notatki) {
75 console.log(`Notes: ${notatki}`);
76 }
77
78 return "Examination completed";
79 }
80
81 @WalidujDane
82 performVaccination(
83 @Parametr idDinosaura: string,
84 @Parametr typSzczepienia: string,
85 @Parametr dataSzczepienia: Date
86 ): boolean {
87 console.log(`Vaccination of dinosaur ${idDinosaura}`);
88 console.log(`Vaccination type: ${typSzczepienia}`);
89 console.log(`Date: ${dataSzczepienia.toLocaleDateString()}`);
90
91 return true;
92 }
93}
94
95// Using the class with parameter validation
96const operacje = new OperacjeDinosaurs();
97
98try {
99 // Correct call
100 operacje.conductResearchZdrowia(
101 "D-001",
102 new Date(),
103 38.5,
104 1200,
105 "Dinosaur in good condition"
106 );
107
108 // Incorrect call - temperature is not a number
109 operacje.conductResearchZdrowia(
110 "D-002",
111 new Date(),
112 "wysoka" as any, // This will cause an error
113 1500
114 );
115} catch (error) {
116 console.error(error.message);
117}
118
119try {
120 // Correct call
121 operacje.performVaccination(
122 "D-003",
123 "Anti-parasite vaccine",
124 new Date()
125 );
126
127 // Incorrect call - date is not a Date object
128 operacje.performVaccination(
129 "D-004",
130 "Anti-flu vaccine",
131 "dzisiaj" as any // This will cause an error
132 );
133} catch (error) {
134 console.error(error.message);
135}In this example, we use metadata and reflection to validate method parameters. The
@Parametr decorator marks parameters that should be validated, and the @WalidujDane decorator checks the types of those parameters on every method call.Now let's combine all the discussed techniques in one larger example - a dinosaur examination management system:
1// Property decorator ensuring unique IDs
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 // If the property hasn't been set yet, generate an ID
9 if (!this[`_${propertyKey}`]) {
10 this[`_${propertyKey}`] = generujID();
11 }
12 return this[`_${propertyKey}`];
13 },
14 set: function(value: string) {
15 // Allow setting only once
16 if (!this[`_${propertyKey}`]) {
17 this[`_${propertyKey}`] = value;
18 } else {
19 console.warn(`Property ${propertyKey} has already been set and cannot be changed.`);
20 }
21 },
22 enumerable: true,
23 configurable: true
24 });
25 };
26}
27
28// Method decorator for date validation
29function WalidujDataBadania(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
30 const oryginalnaMetoda = descriptor.value;
31
32 descriptor.value = function(...args: any[]) {
33 // Assume the first argument is the examination date
34 const dataBadania = args[0];
35
36 if (!(dataBadania instanceof Date)) {
37 throw new Error("First parameter must be a Date instance");
38 }
39
40 const teraz = new Date();
41
42 if (dataBadania > teraz) {
43 throw new Error("Examination date cannot be in the future");
44 }
45
46 // Check if the date is not older than 30 days
47 const thirtyDaysAgo = new Date();
48 thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
49
50 if (dataBadania < thirtyDaysAgo) {
51 console.warn("Warning: Examination date is older than 30 days");
52 }
53
54 return oryginalnaMetoda.apply(this, args);
55 };
56
57 return descriptor;
58}
59
60// Method decorator for vaccination frequency control
61function ControlFrequency(minDaysBetweenVaccinations: number) {
62 return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
63 // Map storing last vaccination dates for each dinosaur
64 const ostatnieSzczepienia = new Map<string, Date>();
65
66 const oryginalnaMetoda = descriptor.value;
67
68 descriptor.value = function(...args: any[]) {
69 // Assume first argument is dinosaur ID, second is vaccination date
70 const idDinosaura = args[0];
71 const dataSzczepienia = args[1];
72
73 if (ostatnieSzczepienia.has(idDinosaura)) {
74 const ostatniaSzczepionka = ostatnieSzczepienia.get(idDinosaura)!;
75 const differenceDays = Math.floor((dataSzczepienia.getTime() - ostatniaSzczepionka.getTime()) / (1000 * 60 * 60 * 24));
76
77 if (differenceDays < minDaysBetweenVaccinations) {
78 throw new Error(`Too early for next vaccination. Minimum interval is ${minDaysBetweenVaccinations} days. ${minDaysBetweenVaccinations - differenceDays} days remaining.`);
79 }
80 }
81
82 // Save vaccination date
83 ostatnieSzczepienia.set(idDinosaura, dataSzczepienia);
84
85 return oryginalnaMetoda.apply(this, args);
86 };
87
88 return descriptor;
89 };
90}
91
92// Examination results interface
93interface WynikiBadania {
94 temperatura: number; // in degrees Celsius
95 weight: number; // in kilograms
96 length: number; // in meters
97 stanZdrowia: "Zdrowy" | "Lekkie objawy" | "Chory" | "Krytyczny";
98 uwagi?: string;
99}
100
101// Dinosaur examination class
102class BadanieDinosaura {
103 @UnikalneID("BAD")
104 idBadania: string;
105
106 constructor(
107 public idDinosaura: string,
108 public dataBadania: Date,
109 public wyniki: WynikiBadania,
110 public examiner: string
111 ) {}
112
113 @LogOperation
114 pobierzRaport(): string {
115 return `
116 === EXAMINATION REPORT ===
117 Examination ID: ${this.idBadania}
118 Dinosaur ID: ${this.idDinosaura}
119 Date: ${this.dataBadania.toLocaleString()}
120 Conducted by: ${this.examiner}
121
122 RESULTS:
123 - Temperature: ${this.wyniki.temperatura}°C
124 - Weight: ${this.wyniki.weight} kg
125 - Length: ${this.wyniki.length} m
126 - Health status: ${this.wyniki.stanZdrowia}
127 ${this.wyniki.uwagi ? `- Notes: ${this.wyniki.uwagi}` : ""}
128 `;
129 }
130}
131
132// Dinosaur vaccination class
133class SzczepienieDinosaura {
134 @UnikalneID("SZCZ")
135 idSzczepienia: string;
136
137 constructor(
138 public idDinosaura: string,
139 public dataSzczepienia: Date,
140 public genusSzczepionki: string,
141 public dawka: number,
142 public vaccinator: string
143 ) {}
144
145 @LogOperation
146 pobierzRaport(): string {
147 return `
148 === VACCINATION REPORT ===
149 Vaccination ID: ${this.idSzczepienia}
150 Dinosaur ID: ${this.idDinosaura}
151 Date: ${this.dataSzczepienia.toLocaleString()}
152 Vaccine type: ${this.genusSzczepionki}
153 Dose: ${this.dawka} ml
154 Conducted by: ${this.vaccinator}
155 `;
156 }
157}
158
159// Examination and vaccination registry
160class RejestrMedyczny {
161 private badania: BadanieDinosaura[] = [];
162 private szczepienia: SzczepienieDinosaura[] = [];
163
164 @LogOperation
165 @WalidujDataBadania
166 zarejestrujBadanie(
167 dataBadania: Date,
168 idDinosaura: string,
169 temperatura: number,
170 weight: number,
171 length: number,
172 stanZdrowia: "Zdrowy" | "Lekkie objawy" | "Chory" | "Krytyczny",
173 uwagi?: string,
174 examiner: string = "Dr. Harding"
175 ): BadanieDinosaura {
176 const wyniki: WynikiBadania = {
177 temperatura,
178 weight,
179 length,
180 stanZdrowia,
181 uwagi
182 };
183
184 const badanie = new BadanieDinosaura(idDinosaura, dataBadania, wyniki, examiner);
185 this.badania.push(badanie);
186
187 console.log(`Examination registered: ${badanie.idBadania} for dinosaur ${idDinosaura}`);
188
189 return badanie;
190 }
191
192 @LogOperation
193 @ControlFrequency(90) // Minimum 90 days between vaccinations
194 zarejestrujSzczepienie(
195 idDinosaura: string,
196 dataSzczepienia: Date,
197 genusSzczepionki: string,
198 dawka: number,
199 vaccinator: string = "Dr. Harding"
200 ): SzczepienieDinosaura {
201 const szczepienie = new SzczepienieDinosaura(
202 idDinosaura,
203 dataSzczepienia,
204 genusSzczepionki,
205 dawka,
206 vaccinator
207 );
208
209 this.szczepienia.push(szczepienie);
210
211 console.log(`Vaccination registered: ${szczepienie.idSzczepienia} for dinosaur ${idDinosaura}`);
212
213 return szczepienie;
214 }
215
216 @Cachuj
217 @LogOperation
218 getExaminationHistory(idDinosaura: string): BadanieDinosaura[] {
219 console.log(`Fetching examination history for dinosaur ${idDinosaura}...`);
220
221 // Simulating a long operation
222 for (let i = 0; i < 500000; i++) {
223 // CPU load
224 }
225
226 return this.badania.filter(badanie => badanie.idDinosaura === idDinosaura)
227 .sort((a, b) => b.dataBadania.getTime() - a.dataBadania.getTime());
228 }
229
230 @Cachuj
231 @LogOperation
232 getVaccinationHistory(idDinosaura: string): SzczepienieDinosaura[] {
233 console.log(`Fetching vaccination history for dinosaur ${idDinosaura}...`);
234
235 // Simulating a long operation
236 for (let i = 0; i < 500000; i++) {
237 // CPU load
238 }
239
240 return this.szczepienia.filter(szczepienie => szczepienie.idDinosaura === idDinosaura)
241 .sort((a, b) => b.dataSzczepienia.getTime() - a.dataSzczepienia.getTime());
242 }
243
244 @MierzCzasWykonania
245 @LogOperation
246 generujRaportMedyczny(idDinosaura: string): string {
247 const badania = this.getExaminationHistory(idDinosaura);
248 const szczepienia = this.getVaccinationHistory(idDinosaura);
249
250 let raport = `=== MEDICAL REPORT FOR DINOSAUR ${idDinosaura} ===
251
252`;
253
254 raport += `Number of examinations: ${badania.length}
255`;
256 raport += `Number of vaccinations: ${szczepienia.length}
257
258`;
259
260 if (badania.length > 0) {
261 const ostatnieBadanie = badania[0];
262 raport += `LAST EXAMINATION:
263`;
264 raport += `Date: ${ostatnieBadanie.dataBadania.toLocaleString()}
265`;
266 raport += `Health status: ${ostatnieBadanie.wyniki.stanZdrowia}
267`;
268 raport += `Temperature: ${ostatnieBadanie.wyniki.temperatura}°C
269`;
270 raport += `Weight: ${ostatnieBadanie.wyniki.weight} kg
271`;
272 raport += `Length: ${ostatnieBadanie.wyniki.length} m
273`;
274 if (ostatnieBadanie.wyniki.uwagi) {
275 raport += `Notes: ${ostatnieBadanie.wyniki.uwagi}
276`;
277 }
278 raport += "\n";
279 }
280
281 if (szczepienia.length > 0) {
282 const ostatnieSzczepienie = szczepienia[0];
283 raport += `LAST VACCINATION:
284`;
285 raport += `Date: ${ostatnieSzczepienie.dataSzczepienia.toLocaleString()}
286`;
287 raport += `Vaccine type: ${ostatnieSzczepienie.genusSzczepionki}
288`;
289 raport += `Dose: ${ostatnieSzczepienie.dawka} ml
290
291`;
292 }
293
294 raport += `EXAMINATION HISTORY:
295`;
296 badania.forEach((badanie, indeks) => {
297 raport += `${indeks + 1}. ${badanie.dataBadania.toLocaleDateString()} - ${badanie.wyniki.stanZdrowia}
298`;
299 });
300
301 raport += `
302VACCINATION HISTORY:
303`;
304 szczepienia.forEach((szczepienie, indeks) => {
305 raport += `${indeks + 1}. ${szczepienie.dataSzczepienia.toLocaleDateString()} - ${szczepienie.genusSzczepionki}
306`;
307 });
308
309 return raport;
310 }
311}
312
313// Testing the system
314const rejestrMedyczny = new RejestrMedyczny();
315
316// Registering examinations and vaccinations
317try {
318 // Past examination - OK
319 const badanie1 = rejestrMedyczny.zarejestrujBadanie(
320 new Date("2023-09-01"),
321 "TREX-001",
322 38.2,
323 7500,
324 12.5,
325 "Zdrowy",
326 "Routine examination",
327 "Dr. Harding"
328 );
329
330 // Future examination - Error
331 const badanie2 = rejestrMedyczny.zarejestrujBadanie(
332 new Date("2025-01-01"), // future
333 "TREX-001",
334 39.1,
335 7450,
336 12.5,
337 "Lekkie objawy",
338 "Slight fever"
339 );
340} catch (error) {
341 console.error(error.message);
342}
343
344// Registering vaccinations
345try {
346 // First vaccination
347 const szczepienie1 = rejestrMedyczny.zarejestrujSzczepienie(
348 "TREX-001",
349 new Date("2023-01-15"),
350 "Dinosaur flu vaccine",
351 50,
352 "Dr. Sattler"
353 );
354
355 // Second vaccination - too early, should throw error
356 const szczepienie2 = rejestrMedyczny.zarejestrujSzczepienie(
357 "TREX-001",
358 new Date("2023-03-15"), // Only 60 days later, minimum is 90 days
359 "Anti-parasite vaccine",
360 30
361 );
362} catch (error) {
363 console.error(error.message);
364}
365
366// Testing data retrieval
367console.log(rejestrMedyczny.getExaminationHistory("TREX-001")); // Full fetch
368console.log(rejestrMedyczny.getExaminationHistory("TREX-001")); // Uses cache
369
370// Generating medical report
371console.log(rejestrMedyczny.generujRaportMedyczny("TREX-001"));In this elaborate example, we created a comprehensive system for managing dinosaur examinations and vaccinations in Jurassic Park, using various decorators:
@UnikalneID - ensures each examination and vaccination has a unique ID@LogOperation - logs method calls@WalidujDataBadania - validates examination date correctness@ControlFrequency - ensures proper intervals between vaccinations@Cachuj - caches results of long-running operations@MierzCzasWykonania - measures report generation timeThis system shows how method and property decorators can be used to create advanced business functionality while ensuring code consistency, data safety, and performance.
Method and property decorators are especially useful:
Like class decorators, method and property decorators have their limitations and issues:
as any)Method and property decorators in TypeScript are powerful tools that allow adding extra functionality to class methods and properties in a declarative way. Just as operational protocols in Jurassic Park help control individual actions, method and property decorators help control and extend the behavior of individual class elements.
Thanks to decorators, we can create more modular, readable, and maintainable applications, isolating cross-cutting functionality such as logging, validation, or security. In large projects, well-designed decorators can significantly improve code quality and reduce redundancy.
However, like any advanced tool, method and property decorators should be used wisely. Excessive use of decorators can lead to code that is difficult to understand, debug, and test. The best practice is to use them where they genuinely provide benefits, not just for the sake of using them.