Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Projekt końcowy: Typowany system zarządzania Parkiem Jurajskim

Gratulacje - dotarłeś do końcowego projektu modułu o podstawach TypeScript! Przez ostatnie lekcje poznałeś potężne narzędzia typowania: od podstawowych typów, przez tablice, krotki i enumy, aż po interfejsy, typy unii i aliasy. Teraz nadszedł czas, aby połączyć tę wiedzę w jeden kompleksowy projekt.

Wyobraź sobie, że zespół programistów Parku Jurajskiego w końcu postanowił przepisać swoje systemy z czystego JavaScriptu na TypeScript. Po zbyt wielu incydentach spowodowanych błędami typów - od źle przekazanych parametrów zasilania, przez pomylone gatunki dinozaurów, po awarie systemu bezpieczeństwa z powodu

undefined
- zarząd podjął decyzję: "Nigdy więcej!". Twoim zadaniem jest stworzenie fundamentu nowego, w pełni typowanego systemu zarządzania parkiem.

W tym projekcie zbudujesz kompletny system typów i interfejsów dla Parku Jurajskiego, który zapewni bezpieczeństwo typów na każdym poziomie aplikacji.

Cel projektu

Stworzysz typowany system zarządzania Parkiem Jurajskim składający się z czterech obszarów:

  1. System typów dla dinozaurów - enumy, interfejsy i unie opisujące wszystkie aspekty dinozaurów
  2. System zarządzania sektorami - interfejsy i typy dla infrastruktury parku
  3. System bezpieczeństwa - typy zdarzeń, alertów i protokołów
  4. Centralny system zarządzania - łączenie wszystkich typów w spójny system

Wymagania funkcjonalne

1. System typów dla dinozaurów (Enumy, interfejsy, aliasy)

Pierwszy obszar to precyzyjny model danych opisujący dinozaury w parku. Używamy enumów dla stałych wartości, interfejsów dla struktury obiektów i aliasów typów dla złożonych kombinacji.

1// Enumy - stałe wartości w systemie
2enum Diet {
3  Carnivore = "mięsożerca",
4  Herbivore = "roślinożerca",
5  Omnivore = "wszystkożerca",
6  Piscivore = "rybożerca"
7}
8
9enum Era {
10  Triassic = "trias",
11  Jurassic = "jura",
12  Cretaceous = "kreda"
13}
14
15enum ThreatLevel {
16  Minimal = 1,
17  Low = 2,
18  Moderate = 3,
19  High = 4,
20  Extreme = 5
21}
22
23enum DinoStatus {
24  Healthy = "zdrowy",
25  Sick = "chory",
26  Sleeping = "śpi",
27  Feeding = "je",
28  Agitated = "pobudzony",
29  Escaped = "uciekł"
30}
31
32// Typ aliasowy - gatunek dinozaura
33type Species =
34  | "Tyrannosaurus Rex"
35  | "Velociraptor"
36  | "Triceratops"
37  | "Brachiosaurus"
38  | "Stegosaurus"
39  | "Pteranodon"
40  | "Dilophosaurus"
41  | "Gallimimus"
42  | "Parasaurolophus"
43  | "Ankylosaurus";
44
45// Interfejs - dane medyczne dinozaura
46interface DinoHealth {
47  weight: number;         // w kilogramach
48  height: number;         // w metrach
49  heartRate: number;      // uderzenia na minutę
50  temperature: number;    // stopnie Celsjusza
51  lastFed: Date;
52  lastCheckup: Date;
53  conditions: string[];   // lista stanów zdrowotnych
54}
55
56// Interfejs - pełny profil dinozaura
57interface Dinosaur {
58  id: string;
59  name: string;
60  species: Species;       // Typ aliasowy
61  diet: Diet;             // Enum
62  era: Era;               // Enum
63  threatLevel: ThreatLevel; // Enum
64  status: DinoStatus;     // Enum
65  health: DinoHealth;     // Zagnieżdżony interfejs
66  sectorId: string;
67  trackingChipActive: boolean;
68  dateAdded: Date;
69}
70
71// Typ unii - filtr wyszukiwania dinozaurów
72type DinoSearchCriteria =
73  | { by: "species"; value: Species }
74  | { by: "diet"; value: Diet }
75  | { by: "threatLevel"; minLevel: ThreatLevel }
76  | { by: "status"; value: DinoStatus }
77  | { by: "sector"; sectorId: string };
78
79// Funkcja z typami
80function searchDinosaurs(
81  dinosaurs: Dinosaur[],
82  criteria: DinoSearchCriteria
83): Dinosaur[] {
84  switch (criteria.by) {
85    case "species":
86      return dinosaurs.filter(d => d.species === criteria.value);
87    case "diet":
88      return dinosaurs.filter(d => d.diet === criteria.value);
89    case "threatLevel":
90      return dinosaurs.filter(d => d.threatLevel >= criteria.minLevel);
91    case "status":
92      return dinosaurs.filter(d => d.status === criteria.value);
93    case "sector":
94      return dinosaurs.filter(d => d.sectorId === criteria.sectorId);
95  }
96}

