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

Projekt końcowy: Zaawansowany system typów dla Parku Jurajskiego

"Nadszedł czas na podsumowanie," ogłasza Dr. Henry Wu, główny genetyk InGen, stojąc przed ogromnym holoekranem laboratorium. "Przez ostatnie tygodnie opanowałeś zaawansowane mechanizmy TypeScript - od modyfikatorów dostępu, przez generyki, po typy warunkowe i mapowane. Teraz zbudujesz kompletny system zarządzania Parkiem Jurajskim, który połączy wszystkie te elementy w jedną, spójną całość."

W tym projekcie stworzysz zaawansowany system typów dla Parku Jurajskiego. Wykorzystasz klasy abstrakcyjne, interfejsy, generyki, typy warunkowe, typy mapowane, utility types i dekoratory - wszystko to, czego nauczyłeś się w tym module. System będzie obsługiwał rejestrację dinozaurów, zarządzanie zagrodami, monitorowanie stanu zdrowia i generowanie raportów bezpieczeństwa.

Cel projektu

Zbudujesz kompletny, w pełni typowany system

JurassicParkManager
, który:

  • Wykorzystuje klasy abstrakcyjne jako bazę dla różnych kategorii dinozaurów
  • Implementuje interfejsy definiujące kontrakty dla podsystemów parku
  • Używa generyków do tworzenia elastycznych struktur danych
  • Stosuje typy warunkowe do dynamicznego określania zachowań
  • Wykorzystuje typy mapowane do transformacji danych
  • Korzysta z utility types do precyzyjnego modelowania

Wymagania funkcjonalne

1. Hierarchia klas dinozaurów z klasami abstrakcyjnymi i interfejsami

Zdefiniuj abstrakcyjną klasę bazową

Dinosaur
z właściwościami chronionymi i abstrakcyjnymi metodami. Następnie stwórz interfejsy opisujące różne zdolności dinozaurów i zaimplementuj je w klasach potomnych.

1// Interfejsy zdolności
2interface IHerbivore {
3  graze(area: string): string;
4  preferredPlants: string[];
5}
6
7interface ICarnivore {
8  hunt(target: string): string;
9  attackPower: number;
10}
11
12interface IFlying {
13  fly(altitude: number): string;
14  maxAltitude: number;
15}
16
17interface ISwimming {
18  swim(depth: number): string;
19  maxDepth: number;
20}
21
22// Klasa abstrakcyjna z modyfikatorami dostępu
23abstract class Dinosaur {
24  private static nextId: number = 1;
25  
26  protected readonly id: number;
27  public readonly species: string;
28  protected health: number;
29  private enclosureId: string | null = null;
30
31  constructor(species: string, health: number = 100) {
32    this.id = Dinosaur.nextId++;
33    this.species = species;
34    this.health = health;
35  }
36
37  // Metoda abstrakcyjna - każdy podtyp definiuje swój poziom zagrożenia
38  abstract get dangerLevel(): 'low' | 'medium' | 'high' | 'critical';
39
40  // Metoda abstrakcyjna - opis zachowania
41  abstract describeBehavior(): string;
42
43  // Statyczna metoda fabrykująca
44  static getNextId(): number {
45    return Dinosaur.nextId;
46  }
47
48  // Publiczne API
49  public getStatus(): string {
50    return `[${this.id}] ${this.species} - Zdrowie: ${this.health}%`;
51  }
52
53  public assignEnclosure(enclosureId: string): void {
54    this.enclosureId = enclosureId;
55  }
56
57  protected heal(amount: number): void {
58    this.health = Math.min(100, this.health + amount);
59  }
60}
61
62// Klasa potomna implementująca interfejsy
63class TyrannosaurusRex extends Dinosaur implements ICarnivore {
64  public attackPower: number = 95;
65
66  constructor() {
67    super('Tyrannosaurus Rex', 100);
68  }
69
70  get dangerLevel(): 'critical' {
71    return 'critical';
72  }
73
74  describeBehavior(): string {
75    return 'Drapieżnik szczytowy - wymaga maksymalnych zabezpieczeń';
76  }
77
78  hunt(target: string): string {
79    return `T-Rex poluje na ${target} z siłą ataku ${this.attackPower}`;
80  }
81}
82
83class Pteranodon extends Dinosaur implements ICarnivore, IFlying {
84  public attackPower: number = 40;
85  public maxAltitude: number = 500;
86
87  constructor() {
88    super('Pteranodon', 100);
89  }
90
91  get dangerLevel(): 'high' {
92    return 'high';
93  }
94
95  describeBehavior(): string {
96    return 'Latający drapieżnik - wymaga zadaszonej zagrody';
97  }
98
99  hunt(target: string): string {
100    return `Pteranodon nurkuje na ${target} z powietrza`;
101  }
102
103  fly(altitude: number): string {
104    if (altitude > this.maxAltitude) {
105      return 'Wysokość przekracza maksymalny pułap!';
106    }
107    return `Pteranodon leci na wysokości ${altitude}m`;
108  }
109}

