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

Gettery i settery

W naszym Parku Jurajskim, bezpieczeństwo i kontrola są kluczowe. Możemy mieć najlepszych genetyków i najnowocześniejsze wybiegi, ale jeśli nie potrafimy prawidłowo zarządzać dostępem do danych i kontrolować, jak informacje są modyfikowane, możemy szybko stracić kontrolę nad parkiem. Właśnie w tym miejscu gettery i settery przychodzą z pomocą!

Czym są gettery i settery?

Gettery i settery to specjalne metody, które pozwalają na kontrolowany dostęp do właściwości obiektów. Zamiast bezpośrednio odczytywać czy modyfikować dane, używamy specjalnych metod, które mogą zawierać dodatkową logikę, walidację czy transformację danych.

W JavaScript ES6, gettery i settery są zdefiniowane za pomocą słów kluczowych

get
i
set
.

Podstawy getterów

Getter to metoda, która służy do pobierania wartości właściwości. Definiujemy go za pomocą słowa kluczowego

get
:

1class Dinosaur {
2  constructor(name, species, dangerLevel) {
3    this._name = name;
4    this._species = species;
5    this._dangerLevel = dangerLevel;
6    this._isContained = true;
7  }
8
9  // Getter dla właściwości name
10  get name() {
11    return this._name;
12  }
13
14  // Getter dla właściwości species
15  get species() {
16    return this._species;
17  }
18
19  // Getter dla właściwości dangerLevel
20  get dangerLevel() {
21    return this._dangerLevel;
22  }
23
24  // Getter dla właściwości isContained
25  get isContained() {
26    return this._isContained;
27  }
28}
29
30const velociraptor = new Dinosaur("Blue", "Velociraptor", 8);
31
32// Używanie getterów - zauważ, że wyglądają jak zwykłe właściwości!
33console.log(velociraptor.name);        // "Blue"
34console.log(velociraptor.species);     // "Velociraptor"
35console.log(velociraptor.dangerLevel); // 8
36console.log(velociraptor.isContained); // true