Zauważ, jak TypeScript wymusza kompletność obsługi w

switch
- jeśli dodasz nowy wariant do
DinoSearchCriteria
, kompilator przypomni Ci o obsłużeniu nowego przypadku.

2. System zarządzania sektorami (Interfejsy i typy złożone)

Drugi obszar to model infrastruktury parku - sektory, ogrodzenia, systemy zasilania i monitoring.

1// Typ unii - rodzaj ogrodzenia
2type FenceType = "electric" | "concrete" | "reinforced" | "moat";
3
4// Interfejs ogrodzenia
5interface Fence {
6  id: string;
7  type: FenceType;
8  voltage: number;          // 0 dla nie-elektrycznych
9  integrity: number;        // 0-100%
10  lastMaintenance: Date;
11  requiresRepair: boolean;
12}
13
14// Enum - status sektora
15enum SectorStatus {
16  Operational = "operacyjny",
17  Maintenance = "konserwacja",
18  Lockdown = "blokada",
19  Evacuated = "ewakuowany",
20  Offline = "offline"
21}
22
23// Interfejs - system zasilania
24interface PowerSystem {
25  mainPowerLevel: number;     // 0-100%
26  backupPowerLevel: number;   // 0-100%
27  isBackupActive: boolean;
28  solarPanelsOnline: number;  // liczba aktywnych paneli
29  generatorStatus: "running" | "standby" | "failed";
30}
31
32// Interfejs sektora z zagnieżdżonymi typami
33interface Sector {
34  id: string;
35  name: string;
36  status: SectorStatus;
37  dinosaurs: Dinosaur[];
38  fences: Fence[];
39  power: PowerSystem;
40  capacity: number;          // maksymalna liczba dinozaurów
41  securityLevel: ThreatLevel;
42  staff: StaffMember[];
43}
44
45// Typ aliasowy - rola pracownika
46type StaffRole =
47  | "weterynarz"
48  | "strażnik"
49  | "technik"
50  | "naukowiec"
51  | "kierownik"
52  | "przewodnik";
53
54// Interfejs pracownika
55interface StaffMember {
56  id: string;
57  name: string;
58  role: StaffRole;
59  assignedSector: string;
60  clearanceLevel: ThreatLevel;  // Reużycie enuma w innym kontekście
61  onDuty: boolean;
62  specializations: string[];
63}
64
65// Typ - podsumowanie stanu sektora
66type SectorSummary = {
67  sectorId: string;
68  sectorName: string;
69  status: SectorStatus;
70  dinoCount: number;
71  avgFenceIntegrity: number;
72  powerStatus: "stabilne" | "niskie" | "krytyczne";
73  staffOnDuty: number;
74  alerts: number;
75};
76
77// Funkcja generująca podsumowanie
78function getSectorSummary(sector: Sector): SectorSummary {
79  const avgFence = sector.fences.reduce(
80    (sum, f) => sum + f.integrity, 0
81  ) / sector.fences.length;
82
83  const powerStatus: SectorSummary["powerStatus"] =
84    sector.power.mainPowerLevel > 60 ? "stabilne" :
85    sector.power.mainPowerLevel > 30 ? "niskie" : "krytyczne";
86
87  return {
88    sectorId: sector.id,
89    sectorName: sector.name,
90    status: sector.status,
91    dinoCount: sector.dinosaurs.length,
92    avgFenceIntegrity: Math.round(avgFence),
93    powerStatus,
94    staffOnDuty: sector.staff.filter(s => s.onDuty).length,
95    alerts: 0
96  };
97}