2. Generyczne struktury danych z ograniczeniami

Stwórz generyczne klasy i funkcje z odpowiednimi constraintami, aby zapewnić bezpieczeństwo typów w całym systemie.

1// Generyczna klasa rejestru z ograniczeniem
2class ParkRegistry<T extends Dinosaur> {
3  private entries: Map<number, T> = new Map();
4
5  add(item: T): void {
6    this.entries.set(item['id'], item);
7  }
8
9  get(id: number): T | undefined {
10    return this.entries.get(id);
11  }
12
13  getAll(): T[] {
14    return Array.from(this.entries.values());
15  }
16
17  // Generyczna metoda z dodatkowym ograniczeniem
18  filter<K extends keyof T>(key: K, value: T[K]): T[] {
19    return this.getAll().filter(item => item[key] === value);
20  }
21
22  // Metoda z wieloma typami generycznymi
23  map<U>(transform: (item: T) => U): U[] {
24    return this.getAll().map(transform);
25  }
26}
27
28// Generyczna funkcja z wieloma ograniczeniami
29function transferDinosaur<
30  T extends Dinosaur,
31  S extends ParkRegistry<T>,
32  D extends ParkRegistry<T>
33>(source: S, destination: D, dinoId: number): boolean {
34  const dino = source.get(dinoId);
35  if (!dino) return false;
36  
37  destination.add(dino);
38  return true;
39}
40
41// Generyczny interfejs dla systemów monitoringu
42interface IMonitoringSystem<T extends Dinosaur> {
43  monitor(subject: T): MonitoringReport<T>;
44  getAlerts(): Alert<T>[];
45}
46
47// Generyczny typ raportu
48interface MonitoringReport<T extends Dinosaur> {
49  subject: T;
50  timestamp: Date;
51  status: 'normal' | 'warning' | 'danger';
52  details: string;
53}
54
55interface Alert<T extends Dinosaur> {
56  dinosaur: T;
57  level: 'info' | 'warning' | 'critical';
58  message: string;
59}

3. Typy warunkowe i mapowane do dynamicznego modelowania

Wykorzystaj conditional types i mapped types, aby automatycznie dostosowywać zachowanie systemu w zależności od typu dinozaura i poziomu zagrożenia.

1// Typ warunkowy - określa wymagania zagrody na podstawie poziomu zagrożenia
2type DangerLevel = 'low' | 'medium' | 'high' | 'critical';
3
4type EnclosureRequirements<D extends DangerLevel> =
5  D extends 'critical' ? {
6    fenceVoltage: number;
7    guardCount: number;
8    cameraCount: number;
9    backupPower: boolean;
10    emergencyProtocol: string;
11  } :
12  D extends 'high' ? {
13    fenceVoltage: number;
14    guardCount: number;
15    cameraCount: number;
16    backupPower: boolean;
17  } :
18  D extends 'medium' ? {
19    fenceVoltage: number;
20    cameraCount: number;
21  } :
22  {
23    fenceType: string;
24  };
25
26// Użycie typu warunkowego
27const trexEnclosure: EnclosureRequirements<'critical'> = {
28  fenceVoltage: 10000,
29  guardCount: 4,
30  cameraCount: 12,
31  backupPower: true,
32  emergencyProtocol: 'Ewakuacja sektora i wysłanie zespołu interwencyjnego'
33};
34
35// Typy mapowane - generowanie wersji readonly i opcjonalnych
36type DinosaurData = {
37  species: string;
38  health: number;
39  location: string;
40  lastFed: Date;
41  dangerLevel: DangerLevel;
42};
43
44// Mapped type: wersja do wyświetlania (readonly)
45type ReadonlyDinosaurData = {
46  readonly [K in keyof DinosaurData]: DinosaurData[K];
47};
48
49// Mapped type: wersja do aktualizacji (wszystko opcjonalne)
50type DinosaurUpdate = {
51  [K in keyof DinosaurData]?: DinosaurData[K];
52};
53
54// Mapped type: wyciągnij tylko pola typu string
55type StringFieldsOnly<T> = {
56  [K in keyof T as T[K] extends string ? K : never]: T[K];
57};
58
59type DinosaurStringFields = StringFieldsOnly<DinosaurData>;
60// Rezultat: { species: string; location: string; }
61
62// Zaawansowany mapped type z warunkową transformacją
63type DinosaurDisplay<T> = {
64  [K in keyof T]: T[K] extends Date
65    ? string                    // Daty zamień na stringi
66    : T[K] extends number
67      ? `${number}%`           // Liczby z procentem
68      : T[K];                   // Reszta bez zmian
69};
70
71// Conditional type z infer
72type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
73
74type DinosaurFactory = (species: string) => Dinosaur;
75type CreatedDino = ExtractReturnType<DinosaurFactory>; // Dinosaur
76
77// Dystrybucyjny conditional type
78type ExcludeDangerLevels<T, U> = T extends U ? never : T;
79type SafeLevels = ExcludeDangerLevels<DangerLevel, 'high' | 'critical'>;
80// Rezultat: 'low' | 'medium'

