We use cookies to enhance your experience on the site
CodeWorlds

Static and Abstract Properties

"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:

  1. Threat level constants are static properties, accessible directly through the class
  2. The static property
    _currentThreatLevel
    is shared across all instances
  3. Static methods can modify static properties and call other static methods
  4. 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:

  1. Constants and configuration - Values that remain unchanged for all instances of a class
  2. Global state - When we need a value shared by all instances
  3. Utility methods - Helper functions that don't require instance state
  4. 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:

  1. Using static constants (
    NUCLEOTIDE_BASE
    ,
    MIN_DNA_LENGTH
    )
  2. Global state tracking the number and registration of species
  3. Static utility methods like
    verifySequence
    and
    wygenerujId
  4. A factory method
    createTestSpecies
    creating instances with random data
  5. 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:

  1. Dinosaur
    is an abstract class containing shared properties and methods
  2. Abstract methods
    calculateCaloricNeeds()
    and
    determineThreatLevel()
    must be implemented by inheriting classes
  3. The abstract property
    typDiety
    must be defined in derived classes
  4. Classes
    PredatoryDinosaur
    and
    HerbivorousDinosaur
    implement the abstract methods and properties
  5. You cannot create an instance of the
    Dinosaur
    class directly

Applications of Abstract Classes and Methods

Abstract classes are especially useful in the following cases:

  1. Defining a common interface - Specifying a consistent set of methods for a group of related classes
  2. Sharing code - Providing common implementation for groups of classes
  3. Enforcing a contract - Guaranteeing that all derived classes will implement the required methods
  4. 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:

  1. We use the abstract class
    SecuritySystem
    as the base for all security systems
  2. The class defines static properties and methods (e.g., park alert level, incident list)
  3. Abstract methods and properties enforce the implementation of specific functionality in derived classes
  4. Derived classes (
    ElectricFenceSystem
    and
    VideoMonitoringSystem
    ) implement the abstract elements
  5. 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:

  1. 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
  2. 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."

Go to CodeWorlds