Interfejsy budują się hierarchicznie -

Sector
zawiera tablice
Dinosaur[]
,
Fence[]
i
StaffMember[]
, tworząc bogaty model danych.

3. System bezpieczeństwa (Typy unii i aliasy)

Trzeci obszar to system zdarzeń i alertów bezpieczeństwa, który intensywnie korzysta z typów unii, aby precyzyjnie modelować różne rodzaje incydentów.

1// Typy unii dla zdarzeń bezpieczeństwa - każde zdarzenie ma inną strukturę
2type SecurityEvent =
3  | {
4      type: "fence_breach";
5      sectorId: string;
6      fenceId: string;
7      dinosaurId: string;
8      severity: "warning" | "critical";
9    }
10  | {
11      type: "power_outage";
12      sectorId: string;
13      affectedSystems: string[];
14      backupActivated: boolean;
15    }
16  | {
17      type: "dinosaur_escape";
18      dinosaurId: string;
19      species: Species;
20      lastKnownSector: string;
21      threatLevel: ThreatLevel;
22    }
23  | {
24      type: "sensor_failure";
25      sectorId: string;
26      sensorIds: string[];
27      possibleCause: "damage" | "power" | "sabotage" | "unknown";
28    }
29  | {
30      type: "unauthorized_access";
31      location: string;
32      personId: string | null;
33      timestamp: Date;
34    };
35
36// Enum - priorytet alertu
37enum AlertPriority {
38  Info = "INFO",
39  Warning = "UWAGA",
40  Danger = "ZAGROŻENIE",
41  Critical = "KRYTYCZNY",
42  Evacuation = "EWAKUACJA"
43}
44
45// Interfejs alertu
46interface SecurityAlert {
47  id: string;
48  event: SecurityEvent;
49  priority: AlertPriority;
50  createdAt: Date;
51  acknowledgedBy: string | null;  // ID pracownika lub null
52  resolved: boolean;
53  notes: string[];
54}
55
56// Typ - protokół odpowiedzi na zagrożenie
57type ResponseProtocol = {
58  alertId: string;
59  actions: ProtocolAction[];
60  estimatedTime: number;     // w minutach
61  requiredClearance: ThreatLevel;
62};
63
64// Typ unii - akcja protokołu
65type ProtocolAction =
66  | { action: "lockdown"; sectors: string[] }
67  | { action: "evacuate"; sectors: string[]; priority: "staff" | "visitors" | "all" }
68  | { action: "deploy_team"; teamType: StaffRole; targetSector: string }
69  | { action: "activate_backup"; systems: string[] }
70  | { action: "track_dinosaur"; dinosaurId: string; useHelicopter: boolean };
71
72// Funkcja obsługi zdarzenia z type narrowing
73function handleSecurityEvent(event: SecurityEvent): ResponseProtocol {
74  const actions: ProtocolAction[] = [];
75  let estimatedTime = 10;
76  let requiredClearance = ThreatLevel.Moderate;
77
78  switch (event.type) {
79    case "fence_breach":
80      actions.push({ action: "lockdown", sectors: [event.sectorId] });
81      actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
82      if (event.severity === "critical") {
83        actions.push({ action: "evacuate", sectors: [event.sectorId], priority: "visitors" });
84        requiredClearance = ThreatLevel.High;
85      }
86      break;
87
88    case "dinosaur_escape":
89      actions.push({ action: "track_dinosaur", dinosaurId: event.dinosaurId, useHelicopter: event.threatLevel >= ThreatLevel.High });
90      actions.push({ action: "lockdown", sectors: [event.lastKnownSector] });
91      actions.push({ action: "deploy_team", teamType: "strażnik", targetSector: event.lastKnownSector });
92      estimatedTime = 60;
93      requiredClearance = ThreatLevel.Extreme;
94      break;
95
96    case "power_outage":
97      actions.push({ action: "activate_backup", systems: event.affectedSystems });
98      actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
99      if (!event.backupActivated) {
100        actions.push({ action: "lockdown", sectors: [event.sectorId] });
101        requiredClearance = ThreatLevel.High;
102      }
103      break;
104
105    case "sensor_failure":
106      actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
107      if (event.possibleCause === "sabotage") {
108        actions.push({ action: "lockdown", sectors: [event.sectorId] });
109        requiredClearance = ThreatLevel.Extreme;
110      }
111      break;
112
113    case "unauthorized_access":
114      actions.push({ action: "deploy_team", teamType: "strażnik", targetSector: event.location });
115      break;
116  }
117
118  return {
119    alertId: `PROTOCOL-${Date.now()}`,
120    actions,
121    estimatedTime,
122    requiredClearance
123  };
124}