4. Utility Types i zaawansowane wzorce typów

Zastosuj wbudowane utility types oraz stwórz własne, aby precyzyjnie modelować operacje na danych dinozaurów.

1// Wbudowane utility types
2type NewDinosaur = Omit<DinosaurData, 'lastFed'> & {
3  registrationDate: Date;
4};
5
6type DinosaurPreview = Pick<DinosaurData, 'species' | 'dangerLevel'>;
7
8type DinosaurRecord = Record<string, DinosaurData>;
9
10// Wymaganie pól, które wcześniej były opcjonalne
11type RequiredDinosaurUpdate = Required<DinosaurUpdate>;
12
13// Własne utility types
14type DeepReadonly<T> = {
15  readonly [K in keyof T]: T[K] extends object
16    ? T[K] extends Function
17      ? T[K]
18      : DeepReadonly<T[K]>
19    : T[K];
20};
21
22type DeepPartial<T> = {
23  [K in keyof T]?: T[K] extends object
24    ? T[K] extends Function
25      ? T[K]
26      : DeepPartial<T[K]>
27    : T[K];
28};
29
30// Typ wykluczający null i undefined z każdego pola
31type NonNullableFields<T> = {
32  [K in keyof T]: NonNullable<T[K]>;
33};
34
35// System konfiguracji zagrody z pełnym typowaniem
36interface EnclosureConfig {
37  dimensions: {
38    width: number;
39    height: number;
40    depth: number;
41  };
42  security: {
43    fenceType: 'electric' | 'reinforced' | 'laser';
44    voltage?: number;
45    guardSchedule: string[];
46  };
47  environment: {
48    temperature: number;
49    humidity: number;
50    terrain: 'forest' | 'plains' | 'water' | 'mixed';
51  };
52}
53
54// Deep partial pozwala na aktualizację dowolnie zagnieżdżonego pola
55type EnclosureUpdate = DeepPartial<EnclosureConfig>;
56
57const partialUpdate: EnclosureUpdate = {
58  security: {
59    voltage: 15000  // Aktualizuj tylko napięcie
60  }
61};
62
63// Deep readonly dla konfiguracji, która nie powinna być zmieniana
64type FrozenConfig = DeepReadonly<EnclosureConfig>;

Wskazówki implementacyjne

Zacznij od zdefiniowania interfejsów i typów bazowych - to fundament całego systemu. Następnie stwórz klasę abstrakcyjną

Dinosaur
i kilka klas potomnych implementujących różne interfejsy zdolności. Potem zbuduj generyczny
ParkRegistry
i system monitoringu. Na końcu dodaj typy warunkowe i mapowane, które dynamicznie dostosowują zachowanie systemu.

Pamiętaj o następującej strukturze plików:

1// 1. Typy i interfejsy bazowe
2// 2. Klasa abstrakcyjna Dinosaur
3// 3. Klasy potomne (TRex, Velociraptor, Triceratops, Pteranodon...)
4// 4. Generyczny ParkRegistry<T>
5// 5. System monitoringu z typami warunkowymi
6// 6. Utility types i transformacje
7// 7. Główna klasa JurassicParkManager łącząca wszystko

Przykład inicjalizacji systemu:

1// Tworzenie rejestru
2const carnivoreRegistry = new ParkRegistry<TyrannosaurusRex>();
3const herbivoreRegistry = new ParkRegistry<Triceratops>();
4
5// Dodawanie dinozaurów
6const rex = new TyrannosaurusRex();
7carnivoreRegistry.add(rex);
8
9// Konfiguracja zagrody z typami warunkowymi
10const criticalEnclosure: EnclosureRequirements<'critical'> = {
11  fenceVoltage: 10000,
12  guardCount: 4,
13  cameraCount: 12,
14  backupPower: true,
15  emergencyProtocol: 'Kod Czerwony - pełna ewakuacja'
16};
17
18// Generowanie raportu
19const reports = carnivoreRegistry.map(dino => ({
20  species: dino.species,
21  status: dino.getStatus(),
22  danger: dino.dangerLevel
23}));
24
25console.log('=== Raport Parku Jurajskiego ===');
26reports.forEach(r => console.log(`${r.species}: ${r.status} [Zagrożenie: ${r.danger}]`));

Powodzenia!

"Pamiętaj," mówi Dr. Wu, poprawiając okulary, "system typów TypeScript to Twoje DNA - fundament, na którym budujesz cały ekosystem. Im lepiej zaprojektujesz typy, tym stabilniejszy będzie Twój Park. Generyki dają elastyczność, typy warunkowe - inteligencję, a typy mapowane - zdolność do transformacji. Połącz je wszystkie i stwórz system, który przetrwa próbę czasu... i próbę uciekających dinozaurów."

Czas zastosować wszystko, czego się nauczyłeś. Otwórz edytor i zbuduj swój zaawansowany system typów!

Przejdź do CodeWorlds