JavaScript and TypeScript course Β· Module 7: Advanced TypeScript
Static and Abstract Properties
In this lesson4
"In Jurassic Park, we have protocols and standards that apply to all objects of a given class," explains Dr. Henry Wu, the park's chief geneticist. "For example, all our dinosaur enclosures follow the same security procedure, regardless of which specific enclosure we're considering. On the other hand, every dinosaur species has its own unique DNA pattern, but we all know that DNA must exist - even if the details differ between species."
In object-oriented programming, TypeScript offers two powerful concepts that reflect these ideas:
- Static properties and methods - shared by all instances of a class
- Abstract properties and methods - must be implemented by inheriting classes
In this exercise, we will dive deep into these concepts by creating an advanced genetics and security protocol management system for Jurassic Park.
Static Properties and Methods
Static elements in a class belong to the class itself, not to its instances. This means:
- There is only one copy of each static property, regardless of the number of created instances
- Access to static properties is through the class name, not through an instance
- Static methods can be called directly on the class
Basic Usage of Static Elements
Let's see how static elements can be used in the context of Jurassic Park:
1class SecurityProtocol {
2 // Static property - shared by all instances
3 static readonly LEVEL_CRITICAL: number = 5;
4 static readonly LEVEL_HIGH: number = 4;
5 static readonly LEVEL_MEDIUM: number = 3;
6 static readonly LEVEL_LOW: number = 2;
7 static readonly LEVEL_MINIMAL: number = 1;
8
9 // Static property storing the current global park threat level
10 private static _currentThreatLevel: number = SecurityProtocol.LEVEL_LOW;
11
12 // Instance properties
13 private readonly area: string;
14 private localThreatLevel: number;
15
16 constructor(area: string) {
17 this.area = area;
18 this.localThreatLevel = SecurityProtocol._currentThreatLevel;
19 console.log(`Created security protocol for area: ${area}`);
20 }
21
22 // Static method accessible directly on the class
23 static raiseGlobalThreatLevel(): void {
24 if (this._currentThreatLevel < this.LEVEL_CRITICAL) {
25 this._currentThreatLevel++;
26 console.log(`WARNING: Raised global threat level to ${this._currentThreatLevel}`);
27
28 // Calling another static method
29 this.notifyAboutThreatLevel();
30 } else {
31 console.log("WARNING: Maximum threat level already reached!");
32 }
33 }
34
35 // Another static method
36 static lowerGlobalThreatLevel(): void {
37 if (this._currentThreatLevel > this.LEVEL_MINIMAL) {
38 this._currentThreatLevel--;
39 console.log(`Lowered global threat level to ${this._currentThreatLevel}`);
40 } else {
41 console.log("Threat level is already at minimum.");
42 }
43 }
44
45 // Static getter
46 static get currentThreatLevel(): number {
47 return this._currentThreatLevel;
48 }
49
50 // Private static method
51 private static notifyAboutThreatLevel(): void {
52 console.log(`Notification: Current park threat level: ${this._currentThreatLevel}`);
53 }
54
55 // Instance method using static properties
56 updateLocalThreatLevel(): void {
57 // Updating the local level based on the global level
58 this.localThreatLevel = SecurityProtocol._currentThreatLevel;
59 console.log(`Obszar ${this.area}: updated threat level to ${this.localThreatLevel}`);
60 }
61
62 get securityStatus(): string {
63 const poziomy = [
64 "UNKNOWN",
65 "MINIMAL",
66 "LOW",
67 "MEDIUM",
68 "HIGH",
69 "CRITICAL"
70 ];
71
72 return `Area: ${this.area}, Threat level: ${poziomy[this.localThreatLevel]}`;
73 }
74}
75
76// Using static properties and methods - without creating instances!
77console.log("Default threat level:", SecurityProtocol.currentThreatLevel);
78
79// Calling a static method directly on the class
80SecurityProtocol.raiseGlobalThreatLevel();
81SecurityProtocol.raiseGlobalThreatLevel();
82
83// Creating instances of the class
84const centralProtocol = new SecurityProtocol("Operations Center");
85const enclosureProtocol = new SecurityProtocol("T-Rex Enclosure");
86
87// Displaying the security status
88console.log(centralProtocol.securityStatus);
89console.log(enclosureProtocol.securityStatus);
90
91// Raising the global threat level again
92SecurityProtocol.raiseGlobalThreatLevel();
93
94// Updating local threat levels
95centralProtocol.updateLocalThreatLevel();
96enclosureProtocol.updateLocalThreatLevel();
97
98// Checking updated statuses
99console.log(centralProtocol.securityStatus);
100console.log(enclosureProtocol.securityStatus);In the above example:
- Threat level constants are static properties, accessible directly through the class
- The static property
_currentThreatLevelis shared across all instances - Static methods can modify static properties and call other static methods
- Class instances can update their internal states based on static properties
When to Use Static Elements
Static elements are especially useful in the following cases:
- Constants and configuration - Values that remain unchanged for all instances of a class
- Global state - When we need a value shared by all instances
- Utility methods - Helper functions that don't require instance state
- Factory methods - Alternative ways to create class instances
Here's a more elaborate example showing different uses of static elements:
1class GeneticRegistry {
2 // Static constants
3 static readonly NUCLEOTIDE_BASE = ["A", "C", "G", "T"];
4 static readonly MIN_DNA_LENGTH = 1000000;
5
6 // Global state
7 private static _registeredSpeciesCount: number = 0;
8 private static _speciesRegistrations: Map<string, string> = new Map();
9
10 // Instance properties
11 private id: string;
12 private dnaSequence: string;
13 private nazwa: string;
14
15 constructor(nazwa: string, dnaSequence: string) {
16 if (dnaSequence.length < GeneticRegistry.MIN_DNA_LENGTH) {
17 throw new Error(`DNA sequence is too short. Minimum required: ${GeneticRegistry.MIN_DNA_LENGTH} base pairs.`);
18 }
19
20 this.nazwa = nazwa;
21 this.dnaSequence = dnaSequence;
22 this.id = GeneticRegistry.wygenerujId(nazwa);
23
24 // Registering a new species
25 GeneticRegistry._speciesRegistrations.set(this.id, nazwa);
26 GeneticRegistry._registeredSpeciesCount++;
27 }
28
29 // Utility method - creates an ID based on name
30 private static wygenerujId(nazwa: string): string {
31 const timestamp = Date.now();
32 const cleanName = nazwa.toLowerCase().replace(/[^a-z0-9]/g, "");
33 return `DNO-${cleanName}-${timestamp}`;
34 }
35
36 // Factory method - creates an instance with random DNA
37 static createTestSpecies(nazwa: string): GeneticRegistry {
38 const dnaLength = this.MIN_DNA_LENGTH + Math.floor(Math.random() * 500000);
39 let sequence = "";
40
41 for (let i = 0; i < dnaLength; i++) {
42 const randomIndex = Math.floor(Math.random() * this.NUCLEOTIDE_BASE.length);
43 sequence += this.NUCLEOTIDE_BASE[randomIndex];
44 }
45
46 return new GeneticRegistry(nazwa, sequence);
47 }
48
49 // Utility method - verifies the correctness of a DNA sequence
50 static verifySequence(sequence: string): boolean {
51 if (sequence.length < this.MIN_DNA_LENGTH) return false;
52
53 // Checks if the sequence contains only allowed nucleotides
54 for (const nucleotide of sequence) {
55 if (!this.NUCLEOTIDE_BASE.includes(nucleotide)) {
56 return false;
57 }
58 }
59
60 return true;
61 }
62
63 // Getter for the static property
64 static get registeredSpeciesCount(): number {
65 return this._registeredSpeciesCount;
66 }
67
68 // Static method returning all registered species
69 static getAllSpecies(): string[] {
70 return Array.from(this._speciesRegistrations.values());
71 }
72
73 // Instance method using static fields
74 analyzeSequence(): { proportions: Record<string, number>, length: number } {
75 const proportions: Record<string, number> = {};
76
77 // Initializing counters for each nucleotide
78 GeneticRegistry.NUCLEOTIDE_BASE.forEach(n => {
79 proportions[n] = 0;
80 });
81
82 // Counting nucleotides
83 for (const nucleotide of this.dnaSequence) {
84 proportions[nucleotide]++;
85 }
86
87 // Converting to percentages
88 GeneticRegistry.NUCLEOTIDE_BASE.forEach(n => {
89 proportions[n] = Number((proportions[n] / this.dnaSequence.length * 100).toFixed(2));
90 });
91
92 return {
93 proportions,
94 length: this.dnaSequence.length
95 };
96 }
97
98 // Instance getters
99 get identyfikator(): string {
100 return this.id;
101 }
102
103 get gatunekName(): string {
104 return this.nazwa;
105 }
106}
107
108// Using static methods and properties
109console.log("Minimum DNA length:", GeneticRegistry.MIN_DNA_LENGTH);
110console.log("Available nucleotides:", GeneticRegistry.NUCLEOTIDE_BASE.join(", "));
111
112// Verifying a sample sequence
113const exampleSequence = "ACGTACGTACGT".repeat(100000); // Repeating to reach min. length
114console.log("Sequence verification:", GeneticRegistry.verifySequence(exampleSequence));
115
116// Creating instances using the factory method
117const trex = GeneticRegistry.createTestSpecies("Tyrannosaurus Rex");
118const raptor = GeneticRegistry.createTestSpecies("Velociraptor");
119const trike = GeneticRegistry.createTestSpecies("Triceratops");
120
121// Checking how many species have been registered
122console.log("Number of registered species:", GeneticRegistry.registeredSpeciesCount);
123console.log("Species list:", GeneticRegistry.getAllSpecies());
124
125// Using instance methods
126const trexAnalysis = trex.analyzeSequence();
127console.log(`Analiza ${trex.gatunekName} (ID: ${trex.identyfikator}):`);
128console.log(`Sequence length: ${trexAnalysis.length} base pairs`);
129console.log("Nucleotide proportions:", trexAnalysis.proportions);This example demonstrates:
- Using static constants (
NUCLEOTIDE_BASE,MIN_DNA_LENGTH) - Global state tracking the number and registration of species
- Static utility methods like
verifySequenceandwygenerujId - A factory method
createTestSpeciescreating instances with random data - How instance methods can use static properties
Abstract Classes and Methods
Abstract classes in TypeScript define a template that must be implemented by inheriting classes. You cannot create an instance of an abstract class directly - it serves as a base "blueprint" for other classes.
Abstract methods, defined in an abstract class, must be implemented by inheriting classes. They define a contract specifying which methods must be available, but they don't contain their own implementation.
Abstract Class Basics
Let's see how to use abstract classes in the context of Jurassic Park:
1// Abstract base class for all dinosaurs
2abstract class Dinosaur {
3 // Properties common to all dinosaurs
4 private _id: string;
5 protected _nazwa: string;
6 protected _species: string;
7 protected _age: number;
8 protected _weight: number;
9
10 constructor(id: string, nazwa: string, gatunek: string, wiek: number, weight: number) {
11 this._id = id;
12 this._name = name;
13 this._species = species;
14 this._age = age;
15 this._weight = weight;
16 }
17
18 // Getters
19 get id(): string { return this._id; }
20 get nazwa(): string { return this._name; }
21 get species(): string { return this._species; }
22 get age(): number { return this._age; }
23 get weight(): number { return this._weight; }
24
25 // Methods implemented in all derived classes
26 abstract calculateCaloricNeeds(): number;
27 abstract determineThreatLevel(): number;
28
29 // Abstract property - must be implemented in derived classes
30 abstract get typDiety(): string;
31
32 // Concrete method (not abstract) - available in all derived classes
33 getBasicData(): string {
34 return `${this._name} (${this._gatunek}) - ID: ${this._id}, Age: ${this._wiek} lat, Weight: ${this._weight} kg`;
35 }
36
37 // Method using abstract methods
38 generateReportDinosaura(): string {
39 const kalorie = this.calculateCaloricNeeds();
40 const threatLevel = this.determineThreatLevel();
41
42 return `
43=== RAPORT DINOZAURA ===
44${this.getBasicData()}
45Typ diety: ${this.typDiety}
46Dzienne zapotrzebowanie kaloryczne: ${kalorie} kcal
47Threat level: ${threatLevel}/10
48======================
49 `;
50 }
51}
52
53// Class inheriting from the abstract class
54class PredatoryDinosaur extends Dinosaur {
55 private _biteForce: number;
56 private _runSpeed: number;
57
58 constructor(
59 id: string,
60 nazwa: string,
61 gatunek: string,
62 wiek: number,
63 weight: number,
64 biteForce: number,
65 runSpeed: number
66 ) {
67 super(id, nazwa, gatunek, wiek, weight);
68 this._biteForce = biteForce;
69 this._runSpeed = runSpeed;
70 }
71
72 // Getters
73 get biteForce(): number { return this._biteForce; }
74 get runSpeed(): number { return this._runSpeed; }
75
76 // Implementation of abstract property
77 get typDiety(): string {
78 return "carnivore";
79 }
80
81 // Implementations of abstract methods
82 calculateCaloricNeeds(): number {
83 // For predators we use a complex formula
84 return Math.round(this._weight * 0.6 * (this._runSpeed * 0.2));
85 }
86
87 determineThreatLevel(): number {
88 // Threat level depends on bite force, speed, and weight
89 const bazowy = (this._biteForce * 0.7 + this._runSpeed * 0.3) / 100;
90 return Math.min(10, Math.max(1, Math.round(bazowy)));
91 }
92
93 // Method specific to predators
94 poluj(): string {
95 return `${this._name} hunts at speed ${this._runSpeed} km/h and bite force ${this._biteForce} PSI`;
96 }
97}
98
99// Another inheriting class
100class HerbivorousDinosaur extends Dinosaur {
101 private _neckLength: number;
102 private _armorType: "none" | "light" | "medium" | "heavy";
103
104 constructor(
105 id: string,
106 nazwa: string,
107 gatunek: string,
108 wiek: number,
109 weight: number,
110 neckLength: number,
111 armorType: "none" | "light" | "medium" | "heavy"
112 ) {
113 super(id, nazwa, gatunek, wiek, weight);
114 this._neckLength = neckLength;
115 this._armorType = armorType;
116 }
117
118 // Getters
119 get neckLength(): number { return this._neckLength; }
120 get armorType(): string { return this._armorType; }
121
122 // Implementation of abstract property
123 get typDiety(): string {
124 return "herbivorous";
125 }
126
127 // Implementations of abstract methods
128 calculateCaloricNeeds(): number {
129 // For herbivores we use a simpler formula - they need more calories per kilogram
130 return Math.round(this._weight * 0.8);
131 }
132
133 determineThreatLevel(): number {
134 // Threat level is lower for herbivores, but depends on weight and armor type
135 let bazowy = this._weight / 1000;
136
137 // Modifier dependent on armor
138 const modyfikatoryPancerza = {
139 "none": 0,
140 "light": 1,
141 "medium": 2,
142 "heavy": 3
143 };
144
145 bazowy += modyfikatoryPancerza[this._armorType];
146
147 return Math.min(7, Math.max(1, Math.round(bazowy))); // Max. 7 for herbivores
148 }
149
150 // Method specific to herbivores
151 graze(): string {
152 const reachHeight = this._neckLength > 2 ? "tall" : "low";
153 return `${this._name} is grazing, eating plants from ${reachHeight} tree sections`;
154 }
155}
156
157// We cannot create an instance of an abstract class
158// const invalid = new Dinosaur("ID", "Name", "Species", 1, 100); // Error!
159
160// Creating instances of derived classes
161const trex = new PredatoryDinosaur("T-REX-1", "Rex", "Tyrannosaurus", 8, 7000, 12800, 32);
162const stegosaurus = new HerbivorousDinosaur("STEG-1", "Spike", "Stegosaurus", 12, 5000, 1.2, "heavy");
163const diplodocus = new HerbivorousDinosaur("DIPL-1", "Dippy", "Diplodocus", 15, 16000, 6.5, "none");
164
165// Using methods implemented in derived classes
166console.log(trex.generateReportDinosaura());
167console.log(stegosaurus.generateReportDinosaura());
168console.log(diplodocus.generateReportDinosaura());
169
170// Using methods specific to a given class
171console.log(trex.poluj());
172console.log(stegosaurus.graze());
173
174// For both types of dinosaurs we can use base class methods
175console.log(trex.getBasicData());
176console.log(diplodocus.getBasicData());In the above example:
Dinosauris an abstract class containing shared properties and methods- Abstract methods
calculateCaloricNeeds()anddetermineThreatLevel()must be implemented by inheriting classes - The abstract property
typDietymust be defined in derived classes - Classes
PredatoryDinosaurandHerbivorousDinosaurimplement the abstract methods and properties - You cannot create an instance of the
Dinosaurclass directly
Applications of Abstract Classes and Methods
Abstract classes are especially useful in the following cases:
- Defining a common interface - Specifying a consistent set of methods for a group of related classes
- Sharing code - Providing common implementation for groups of classes
- Enforcing a contract - Guaranteeing that all derived classes will implement the required methods
- Creating type hierarchies - Building complex relationships between types
Let's see a more advanced example combining abstract classes and methods with static elements in a security protocol management system for Jurassic Park:
1// Helper types
2type AlertLevel = "normall" | "elevated" | "high" | "critical";
3type StatusSystem = "aktywny" | "nieaktywny" | "w_konserwacji" | "awaria";
4
5// Interface for monitoring systems
6interface MonitoringSystem {
7 startMonitoring(): void;
8 stopMonitoring(): void;
9 pobierzAktualnyStatus(): StatusSystem;
10}
11
12// Abstract base class for all security systems
13abstract class SecuritySystem implements MonitoringSystem {
14 // Static properties - for all security systems
15 private static _parkAlertLevel: AlertLevel = "normall";
16 private static _aktywneSystemy: Set<string> = new Set();
17 private static _incydenty: Array<{data: Date, systemType: string, location: string, description: string}> = [];
18
19 // Regular properties
20 protected _id: string;
21 protected _location: string;
22 protected _status: StatusSystem;
23 protected _czyMonitorowany: boolean;
24 protected _reviewDate: Date;
25
26 constructor(id: string, location: string) {
27 this._id = id;
28 this._location = location;
29 this._status = "nieaktywny";
30 this._isMonitored = false;
31 this._reviewDate = new Date();
32
33 // System registration
34 SecuritySystem.registerSystem(id, this.constructor.name);
35 }
36
37 // Static methods
38 static registerSystem(id: string, systemType: string): void {
39 console.log(`Registered new security system: ${systemType} (ID: ${id})`);
40 }
41
42 static reportIncident(systemType: string, location: string, description: string): void {
43 const incydent = {
44 data: new Date(),
45 systemType,
46 location,
47 description
48 };
49
50 this._incydenty.push(incydent);
51 console.log(`ALERT: Incident reported at location ${location}: ${description}`);
52
53 // Automatically raising the alert level
54 if (this._parkAlertLevel === "normall") {
55 this.changeAlertLevel("elevated");
56 } else if (this._parkAlertLevel === "elevated") {
57 this.changeAlertLevel("high");
58 } else if (this._parkAlertLevel === "high") {
59 this.changeAlertLevel("critical");
60 }
61 }
62
63 static changeAlertLevel(newLevel: AlertLevel): void {
64 this._parkAlertLevel = newLevel;
65 console.log(`WARNING: Changed park alert level to: ${newLevel.toUpperCase()}`);
66 }
67
68 static get alertLevel(): AlertLevel {
69 return this._parkAlertLevel;
70 }
71
72 static get recentIncidents(): Array<{data: Date, systemType: string, location: string, description: string}> {
73 // Returning the 5 most recent incidents
74 return [...this._incydenty].slice(-5);
75 }
76
77 // Instance getters
78 get id(): string { return this._id; }
79 get location(): string { return this._location; }
80 get status(): StatusSystem { return this._status; }
81
82 // Implementation of MonitoringSystem interface
83 startMonitoring(): void {
84 this._isMonitored = true;
85 SecuritySystem._aktywneSystemy.add(this._id);
86 console.log(`Monitoring started for systemu ${this.constructor.name} w lokalizacji ${this._location}`);
87 }
88
89 stopMonitoring(): void {
90 this._isMonitored = false;
91 SecuritySystem._aktywneSystemy.delete(this._id);
92 console.log(`Monitoring stopped for systemu ${this.constructor.name} w lokalizacji ${this._location}`);
93 }
94
95 pobierzAktualnyStatus(): StatusSystem {
96 return this._status;
97 }
98
99 // Abstract methods that must be implemented
100 abstract uruchom(): void;
101 abstract zatrzymaj(): void;
102 abstract performReview(): void;
103
104 // Abstract properties
105 abstract get systemType(): string;
106 abstract get poziomPriorytetuAlarmowego(): number; // 1-10
107
108 // Method using abstract properties and methods
109 generateStatusReport(): string {
110 return `
111=== SECURITY SYSTEM REPORT ===
112ID: ${this._id}
113Typ: ${this.systemType}
114Lokalizacja: ${this._location}
115Status: ${this._status}
116Monitorowany: ${this._isMonitored ? "TAK" : "NIE"}
117Priorytet alarmowy: ${this.poziomPriorytetuAlarmowego}/10
118Last review: ${this._reviewDate.toLocaleDateString()}
119Global alert level: ${SecuritySystem._parkAlertLevel.toUpperCase()}
120=================================
121 `;
122 }
123}
124
125// Derived class - Electric Fence System
126class ElectricFenceSystem extends SecuritySystem {
127 private _voltage: number; // w woltach
128 private _fenceLength: number; // w metrach
129 private _securityType: "standard" | "reinforced" | "ultra-reinforced";
130
131 constructor(
132 id: string,
133 location: string,
134 fenceLength: number,
135 typZabezpieczenia: "standard" | "reinforced" | "ultra-reinforced"
136 ) {
137 super(id, location);
138 this._voltage = 0;
139 this._fenceLength = fenceLength;
140 this._securityType = typZabezpieczenia;
141 }
142
143 // Implementation of abstract properties
144 get systemType(): string {
145 return "Ogrodzenie Elektryczne";
146 }
147
148 get poziomPriorytetuAlarmowego(): number {
149 // Priority depends on the security type
150 const bazowy = this._securityType === "standard" ? 7 :
151 this._securityType === "reinforced" ? 8 : 10;
152
153 // We take fence length into account - longer ones have higher priority
154 const modyfikator = Math.min(2, Math.floor(this._fenceLength / 1000));
155
156 return Math.min(10, bazowy + modyfikator);
157 }
158
159 // Implementation of abstract methods
160 uruchom(): void {
161 if (this._status === "w_konserwacji") {
162 console.log(`ERROR: Cannot start system ${this._id} - is under maintenance`);
163 return;
164 }
165
166 this._status = "aktywny";
167
168 // Voltage depends on the security type
169 this._voltage = this._securityType === "standard" ? 10000 :
170 this._securityType === "reinforced" ? 20000 : 30000;
171
172 console.log(`Uruchomiono system ogrodzenia ${this._id} w lokalizacji ${this._location}`);
173 console.log(`Voltage: ${this._voltage}V, Length: ${this._fenceLength}m`);
174 }
175
176 zatrzymaj(): void {
177 this._status = "nieaktywny";
178 this._voltage = 0;
179 console.log(`Zatrzymano system ogrodzenia ${this._id} w lokalizacji ${this._location}`);
180
181 // Reporting an incident at high alert level
182 if (SecuritySystem.alertLevel === "high" || SecuritySystem.alertLevel === "critical") {
183 SecuritySystem.reportIncident(
184 this.systemType,
185 this._location,
186 `Fence shutdown at high alert level!`
187 );
188 }
189 }
190
191 performReview(): void {
192 const poprzedniStatus = this._status;
193 this._status = "w_konserwacji";
194 console.log(`Started fence system review ${this._id}`);
195
196 // Simulation of inspection
197 setTimeout(() => {
198 this._reviewDate = new Date();
199 this._status = poprzedniStatus;
200 console.log(`Completed fence system review ${this._id}`);
201 }, 2000);
202 }
203
204 // Methods specific to this system type
205 changeVoltage(newVoltage: number): void {
206 if (this._status !== "aktywny") {
207 console.log(`ERROR: Cannot change voltage - system is not active`);
208 return;
209 }
210
211 this._voltage = newVoltage;
212 console.log(`Changed fence voltage ${this._id} na ${newVoltage}V`);
213 }
214
215 reportFenceBreach(): void {
216 this._status = "awaria";
217 SecuritySystem.reportIncident(
218 this.systemType,
219 this._location,
220 `Electric fence breach detected!`
221 );
222 }
223}
224
225// Another derived class - Video Monitoring System
226class VideoMonitoringSystem extends SecuritySystem {
227 private _cameraCount: number;
228 private _resolution: "SD" | "HD" | "4K";
229 private _trybNocny: boolean;
230
231 constructor(
232 id: string,
233 location: string,
234 countKamer: number,
235 resolution: "SD" | "HD" | "4K"
236 ) {
237 super(id, location);
238 this._cameraCount = countKamer;
239 this._resolution = resolution;
240 this._trybNocny = false;
241 }
242
243 // Implementation of abstract properties
244 get systemType(): string {
245 return "Monitoring Wideo";
246 }
247
248 get poziomPriorytetuAlarmowego(): number {
249 // Priority depends on the number of cameras and resolution
250 const bazowy = 5;
251
252 // Resolution modifier
253 const resolutionModifier =
254 this._resolution === "SD" ? 0 :
255 this._resolution === "HD" ? 1 : 2;
256
257 // Camera count modifier
258 const cameraModifier = Math.min(2, Math.floor(this._cameraCount / 10));
259
260 return Math.min(10, bazowy + resolutionModifier + cameraModifier);
261 }
262
263 // Implementation of abstract methods
264 uruchom(): void {
265 this._status = "aktywny";
266 console.log(`Uruchomiono system monitoringu ${this._id} w lokalizacji ${this._location}`);
267 console.log(`Camera count: ${this._cameraCount}, Resolution: ${this._resolution}`);
268 }
269
270 zatrzymaj(): void {
271 this._status = "nieaktywny";
272 console.log(`Zatrzymano system monitoringu ${this._id} w lokalizacji ${this._location}`);
273 }
274
275 performReview(): void {
276 this._status = "w_konserwacji";
277 console.log(`Started monitoring system review ${this._id}`);
278
279 // In this case, the inspection completes immediately
280 this._reviewDate = new Date();
281 this._status = "aktywny";
282 console.log(`Completed monitoring system review ${this._id}`);
283 }
284
285 // Methods specific to this system type
286 enableNightMode(): void {
287 this._trybNocny = true;
288 console.log(`Enabled night mode in monitoring system ${this._id}`);
289 }
290
291 disableNightMode(): void {
292 this._trybNocny = false;
293 console.log(`Disabled night mode in monitoring system ${this._id}`);
294 }
295
296 detectMotion(area: string): void {
297 console.log(`System ${this._id}: Wykryto ruch w areaze ${area}`);
298
299 if (SecuritySystem.alertLevel !== "normall") {
300 SecuritySystem.reportIncident(
301 this.systemType,
302 `${this._location} - area ${area}`,
303 `Wykryto nieautoryzowany ruch!`
304 );
305 }
306 }
307}
308
309// Demonstration of Jurassic Park security system
310function testSecuritySystem() {
311 console.log("=== INITIALIZING JURASSIC PARK SECURITY SYSTEMS ===");
312
313 // Creating fence systems
314 const mainFence = new ElectricFenceSystem("FNC-001", "Sector A - Main entrance", 2500, "ultra-reinforced");
315 const tRexFence = new ElectricFenceSystem("FNC-002", "Sektor B - Wybieg T-Rex", 1200, "ultra-reinforced");
316 const raptorFence = new ElectricFenceSystem("FNC-003", "Sector C - Raptor Enclosure", 800, "reinforced");
317
318 // Creating monitoring systems
319 const mainMonitoring = new VideoMonitoringSystem("CAM-001", "Control center", 20, "4K");
320 const monitoringTRex = new VideoMonitoringSystem("CAM-002", "Sektor B - Wybieg T-Rex", 12, "HD");
321 const raptorMonitoring = new VideoMonitoringSystem("CAM-003", "Sector C - Raptor Enclosure", 8, "HD");
322
323 // Starting all systems
324 console.log("\n=== STARTING SYSTEMS ===");
325 mainFence.uruchom();
326 tRexFence.uruchom();
327 raptorFence.uruchom();
328
329 mainMonitoring.uruchom();
330 monitoringTRex.uruchom();
331 raptorMonitoring.uruchom();
332
333 // Starting monitoring
334 console.log("\n=== AKTYWACJA MONITOROWANIA ===");
335 mainFence.startMonitoring();
336 tRexFence.startMonitoring();
337 raptorFence.startMonitoring();
338
339 mainMonitoring.startMonitoring();
340 monitoringTRex.startMonitoring();
341 raptorMonitoring.startMonitoring();
342
343 // Checking statuses
344 console.log("\n=== SYSTEM REPORTS ===");
345 console.log(mainFence.generateStatusReport());
346 console.log(monitoringTRex.generateStatusReport());
347
348 // Simulating an incident
349 console.log("\n=== SYMULACJA INCYDENTU ===");
350 console.log("Current alert level:", SecuritySystem.alertLevel);
351
352 raptorMonitoring.detectMotion("South corner");
353 raptorFence.reportFenceBreach();
354
355 // Checking alert level after incidents
356 console.log("\nCurrent alert level after incidents:", SecuritySystem.alertLevel);
357
358 // Displaying recent incidents
359 console.log("\n=== OSTATNIE INCYDENTY ===");
360 SecuritySystem.recentIncidents.forEach((incydent, index) => {
361 console.log(`${index + 1}. [${incydent.data.toLocaleString()}] ${incydent.systemType} w ${incydent.location}: ${incydent.description}`);
362 });
363
364 // Conducting inspection
365 console.log("\n=== SYSTEM REVIEW ===");
366 tRexFence.performReview();
367 mainMonitoring.performReview();
368
369 // Manual alert level change
370 console.log("\n=== NORMALIZACJA SYTUACJI ===");
371 SecuritySystem.changeAlertLevel("normall");
372}
373
374// Running the test
375testSecuritySystem();In this advanced example:
- We use the abstract class
SecuritySystemas the base for all security systems - The class defines static properties and methods (e.g., park alert level, incident list)
- Abstract methods and properties enforce the implementation of specific functionality in derived classes
- Derived classes (
ElectricFenceSystemandVideoMonitoringSystem) implement the abstract elements - Static elements enable central coordination (e.g., reporting incidents and managing alert levels)
Combining Static and Abstract Elements
Static and abstract elements can be a powerful tool when used together in a well-designed class hierarchy. Here are some patterns and examples of their application:
1. Abstract Base Class with Static Factory Methods
1abstract class DinosaurSpecies {
2 // Static properties
3 private static _zarejestrowaneDinosaurs: Map<string, DinosaurSpecies> = new Map();
4
5 // Instance properties
6 private _name: string;
7 private _eraDominocenitryczna: string;
8 protected _dietType: "carnivore" | "herbivorous" | "omnivore";
9
10 constructor(nazwa: string, era: string, dieta: "carnivore" | "herbivorous" | "omnivore") {
11 this._name = name;
12 this._eraDominocenitryczna = era;
13 this._dietType = dieta;
14
15 // Adding to registry
16 DinosaurSpecies._zarejestrowaneDinosaury.set(nazwa, this);
17 }
18
19 // Static factory method - creates the appropriate species based on diet
20 static createSpecies(
21 nazwa: string,
22 era: string,
23 dieta: "carnivore" | "herbivorous" | "omnivore",
24 additionalParams: Record<string, any>
25 ): DinosaurSpecies {
26 if (dieta === "carnivore") {
27 return new CarnivoreSpecies(
28 nazwa,
29 era,
30 additionalParams.biteForce,
31 additionalParams.runSpeed
32 );
33 } else {
34 return new HerbivoreSpecies(
35 nazwa,
36 era,
37 additionalParams.neckLength || 0,
38 additionalParams.hasArmor || false
39 );
40 }
41 }
42
43 // Static method for retrieving registered species
44 static getSpecies(nazwa: string): DinosaurSpecies | undefined {
45 return this._zarejestrowaneDinosaury.get(nazwa);
46 }
47
48 static get allSpecies(): string[] {
49 return Array.from(this._zarejestrowaneDinosaury.keys());
50 }
51
52 // Instance getters
53 get nazwa(): string { return this._name; }
54 get era(): string { return this._eraDominocenitryczna; }
55 get typDiety(): string { return this._dietType; }
56
57 // Abstract methods
58 abstract descriptionZachowania(): string;
59 abstract estimateWeight(length: number): number;
60
61 // Method using abstract methods
62 generujOpis(): string {
63 return `${this._name} - dinosaur ${this._dietType} z ery ${this._eraDominocenitryczna}.
64${this.descriptionZachowania()}`;
65 }
66}
67
68// Derived classes
69class CarnivoreSpecies extends DinosaurSpecies {
70 private _biteForce: number; // w PSI
71 private _runSpeed: number; // w km/h
72
73 constructor(nazwa: string, era: string, biteForce: number, runSpeed: number) {
74 super(nazwa, era, "carnivore");
75 this._biteForce = biteForce;
76 this._runSpeed = runSpeed;
77 }
78
79 // Getters
80 get biteForce(): number { return this._biteForce; }
81 get runSpeed(): number { return this._runSpeed; }
82
83 // Implementation of abstract methods
84 descriptionZachowania(): string {
85 return `Hunts at speed ${this._runSpeed} km/h and bite force ${this._biteForce} PSI.`;
86 }
87
88 estimateWeight(length: number): number {
89 // Approximate formula for predators
90 return length * length * 100;
91 }
92}
93
94class HerbivoreSpecies extends DinosaurSpecies {
95 private _neckLength: number; // w metrach
96 private _hasArmor: boolean;
97
98 constructor(nazwa: string, era: string, neckLength: number, hasArmor: boolean) {
99 super(nazwa, era, "herbivorous");
100 this._neckLength = neckLength;
101 this._hasArmor = hasArmor;
102 }
103
104 // Getters
105 get neckLength(): number { return this._neckLength; }
106 get hasArmor(): boolean { return this._hasArmor; }
107
108 // Implementation of abstract methods
109 descriptionZachowania(): string {
110 const descriptionSzyi = this._neckLength > 3
111 ? `Has a long neck (${this._neckLength}m) allowing it to eat tall vegetation.`
112 : "Feeds on low vegetation.";
113
114 const descriptionPancerza = this._hasArmor
115 ? "Has armor for protection against predators."
116 : "Nie posiada pancerza ochronnego.";
117
118 return `${descriptionSzyi} ${descriptionPancerza}`;
119 }
120
121 estimateWeight(length: number): number {
122 // Approximate formula for herbivores (usually heavier)
123 let weight = length * length * 150;
124
125 // Additional weight for armor
126 if (this._hasArmor) {
127 weight *= 1.3;
128 }
129
130 return weight;
131 }
132}
133
134// Usage
135function testDinosaurSpecies() {
136 // Creating species using the factory method
137 DinosaurSpecies.createSpecies(
138 "Tyrannosaurus Rex",
139 "late Cretaceous",
140 "carnivore",
141 { biteForce: 12800, runSpeed: 32 }
142 );
143
144 DinosaurSpecies.createSpecies(
145 "Velociraptor",
146 "late Cretaceous",
147 "carnivore",
148 { biteForce: 2000, runSpeed: 64 }
149 );
150
151 DinosaurSpecies.createSpecies(
152 "Brachiosaurus",
153 "late Jurassic",
154 "herbivorous",
155 { neckLength: 9, hasArmor: false }
156 );
157
158 DinosaurSpecies.createSpecies(
159 "Stegosaurus",
160 "late Jurassic",
161 "herbivorous",
162 { neckLength: 0.5, hasArmor: true }
163 );
164
165 // Accessing registered species
166 console.log("Available species:", DinosaurSpecies.allSpecies);
167
168 // Retrieving species information
169 const trex = DinosaurSpecies.getSpecies("Tyrannosaurus Rex");
170 const brach = DinosaurSpecies.getSpecies("Brachiosaurus");
171
172 if (trex) {
173 console.log(trex.generujOpis());
174 console.log(`Estimated weight (12m long): ${trex.estimateWeight(12)} kg`);
175 }
176
177 if (brach) {
178 console.log(brach.generujOpis());
179 console.log(`Estimated weight (25m long): ${brach.estimateWeight(25)} kg`);
180 }
181
182 // Type checking
183 const steg = DinosaurSpecies.getSpecies("Stegosaurus") as HerbivoreSpecies;
184 if (steg && steg instanceof HerbivoreSpecies) {
185 console.log(`Stegosaurus posiada pancerz: ${steg.hasArmor ? "TAK" : "NIE"}`);
186 }
187
188 const raptor = DinosaurSpecies.getSpecies("Velociraptor") as CarnivoreSpecies;
189 if (raptor && raptor instanceof CarnivoreSpecies) {
190 console.log(`Velociraptor - run speed: ${raptor.runSpeed} km/h`);
191 }
192}
193
194testDinosaurSpecies();2. Abstract Static Methods
In TypeScript since version 4.0, it's possible to define abstract static methods. This enables creating class hierarchies where derived classes must implement specific static methods:
1abstract class LocationSystem {
2 // Instance properties
3 protected _id: string;
4 protected _nazwa: string;
5
6 constructor(id: string, nazwa: string) {
7 this._id = id;
8 this._name = name;
9 }
10
11 // Abstract static method that derived classes must implement
12 static abstract generateLocationId(typ: string, numer: number): string;
13
14 // Regular abstract instance method
15 abstract pobierzInformacjeOLokacji(): Record<string, any>;
16
17 // Instance method
18 generateReport(): string {
19 const info = this.pobierzInformacjeOLokacji();
20
21 let raport = `=== RAPORT LOKACJI: ${this._name} (ID: ${this._id}) ===\n`;
22
23 for (const [klucz, value] of Object.entries(info)) {
24 raport += `${klucz}: ${value}\n`;
25 }
26
27 return raport;
28 }
29}
30
31class DinosaurEnclosure extends LocationSystem {
32 private _gatunek: string;
33 private _powierzchnia: number; // w hektarach
34 private _specimenCount: number;
35
36 constructor(numer: number, nazwa: string, gatunek: string, powierzchnia: number, specimenCount: number) {
37 // Generating ID for the enclosure
38 const id = DinosaurEnclosure.generateLocationId("wybieg", numer);
39 super(id, nazwa);
40
41 this._species = species;
42 this._powierzchnia = powierzchnia;
43 this._specimenCount = specimenCount;
44 }
45
46 // Implementation of abstract static method
47 static generateLocationId(typ: string, numer: number): string {
48 return `${typ.toUpperCase()}-${numer.toString().padStart(3, '0')}`;
49 }
50
51 // Implementation of abstract instance method
52 pobierzInformacjeOLokacji(): Record<string, any> {
53 return {
54 typ: "Dinosaur enclosure",
55 gatunek: this._gatunek,
56 powierzchnia: `${this._powierzchnia} ha`,
57 specimenCount: this._specimenCount,
58 populationDensity: `${(this._specimenCount / this._powierzchnia).toFixed(2)} spec./ha`
59 };
60 }
61
62 // Methods specific to this class
63 addSpecimen(): void {
64 this._specimenCount++;
65 console.log(`Added new specimen to enclosure ${this._name}. New count: ${this._specimenCount}`);
66 }
67
68 removeSpecimen(): boolean {
69 if (this._specimenCount > 0) {
70 this._specimenCount--;
71 console.log(`Removed specimen from enclosure ${this._name}. New count: ${this._specimenCount}`);
72 return true;
73 }
74
75 console.log(`Error: Cannot remove specimen from enclosure ${this._name} - no specimens.`);
76 return false;
77 }
78}
79
80class TouristAttraction extends LocationSystem {
81 private _attractionType: string;
82 private _capacity: number;
83 private _czasOczekiwania: number; // w minutach
84
85 constructor(numer: number, nazwa: string, attractionType: string, capacity: number) {
86 // Generating ID for the attraction
87 const id = TouristAttraction.generateLocationId("atrakcja", numer);
88 super(id, nazwa);
89
90 this._attractionType = attractionType;
91 this._capacity = capacity;
92 this._czasOczekiwania = 0;
93 }
94
95 // Implementation of abstract static method
96 static generateLocationId(typ: string, numer: number): string {
97 // Different ID format for attractions
98 return `${typ.substring(0, 3).toUpperCase()}${numer.toString().padStart(4, '0')}`;
99 }
100
101 // Implementation of abstract instance method
102 pobierzInformacjeOLokacji(): Record<string, any> {
103 return {
104 typ: "Atrakcja turystyczna",
105 category: this._attractionType,
106 capacity: this._capacity,
107 czasOczekiwania: `${this._czasOczekiwania} min`
108 };
109 }
110
111 // Methods specific to this class
112 aktualizujCzasOczekiwania(nowyOczekiwanieCzas: number): void {
113 this._czasOczekiwania = nowyOczekiwanieCzas;
114 console.log(`Zaktualizowano czas oczekiwania dla ${this._name}: ${this._czasOczekiwania} min`);
115 }
116}
117
118// Application
119function testLocationSystem() {
120 // Creating enclosures
121 const tRexEnclosure = new DinosaurEnclosure(1, "T-Rex Kingdom", "Tyrannosaurus Rex", 5, 1);
122 const raptorEnclosure = new DinosaurEnclosure(2, "Raptor Valley", "Velociraptor", 3, 6);
123
124 // Creating attractions
125 const visitorCenter = new TouristAttraction(101, "Genetics Center", "educational", 500);
126 const jeepSafari = new TouristAttraction(102, "Jurassic Safari", "ride", 50);
127
128 // Displaying reports
129 console.log(wybiegTRex.generateReport());
130 console.log(raptorEnclosure.generateReport());
131 console.log(visitorCenter.generateReport());
132 console.log(jeepSafari.generateReport());
133
134 // Performing operations
135 raptorEnclosure.addSpecimen();
136 raptorEnclosure.addSpecimen();
137 jeepSafari.aktualizujCzasOczekiwania(45);
138
139 // Checking updated data
140 console.log(raptorEnclosure.generateReport());
141 console.log(jeepSafari.generateReport());
142
143 // Demonstration of different ID formats generated by static method implementations
144 console.log("Enclosure ID format:", DinosaurEnclosure.generateLocationId("wybieg", 42));
145 console.log("Format ID atrakcji:", TouristAttraction.generateLocationId("atrakcja", 42));
146}
147
148testLocationSystem();Summary
In this exercise, we learned about two powerful TypeScript mechanisms:
Static properties and methods:
- Belong to the class, not to instances
- Enable sharing data and functionality between all instances
- Ideal for defining constants, global state, and utility methods
- Accessible directly through the class name, without creating instances
Abstract classes and methods:
- Define a "template" or "contract" that must be implemented by inheriting classes
- Cannot be instantiated directly
- Enforce the implementation of specific methods in derived classes
- Allow sharing common functionality through a class hierarchy
By combining these concepts, we can create elegant, modular, and type-safe systems that are ideal for modeling complex relationships and processes, such as those in the Jurassic Park management system.
Dr. Henry Wu would summarize it as follows: "In nature, as in programming, there is beauty in structure and order. Static elements ensure consistency for the entire species, while abstract concepts form the basic blueprint that each organism must implement in its own way. Together they create perfect harmony - with proper safeguards, of course... in most cases."
Code for this lesson: index.ts
1// Static properties and abstract classes - Jurassic Park
2console.log("=== STATIC PROPERTIES AND ABSTRACT CLASSES ===\n");
3
4// ===========================================
5// 1. The abstract class Dinosaur
6// ===========================================
7
8abstract class Dinosaur {
9 // Static counter for all dinosaurs
10 static totalCount: number = 0;
11 static readonly PARK_NAME: string = "Jurassic World";
12
13 // Static factory method
14 static createId(): string {
15 return `DINO-${String(++Dinosaur.totalCount).padStart(4, "0")}`;
16 }
17
18 // Instance fields
19 readonly id: string;
20 name: string;
21 protected weight: number;
22
23 constructor(name: string, weight: number) {
24 this.id = Dinosaur.createId();
25 this.name = name;
26 this.weight = weight;
27 }
28
29 // Abstract method - each species must implement it
30 abstract makeSound(): string;
31 abstract getDangerLevel(): number;
32
33 // Common method
34 getProfile(): string {
35 return `[${this.id}] ${this.name} - ${this.weight}kg`;
36 }
37}
38
39// ===========================================
40// 2. Implementations of concrete classes
41// ===========================================
42
43class TRex extends Dinosaur {
44 private biteForce: number;
45
46 constructor(name: string, weight: number, biteForce: number) {
47 super(name, weight);
48 this.biteForce = biteForce;
49 }
50
51 makeSound(): string {
52 return "ROOOOAAAR!";
53 }
54
55 getDangerLevel(): number {
56 return 10;
57 }
58}
59
60class Triceratops extends Dinosaur {
61 private hornLength: number;
62
63 constructor(name: string, weight: number, hornLength: number) {
64 super(name, weight);
65 this.hornLength = hornLength;
66 }
67
68 makeSound(): string {
69 return "BRRRRR!";
70 }
71
72 getDangerLevel(): number {
73 return 4;
74 }
75}
76
77// ===========================================
78// 3. Static factory method
79// ===========================================
80
81class DinosaurFactory {
82 static registry: Map<string, Dinosaur> = new Map();
83
84 static create(type: "trex" | "triceratops", name: string): Dinosaur {
85 let dino: Dinosaur;
86 if (type === "trex") {
87 dino = new TRex(name, 8000, 12800);
88 } else {
89 dino = new Triceratops(name, 6000, 90);
90 }
91 DinosaurFactory.registry.set(dino.id, dino);
92 return dino;
93 }
94
95 static getAll(): Dinosaur[] {
96 return Array.from(DinosaurFactory.registry.values());
97 }
98}
99
100// ===========================================
101// Test
102// ===========================================
103
104const rex = DinosaurFactory.create("trex", "Rexy");
105const trike = DinosaurFactory.create("triceratops", "Cera");
106const rex2 = DinosaurFactory.create("trex", "Buck");
107
108console.log("Park:", Dinosaur.PARK_NAME);
109console.log("Number of dinosaurs:", Dinosaur.totalCount);
110console.log("");
111
112DinosaurFactory.getAll().forEach(dino => {
113 console.log(dino.getProfile());
114 console.log(" Sound:", dino.makeSound());
115 console.log(" Danger:", dino.getDangerLevel(), "/10");
116});
117
118// You cannot create an instance of an abstract class:
119// const dino = new Dinosaur("Test", 100); // Error!
120console.log("\nAn abstract class cannot be instantiated directly.");Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is an abstract class in TypeScript?
2. How do static properties differ from regular ones in a class?
Hands-on tasks in the game
- Code editor
Implement classes with static and abstract properties.
- Click in order
Arrange the elements of an abstract class declaration:
- Code editor
Create classes with different access modifiers for the park system