Type narrowing w

switch
pozwala TypeScriptowi automatycznie zawęzić typ zdarzenia - w bloku
case "fence_breach"
TypeScript wie, że
event
ma pola
fenceId
i
severity
.

4. Centralny system zarządzania (Łączenie typów)

Ostatni obszar to centralny system, który łączy wszystkie powyższe typy w spójny interfejs zarządzania całym parkiem.

1// Interfejs całego parku
2interface JurassicPark {
3  name: string;
4  sectors: Sector[];
5  totalDinosaurs: number;
6  activeAlerts: SecurityAlert[];
7  operationalStatus: "open" | "restricted" | "closed" | "emergency";
8}
9
10// Typ - raport dzienny
11type DailyReport = {
12  date: string;
13  parkStatus: JurassicPark["operationalStatus"]; // Reużycie typu z interfejsu
14  sectorSummaries: SectorSummary[];
15  incidentCount: number;
16  resolvedIncidents: number;
17  dinosaurStats: {
18    total: number;
19    byDiet: Record<Diet, number>;       // Record z enumem jako kluczem
20    byStatus: Record<DinoStatus, number>;
21    escaped: number;
22  };
23  staffStats: {
24    totalOnDuty: number;
25    byRole: Record<StaffRole, number>;  // Record z type alias jako kluczem
26  };
27  recommendations: string[];
28};
29
30// Funkcja generująca raport
31function generateDailyReport(park: JurassicPark): DailyReport {
32  // Inicjalizacja liczników z enumami
33  const byDiet: Record<Diet, number> = {
34    [Diet.Carnivore]: 0,
35    [Diet.Herbivore]: 0,
36    [Diet.Omnivore]: 0,
37    [Diet.Piscivore]: 0
38  };
39
40  const byStatus: Record<DinoStatus, number> = {
41    [DinoStatus.Healthy]: 0,
42    [DinoStatus.Sick]: 0,
43    [DinoStatus.Sleeping]: 0,
44    [DinoStatus.Feeding]: 0,
45    [DinoStatus.Agitated]: 0,
46    [DinoStatus.Escaped]: 0
47  };
48
49  // Zliczanie dinozaurów
50  const allDinos = park.sectors.flatMap(s => s.dinosaurs);
51  allDinos.forEach(dino => {
52    byDiet[dino.diet]++;
53    byStatus[dino.status]++;
54  });
55
56  // Podsumowania sektorów
57  const sectorSummaries = park.sectors.map(getSectorSummary);
58
59  // Rekomendacje na podstawie danych
60  const recommendations: string[] = [];
61
62  if (byStatus[DinoStatus.Escaped] > 0) {
63    recommendations.push(`KRYTYCZNE: ${byStatus[DinoStatus.Escaped]} dinozaurów uciekło!`);
64  }
65
66  if (byStatus[DinoStatus.Agitated] > allDinos.length * 0.3) {
67    recommendations.push("UWAGA: Ponad 30% dinozaurów jest pobudzonych - sprawdzić przyczynę");
68  }
69
70  const lowPowerSectors = sectorSummaries.filter(s => s.powerStatus === "krytyczne");
71  if (lowPowerSectors.length > 0) {
72    recommendations.push(
73      `Krytyczny poziom zasilania w sektorach: ${lowPowerSectors.map(s => s.sectorName).join(", ")}`
74    );
75  }
76
77  return {
78    date: new Date().toISOString().split("T")[0],
79    parkStatus: park.operationalStatus,
80    sectorSummaries,
81    incidentCount: park.activeAlerts.length,
82    resolvedIncidents: park.activeAlerts.filter(a => a.resolved).length,
83    dinosaurStats: {
84      total: allDinos.length,
85      byDiet,
86      byStatus,
87      escaped: byStatus[DinoStatus.Escaped]
88    },
89    staffStats: {
90      totalOnDuty: park.sectors.flatMap(s => s.staff).filter(s => s.onDuty).length,
91      byRole: {
92        weterynarz: 0, strażnik: 0, technik: 0,
93        naukowiec: 0, kierownik: 0, przewodnik: 0
94      }
95    },
96    recommendations
97  };
98}