Zwróć uwagę na konwencję nazywania prywatnych właściwości z podkreślnikiem (_). Chociaż JavaScript nie ma prawdziwych prywatnych właściwości w tradycyjnym sensie (choć nowsze wersje wprowadzają prywatne pola z symbolem #), ta konwencja jest powszechnie stosowana, aby oznaczyć, że właściwość powinna być używana tylko wewnątrz klasy.

Podstawy setterów

Setter to metoda, która służy do ustawiania wartości właściwości. Definiujemy go za pomocą słowa kluczowego

set
:

1class Dinosaur {
2  constructor(name, species, dangerLevel) {
3    this._name = name;
4    this._species = species;
5    this._dangerLevel = dangerLevel;
6    this._isContained = true;
7  }
8
9  // Gettery...
10
11  // Setter dla właściwości name
12  set name(newName) {
13    if (newName.length > 0) {
14      this._name = newName;
15    } else {
16      console.error("Nazwa nie może być pusta!");
17    }
18  }
19
20  // Setter dla właściwości dangerLevel
21  set dangerLevel(level) {
22    if (level >= 1 && level <= 10) {
23      this._dangerLevel = level;
24    } else {
25      console.error("Poziom zagrożenia musi być między 1 a 10!");
26    }
27  }
28
29  // Setter dla właściwości isContained
30  set isContained(status) {
31    // Tylko autoryzowany personel może zmienić status zamknięcia dinozaura
32    if (ParkSecurity.hasAuthorization("enclosure_control")) {
33      this._isContained = status;
34
35      // Jeśli dinozaur nie jest już zamknięty, aktywuj alarm
36      if (!status) {
37        ParkSecurity.activateAlarm(this.species, this.name);
38      }
39    } else {
40      console.error("Brak autoryzacji do kontroli zamknięcia! Incydent zostanie zgłoszony!");
41      ParkSecurity.reportUnauthorizedAccess();
42    }
43  }
44}
45
46const velociraptor = new Dinosaur("Blue", "Velociraptor", 8);
47
48// Używanie setterów
49velociraptor.name = "Echo";               // Zmienia nazwę na "Echo"
50console.log(velociraptor.name);           // "Echo"
51
52velociraptor.dangerLevel = 9;             // Zmienia poziom zagrożenia na 9
53console.log(velociraptor.dangerLevel);    // 9
54
55velociraptor.dangerLevel = 11;            // Błąd: "Poziom zagrożenia musi być między 1 a 10!"
56console.log(velociraptor.dangerLevel);    // 9 (bez zmian)
57
58// Bez autoryzacji, nie możesz zmienić statusu zamknięcia
59velociraptor.isContained = false;         // Błąd: "Brak autoryzacji do kontroli zamknięcia! Incydent zostanie zgłoszony!"

W tym przykładzie, settery dodają warstwę walidacji i kontroli dostępu przed zmianą wartości właściwości. Zapewniają, że dane są spójne i odpowiadają naszym wymaganiom biznesowym.

Złożone gettery i obliczane właściwości

Gettery mogą zawierać bardziej złożoną logikę i zwracać obliczone wartości:

1class DinosaurEnclosure {
2  constructor(id, capacity) {
3    this._id = id;
4    this._capacity = capacity;
5    this._dinosaurs = [];
6  }
7
8  addDinosaur(dino) {
9    if (this._dinosaurs.length < this._capacity) {
10      this._dinosaurs.push(dino);
11      return true;
12    }
13    return false;
14  }
15
16  get id() {
17    return this._id;
18  }
19
20  get dinosaurs() {
21    return [...this._dinosaurs]; // Zwraca kopię tablicy, aby oryginał nie mógł być modyfikowany
22  }
23
24  get capacity() {
25    return this._capacity;
26  }
27
28  // Getter obliczanej właściwości
29  get occupancyRate() {
30    return (this._dinosaurs.length / this._capacity) * 100;
31  }
32
33  // Getter obliczanej właściwości
34  get dangerLevel() {
35    if (this._dinosaurs.length === 0) return 0;
36
37    // Obliczamy średni poziom zagrożenia wszystkich dinozaurów w tym wybiegu
38    const totalDanger = this._dinosaurs.reduce((sum, dino) => sum + dino.dangerLevel, 0);
39    const averageDanger = totalDanger / this._dinosaurs.length;
40
41    // Jeśli mamy więcej niż 80% pojemności, zwiększamy poziom zagrożenia
42    if (this.occupancyRate > 80) {
43      return Math.min(10, averageDanger * 1.5); // Maksymalnie 10
44    }
45
46    return averageDanger;
47  }
48
49  // Getter obliczanej właściwości
50  get securityRequirements() {
51    const baseRequirement = {
52      guards: 1,
53      cameras: 2,
54      sensors: 5
55    };
56
57    // Zwiększamy wymagania bezpieczeństwa w zależności od poziomu zagrożenia
58    const dangerMultiplier = Math.ceil(this.dangerLevel / 2);
59
60    return {
61      guards: baseRequirement.guards * dangerMultiplier,
62      cameras: baseRequirement.cameras * dangerMultiplier,
63      sensors: baseRequirement.sensors * dangerMultiplier
64    };
65  }
66}
67
68// Tworzenie wybiegu i dodawanie dinozaurów
69const raptorEnclosure = new DinosaurEnclosure("RE-01", 4);
70
71const blue = new Dinosaur("Blue", "Velociraptor", 8);
72const delta = new Dinosaur("Delta", "Velociraptor", 7);
73const echo = new Dinosaur("Echo", "Velociraptor", 7);
74
75raptorEnclosure.addDinosaur(blue);
76raptorEnclosure.addDinosaur(delta);
77raptorEnclosure.addDinosaur(echo);
78
79// Używanie getterów
80console.log(raptorEnclosure.occupancyRate);        // 75
81console.log(raptorEnclosure.dangerLevel);          // 7.33...
82console.log(raptorEnclosure.securityRequirements); // { guards: 4, cameras: 8, sensors: 20 }

W powyższym przykładzie, gettery

occupancyRate
,
dangerLevel
i
securityRequirements
dynamicznie obliczają wartości na podstawie aktualnego stanu obiektu.

Zaawansowane zastosowania setterów

Settery mogą również implementować złożoną logikę biznesową:

1class ParkVisitor {
2  constructor(name, age) {
3    this._name = name;
4    this._age = age;
5    this._visitedAttractions = new Set();
6    this._riskTolerance = age < 18 ? "low" : "medium";
7    this._accessLevel = "standard";
8  }
9
10  get name() {
11    return this._name;
12  }
13
14  get age() {
15    return this._age;
16  }
17
18  get riskTolerance() {
19    return this._riskTolerance;
20  }
21
22  get accessLevel() {
23    return this._accessLevel;
24  }
25
26  get visitedAttractions() {
27    return [...this._visitedAttractions];
28  }
29
30  // Setter dla poziomu ryzyka z walidacją
31  set riskTolerance(level) {
32    const validLevels = ["low", "medium", "high", "extreme"];
33
34    if (!validLevels.includes(level)) {
35      throw new Error("Nieprawidłowy poziom tolerancji ryzyka!");
36    }
37
38    // Niepełnoletni goście mogą mieć co najwyżej średni poziom ryzyka
39    if (this._age < 18 && (level === "high" || level === "extreme")) {
40      console.warn("Niepełnoletni gość nie może mieć wysokiego lub ekstremalnego poziomu ryzyka!");
41      this._riskTolerance = "medium";
42    } else {
43      this._riskTolerance = level;
44    }
45
46    // Automatycznie aktualizujemy poziom dostępu na podstawie tolerancji ryzyka
47    this._updateAccessLevel();
48  }
49
50  // Prywatna metoda do aktualizacji poziomu dostępu
51  _updateAccessLevel() {
52    switch (this._riskTolerance) {
53      case "low":
54        this._accessLevel = "standard";
55        break;
56      case "medium":
57        this._accessLevel = "advanced";
58        break;
59      case "high":
60        this._accessLevel = "premium";
61        break;
62      case "extreme":
63        this._accessLevel = "all-access";
64        break;
65    }
66  }
67
68  // Setter dla wieku, który aktualizuje również tolerancję ryzyka
69  set age(newAge) {
70    if (newAge <= 0 || newAge > 120) {
71      throw new Error("Nieprawidłowy wiek!");
72    }
73
74    const wasUnderage = this._age < 18;
75    const isUnderage = newAge < 18;
76
77    this._age = newAge;
78
79    // Jeśli gość stał się niepełnoletni, dostosowujemy tolerancję ryzyka
80    if (!wasUnderage && isUnderage && (this._riskTolerance === "high" || this._riskTolerance === "extreme")) {
81      console.warn("Dostosowanie tolerancji ryzyka dla niepełnoletniego gościa.");
82      this._riskTolerance = "medium";
83      this._updateAccessLevel();
84    }
85  }
86}
87
88// Tworzenie gościa
89const visitor = new ParkVisitor("John Hammond", 68);
90
91// Używanie getterów i setterów
92console.log(visitor.riskTolerance);  // "medium"
93console.log(visitor.accessLevel);    // "advanced"
94
95visitor.riskTolerance = "high";
96console.log(visitor.riskTolerance);  // "high"
97console.log(visitor.accessLevel);    // "premium"
98
99visitor.age = 16;  // Zmiana wieku na niepełnoletni
100console.log(visitor.age);            // 16
101console.log(visitor.riskTolerance);  // "medium" (automatycznie zmienione)
102console.log(visitor.accessLevel);    // "advanced" (automatycznie zmienione)
103
104try {
105  visitor.riskTolerance = "extreme";  // Próba ustawienia ekstremalnego poziomu dla niepełnoletniego
106} catch (error) {
107  console.error(error.message);
108}
109console.log(visitor.riskTolerance);  // "medium" (bez zmian)

W tym przykładzie, setter

riskTolerance
zawiera logikę biznesową, która zapewnia, że niepełnoletni goście nie mogą mieć wysokiego lub ekstremalnego poziomu tolerancji ryzyka. Dodatkowo, zmiana tolerancji ryzyka automatycznie aktualizuje poziom dostępu gościa.

Zalety używania getterów i setterów

  1. Enkapsulacja danych - ukrywanie wewnętrznej implementacji i stanu obiektu
  2. Walidacja danych - zapewnienie, że dane spełniają określone kryteria przed zapisaniem
  3. Obliczane właściwości - dynamiczne generowanie wartości na podstawie innych właściwości
  4. Efekty uboczne - możliwość wykonania dodatkowych operacji przy dostępie lub modyfikacji
  5. Kontrola dostępu - ograniczenie dostępu do danych w zależności od uprawnień
  6. Kompatybilność wsteczna - możliwość zmiany implementacji bez wpływu na interfejs

Kiedy używać getterów i setterów?

  • Gdy właściwość wymaga walidacji przed zapisaniem
  • Gdy chcesz wykonać dodatkowe operacje przy dostępie lub modyfikacji właściwości
  • Gdy właściwość ma być obliczana na podstawie innych właściwości
  • Gdy chcesz ukryć wewnętrzną reprezentację danych
  • Gdy potrzebujesz kontrolować uprawnienia dostępu do danych

Podsumowanie

Gettery i settery to potężne narzędzia w programowaniu obiektowym, które pozwalają na lepszą kontrolę nad danymi i zachowaniem obiektów. W kontekście Parku Jurajskiego, odpowiednie użycie getterów i setterów może być różnicą między bezpiecznym parkiem a... no cóż, sceną z filmu, gdy T-Rex biega swobodnie po parku!

W następnym ćwiczeniu, poznamy dziedziczenie klas, które pozwoli nam tworzyć hierarchie różnych typów dinozaurów i systemów parku.

Gettery i settery

Przejdź do CodeWorlds