Wskazówki implementacyjne

Przy projektowaniu systemu typów pamiętaj o kluczowych zasadach:

Używaj enumów dla stałych zbiorów wartości:

1// Dobrze - wartości są stałe i znane z góry
2enum Diet { Carnivore = "mięsożerca", Herbivore = "roślinożerca" }
3
4// Źle - zbyt dynamiczne dane dla enuma
5enum DinosaurName { Rex = "Rex", Blue = "Blue" } // Imiona mogą być dowolne

Interfejsy dla struktur obiektów, aliasy dla unii i kombinacji:

1// Interfejs - opisuje kształt obiektu
2interface Dinosaur {
3  name: string;
4  species: Species;
5}
6
7// Alias - łączy typy, tworzy warianty
8type SearchResult = Dinosaur | null;
9type DinoFilter = (dino: Dinosaur) => boolean;
10type DinoOrError = { success: true; data: Dinosaur } | { success: false; error: string };

Typy unii dyskryminowane (discriminated unions) dla zdarzeń:

1// Pole "type" pozwala TypeScriptowi rozróżniać warianty
2type Event =
3  | { type: "A"; fieldA: string }
4  | { type: "B"; fieldB: number };
5
6function handle(event: Event) {
7  if (event.type === "A") {
8    // TypeScript wie, że tu jest fieldA
9    console.log(event.fieldA);
10  }
11}

Record do tworzenia mapowań z enumów:

1// Automatyczna mapa enum → wartość
2const dietLabels: Record<Diet, string> = {
3  [Diet.Carnivore]: "Mięsożerca - karmić mięsem",
4  [Diet.Herbivore]: "Roślinożerca - karmić roślinami",
5  [Diet.Omnivore]: "Wszystkożerca - dieta mieszana",
6  [Diet.Piscivore]: "Rybożerca - karmić rybami"
7};

Powodzenia!

Ten projekt to Twoja szansa na udowodnienie, że rozumiesz moc systemu typów TypeScript. W prawdziwym Parku Jurajskim każda pomyłka może kosztować życie - a TypeScript to Twoje ogrodzenie chroniące przed błędami typów. Jak powiedział Dr. Alan Grant: "Dinozaury i człowiek - dwa gatunki oddzielone przez 65 milionów lat ewolucji - nagle znów muszą żyć obok siebie." A TypeScript sprawia, że ta koegzystencja jest bezpieczna i przewidywalna.

Stwórz swój typowany system zarządzania w edytorze poniżej. Zdefiniuj przynajmniej trzy enumy, cztery interfejsy, dwa typy unii i jedną funkcję wykorzystującą te typy.

Vai a CodeWorlds