Lekcja 24 (OOP 4): Modyfikatory Dostępu (public, protected, private) i Hermetyzacja
Witaj w czwartej lekcji poświęconej Programowaniu Obiektowemu w PHP! W poprzednich lekcjach nauczyliśmy się definiować klasy i obiekty, pracować z ich właściwościami i metodami, a także poznaliśmy rolę konstruktorów, destruktorów i innych metod magicznych. Dzisiaj skupimy się na jednym z fundamentalnych filarów OOP – hermetyzacji (nazywanej również enkapsulacją lub ukrywaniem informacji) oraz na narzędziach, które PHP dostarcza do jej implementacji, czyli modyfikatorach dostępu: public
, protected
i private
.
Zrozumienie i prawidłowe stosowanie modyfikatorów dostępu jest kluczowe dla tworzenia dobrze zaprojektowanych, bezpiecznych i łatwych w utrzymaniu aplikacji obiektowych. Hermetyzacja pozwala nam kontrolować, które części naszej klasy są widoczne i dostępne dla świata zewnętrznego, a które stanowią jej wewnętrzne szczegóły implementacyjne. To prowadzi do bardziej modularnego i elastycznego kodu.
Czym Jest Hermetyzacja (Encapsulation)?
Hermetyzacja to, jak wspomnieliśmy w lekcji wprowadzającej do OOP, mechanizm łączenia danych (właściwości) i kodu (metod) operującego na tych danych w jedną, spójną jednostkę – obiekt – oraz ukrywania wewnętrznej implementacji tego obiektu przed światem zewnętrznym. Innymi słowy, obiekt powinien udostępniać jedynie dobrze zdefiniowany interfejs (zazwyczaj w postaci publicznych metod), za pomocą którego inne części systemu mogą z nim interagować, nie martwiąc się o to, jak obiekt realizuje swoje zadania wewnętrznie.
Główne cele hermetyzacji to:
- Ochrona danych (Integralność danych): Zapobieganie bezpośredniej, niekontrolowanej modyfikacji wewnętrznego stanu obiektu z zewnątrz. Zmiany stanu powinny odbywać się poprzez dedykowane metody (np. settery), które mogą zawierać logikę walidacyjną.
- Ukrywanie implementacji (Information Hiding): Użytkownik obiektu nie musi (i nie powinien) znać szczegółów jego wewnętrznej budowy. Wystarczy mu znajomość publicznego API (Application Programming Interface) obiektu.
- Zwiększenie modularności i zmniejszenie powiązań (Loose Coupling): Obiekty stają się bardziej niezależne. Zmiany w wewnętrznej implementacji jednego obiektu (np. zmiana sposobu przechowywania danych, optymalizacja algorytmu) nie powinny wpływać na inne części systemu, o ile publiczny interfejs obiektu pozostaje niezmieniony.
- Ułatwienie utrzymania i rozwoju kodu: Gdy wewnętrzne szczegóły są ukryte, łatwiej jest modyfikować i rozwijać poszczególne komponenty systemu bez obawy o nieprzewidziane skutki uboczne w innych miejscach.
- Poprawa czytelności i zrozumiałości kodu: Jasno zdefiniowany publiczny interfejs ułatwia zrozumienie, jak korzystać z danego obiektu.
Pomyśl o pilocie do telewizora. Pilot (obiekt) ma przyciski (publiczny interfejs), za pomocą których możesz zmieniać kanały, regulować głośność itp. Nie musisz wiedzieć, jak działa wewnętrzna elektronika pilota (ukryta implementacja), aby móc z niego korzystać. Co więcej, producent może zmienić wewnętrzne komponenty pilota, ale dopóki przyciski działają tak samo, ty jako użytkownik nie odczujesz różnicy. To jest właśnie idea hermetyzacji.
Modyfikatory Dostępu w PHP
PHP dostarcza trzy modyfikatory dostępu, które pozwalają kontrolować widoczność właściwości i metod klasy:
1. public
(Publiczny)
- Dostępność: Właściwości i metody zadeklarowane jako
public
są dostępne z każdego miejsca: - Z wnętrza samej klasy, w której zostały zdefiniowane.
- Z klas potomnych (dziedziczących po tej klasie).
- Spoza klasy, czyli przez instancje (obiekty) tej klasy.
- Zastosowanie: Używany dla składowych, które tworzą publiczny interfejs klasy – czyli te części, które mają być bezpośrednio używane przez inny kod.
- Domyślny modyfikator: Jeśli nie podamy żadnego modyfikatora dostępu dla właściwości (używając np. starej składni
var $nazwa;
) lub metody (co jest rzadkością, bo metody prawie zawsze mają modyfikator), PHP domyślnie traktuje je jakopublic
. Jednak zawsze zaleca się jawne deklarowanie modyfikatorów.
<?php
class PrzykladPublic
{
public string $wiadomoscPubliczna = "To jest wiadomość publiczna.";
public function metodaPubliczna(): void
{
echo "Wywołano metodę publiczną.<br>";
echo $this->wiadomoscPubliczna . "<br>"; // Dostęp z wnętrza klasy
}
}
class PotomnaPublic extends PrzykladPublic
{
public function testDostepu(): void
{
echo $this->wiadomoscPubliczna . " (dostęp z klasy potomnej)<br>"; // Dostęp z klasy potomnej
$this->metodaPubliczna(); // Dostęp z klasy potomnej
}
}
$obj = new PrzykladPublic();
echo $obj->wiadomoscPubliczna . " (dostęp spoza klasy)<br>"; // Dostęp spoza klasy
$obj->metodaPubliczna(); // Dostęp spoza klasy
$potomnyObj = new PotomnaPublic();
$potomnyObj->testDostepu();
?>
2. protected
(Chroniony)
- Dostępność: Właściwości i metody zadeklarowane jako
protected
są dostępne: - Z wnętrza samej klasy, w której zostały zdefiniowane.
- Z klas potomnych (dziedziczących po tej klasie).
- Niedostępność: Nie są dostępne bezpośrednio spoza hierarchii klas (czyli przez obiekty klasy bazowej lub potomnej, jeśli próbujemy się do nich odwołać z kodu zewnętrznego).
- Zastosowanie: Używany dla składowych, które nie powinny być częścią publicznego interfejsu, ale mogą być potrzebne lub modyfikowane przez klasy dziedziczące. Pozwala to na tworzenie bardziej elastycznych hierarchii dziedziczenia, gdzie klasy potomne mogą dostosowywać pewne aspekty zachowania rodzica.
<?php
class PrzykladProtected
{
protected string $wiadomoscChroniona = "To jest wiadomość chroniona.";
protected function metodaChroniona(): void
{
echo "Wywołano metodę chronioną. Wiadomość: " . $this->wiadomoscChroniona . "<br>";
}
public function wykonajCos(): void
{
echo "Wykonuję coś w klasie bazowej...<br>";
$this->metodaChroniona(); // Dostęp z wnętrza tej samej klasy
}
}
class PotomnaProtected extends PrzykladProtected
{
public function testDostepuProtected(): void
{
echo "W klasie potomnej: " . $this->wiadomoscChroniona . "<br>"; // Dostęp do chronionej właściwości rodzica
$this->metodaChroniona(); // Dostęp do chronionej metody rodzica
}
}
$objBazowy = new PrzykladProtected();
$objBazowy->wykonajCos();
// echo $objBazowy->wiadomoscChroniona; // BŁĄD! Fatal error: Cannot access protected property
// $objBazowy->metodaChroniona(); // BŁĄD! Fatal error: Call to protected method
$objPotomny = new PotomnaProtected();
$objPotomny->testDostepuProtected();
// echo $objPotomny->wiadomoscChroniona; // BŁĄD! Nadal niedostępne spoza hierarchii klas
?>
3. private
(Prywatny)
- Dostępność: Właściwości i metody zadeklarowane jako
private
są dostępne tylko i wyłącznie z wnętrza samej klasy, w której zostały zdefiniowane. - Niedostępność: Nie są dostępne ani dla klas potomnych, ani spoza klasy.
- Zastosowanie: Używany dla składowych, które są wewnętrznymi szczegółami implementacyjnymi danej klasy i nie powinny być w żaden sposób dostępne ani modyfikowane z zewnątrz, ani nawet przez klasy dziedziczące. Zapewnia to najwyższy poziom hermetyzacji i zapobiega przypadkowym zmianom wewnętrznego stanu obiektu przez klasy potomne. Jeśli klasa potomna potrzebuje jakiejś funkcjonalności, powinna korzystać z publicznych lub chronionych metod klasy bazowej.
<?php
class PrzykladPrivate
{
private string $sekret = "To jest bardzo tajna wiadomość.";
private float $wewnetrznyLicznik = 0;
private function metodaPrywatna(): void
{
echo "Wywołano metodę prywatną. Sekret: " . $this->sekret . "<br>";
$this->wewnetrznyLicznik++;
}
public function wykonajOperacje(): void
{
echo "Wykonuję operację publiczną...<br>";
$this->metodaPrywatna(); // Dostęp do metody prywatnej z wnętrza tej samej klasy
echo "Wewnętrzny licznik: " . $this->wewnetrznyLicznik . "<br>";
}
}
class PotomnaPrivate extends PrzykladPrivate
{
public function probaDostepuPrywatnego(): void
{
// echo $this->sekret; // BŁĄD! Fatal error: Cannot access private property PrzykladPrivate::$sekret
// $this->metodaPrywatna(); // BŁĄD! Fatal error: Call to private method PrzykladPrivate::metodaPrywatna()
echo "Próba dostępu do prywatnych składowych rodzica z klasy potomnej nie powiodła się (co jest poprawne).<br>";
}
}
$objP = new PrzykladPrivate();
$objP->wykonajOperacje();
// echo $objP->sekret; // BŁĄD! Fatal error: Cannot access private property
// $objP->metodaPrywatna(); // BŁĄD! Fatal error: Call to private method
$objPotP = new PotomnaPrivate();
$objPotP->probaDostepuPrywatnego();
$objPotP->wykonajOperacje(); // Metoda publiczna odziedziczona, która wewnętrznie używa prywatnych składowych klasy bazowej
?>
Podsumowanie widoczności:
Modyfikator | Wewnątrz tej samej klasy | W klasach potomnych | Spoza hierarchii klas (przez obiekt) |
---|---|---|---|
public |
Tak | Tak | Tak |
protected |
Tak | Tak | Nie |
private |
Tak | Nie | Nie |
Gettery i Settery (Accessors and Mutators)
Aby zapewnić kontrolowany dostęp do prywatnych lub chronionych właściwości, często stosuje się publiczne metody nazywane getterami (akcesorami) i setterami (mutatorami).
- Getter to metoda, która służy do odczytywania wartości właściwości. Zazwyczaj ma nazwę w stylu
getNazwaWlasciwosci()
lubisNazwaWlasciwosci()
(dla właściwości logicznych). - Setter to metoda, która służy do ustawiania (modyfikowania) wartości właściwości. Zazwyczaj ma nazwę w stylu
setNazwaWlasciwosci()
. Settery są bardzo ważne, ponieważ pozwalają na dodanie logiki walidacyjnej przed przypisaniem nowej wartości do właściwości, zapewniając integralność danych obiektu.
Przykład klasy KontoBankowe
z prywatną właściwością $saldo
i publicznymi getterami/setterami:
<?php
class KontoBankowe
{
private float $saldo = 0.0;
private string $numerKonta;
public function __construct(string $numerKonta, float $saldoPoczatkowe = 0.0)
{
$this->numerKonta = $numerKonta;
if ($saldoPoczatkowe >= 0) {
$this->saldo = $saldoPoczatkowe;
}
}
// Getter dla salda
public function getSaldo(): float
{
// Możemy tu dodać logikę np. autoryzacji przed zwróceniem salda
return $this->saldo;
}
// Getter dla numeru konta (tylko do odczytu z zewnątrz)
public function getNumerKonta(): string
{
return $this->numerKonta;
}
// Metoda do wpłaty środków (rodzaj settera, ale z logiką biznesową)
public function wplac(float $kwota): bool
{
if ($kwota > 0) {
$this->saldo += $kwota;
echo sprintf("Wpłacono %.2f PLN. Nowe saldo: %.2f PLN.<br>", $kwota, $this->saldo);
return true;
} else {
echo "Kwota wpłaty musi być dodatnia.<br>";
return false;
}
}
// Metoda do wypłaty środków
public function wyplac(float $kwota): bool
{
if ($kwota <= 0) {
echo "Kwota wypłaty musi być dodatnia.<br>";
return false;
}
if ($this->saldo >= $kwota) {
$this->saldo -= $kwota;
echo sprintf("Wypłacono %.2f PLN. Nowe saldo: %.2f PLN.<br>", $kwota, $this->saldo);
return true;
} else {
echo "Niewystarczające środki na koncie. Próbowano wypłacić: %.2f PLN, dostępne: %.2f PLN.<br>", $kwota, $this->saldo;
return false;
}
}
}
$mojeKonto = new KontoBankowe("1234567890", 1000.00);
echo "Numer konta: " . $mojeKonto->getNumerKonta() . "<br>";
echo "Aktualne saldo: " . $mojeKonto->getSaldo() . " PLN<br>";
// $mojeKonto->saldo = 5000; // BŁĄD! Fatal error: Cannot access private property KontoBankowe::$saldo
$mojeKonto->wplac(500.75);
$mojeKonto->wyplac(200.00);
$mojeKonto->wyplac(1500.00); // Próba wypłaty większej kwoty niż saldo
echo "Końcowe saldo: " . $mojeKonto->getSaldo() . " PLN<br>";
?>
W tym przykładzie właściwość $saldo
jest prywatna. Nie można jej bezpośrednio odczytać ani zmodyfikować spoza klasy. Dostęp do niej odbywa się poprzez publiczne metody getSaldo()
, wplac()
i wyplac()
, które kontrolują operacje na saldzie i mogą zawierać dodatkową logikę (np. walidację kwoty, sprawdzanie dostępności środków).
Dlaczego Hermetyzacja Jest Ważna? Korzyści w Praktyce
Rozważmy, dlaczego poświęcamy tyle uwagi ukrywaniu danych i kontrolowaniu dostępu:
-
Zapobieganie Niespójności Danych: Jeśli właściwość reprezentująca np. wiek osoby byłaby publiczna, ktoś mógłby przypadkowo (lub celowo) ustawić ją na wartość ujemną (
$osoba->wiek = -5;
). Używając settera ($osoba->setWiek(-5);
), możemy wewnątrz metody dodać warunek sprawdzający, czy wiek jest poprawny, i odrzucić nieprawidłową wartość lub rzucić wyjątek.<?php class Osoba { private int $wiek; public function setWiek(int $wiek): void { if ($wiek >= 0 && $wiek <= 150) { $this->wiek = $wiek; } else { throw new InvalidArgumentException("Wiek musi być pomiędzy 0 a 150."); } } public function getWiek(): int { return $this->wiek; } } ?>
-
Elastyczność Implementacji: Załóżmy, że mamy klasę, która przechowuje temperaturę. Początkowo możemy przechowywać ją w stopniach Celsjusza. Jeśli właściwość
$temperaturaCelsjusz
jest publiczna, cały kod zewnętrzny będzie z niej korzystał. Jeśli później zdecydujemy, że wewnętrznie chcemy przechowywać temperaturę w Kelwinach dla większej precyzji obliczeń, musielibyśmy zmienić cały kod zewnętrzny. Jeśli jednak temperatura była prywatna, a dostęp do niej odbywał się przez gettery/settery (np.getTemperaturaCelsjusz()
,setTemperaturaCelsjusz()
), moglibyśmy zmienić wewnętrzną reprezentację (np. na$temperaturaKelwin
) i dostosować tylko logikę w getterach/setterach (konwersja C <-> K), a kod zewnętrzny pozostałby bez zmian. - Uproszczenie Interfejsu: Klasa może mieć wiele wewnętrznych właściwości i metod pomocniczych, które są potrzebne do jej działania, ale nie są istotne dla użytkownika klasy. Udostępniając tylko niezbędne publiczne metody, upraszczamy sposób korzystania z klasy i zmniejszamy ryzyko błędów.
- Lepsza Testowalność: Komponenty z jasno zdefiniowanymi, wąskimi interfejsami są łatwiejsze do testowania jednostkowego, ponieważ można je testować w większej izolacji.
- Wspieranie Zasady Pojedynczej Odpowiedzialności (SRP): Chociaż hermetyzacja sama w sobie nie gwarantuje SRP, to pomaga w jej osiągnięciu poprzez wyraźne oddzielenie tego, co klasa robi (jej publiczny kontrakt), od tego, jak to robi (wewnętrzna implementacja).
Kiedy Używać Którego Modyfikatora?
Ogólna zasada jest taka, aby domyślnie stosować jak najbardziej restrykcyjny modyfikator dostępu, który nadal pozwala klasie działać poprawnie, i rozluźniać go tylko wtedy, gdy jest to absolutnie konieczne.
private
: Używaj domyślnie dla wszystkich właściwości i metod, które są czysto wewnętrznymi szczegółami implementacyjnymi danej klasy i nie muszą być dostępne ani modyfikowane przez klasy potomne. To najlepszy wybór dla maksymalnej hermetyzacji.protected
: Używaj dla właściwości i metod, które nie powinny być częścią publicznego API, ale przewidujesz, że klasy potomne mogą potrzebować do nich dostępu, aby rozszerzyć lub zmodyfikować zachowanie klasy bazowej. Używaj ostrożnie, ponieważ tworzy to silniejsze powiązanie między klasą bazową a jej potomkami.public
: Używaj tylko dla tych metod (i rzadziej właściwości), które tworzą publiczny kontrakt (API) klasy – czyli to, co chcesz świadomie udostępnić światu zewnętrznemu. Staraj się minimalizować liczbę publicznych składowych, aby interfejs klasy był jak najprostszy i najbardziej stabilny. Bezpośredni publiczny dostęp do właściwości jest często odradzany na rzecz getterów i setterów, chyba że właściwość jest faktycznie tylko do odczytu i nie wymaga żadnej logiki przy dostępie (np. stałe wartości lub proste DTO - Data Transfer Objects).
Pamiętaj, że dobry projekt obiektowy dąży do minimalizacji publicznego interfejsu i maksymalizacji hermetyzacji. To prowadzi do bardziej elastycznego, odpornego na błędy i łatwiejszego w utrzymaniu kodu.
Podsumowanie Lekcji
W tej lekcji dogłębnie omówiliśmy koncepcję hermetyzacji oraz modyfikatory dostępu w PHP: public
, protected
i private
. Zrozumieliśmy, jak każdy z tych modyfikatorów wpływa na widoczność właściwości i metod klasy, zarówno wewnątrz samej klasy, w klasach potomnych, jak i spoza hierarchii klas. Nauczyliśmy się, że hermetyzacja jest kluczowa dla ochrony danych, ukrywania implementacji, zwiększania modularności i ułatwiania utrzymania kodu.
Przedstawiliśmy również popularny wzorzec stosowania publicznych metod getterów i setterów jako sposobu na kontrolowany dostęp do prywatnych lub chronionych właściwości, co pozwala na dodanie logiki walidacyjnej i utrzymanie integralności danych obiektu. Podkreśliliśmy, że dążenie do jak najsilniejszej hermetyzacji i minimalizowania publicznego interfejsu jest dobrą praktyką w programowaniu obiektowym.
W następnej lekcji zajmiemy się słowem kluczowym static
, które pozwala na definiowanie właściwości i metod należących do samej klasy, a nie do jej konkretnych instancji (obiektów).
Zadanie praktyczne
Stwórz klasę ProduktElektroniczny
.
- Klasa powinna mieć następujące właściwości:
private string $nazwa
private float $cenaNetto
protected float $stawkaVAT = 0.23
(wartość domyślna 23%)public string $producent
- Dodaj konstruktor, który przyjmuje nazwę, cenę netto i producenta, i inicjalizuje odpowiednie właściwości.
- Dodaj publiczne gettery dla wszystkich prywatnych i chronionych właściwości (
getNazwa()
,getCenaNetto()
,getStawkaVAT()
). - Dodaj publiczny setter dla ceny netto (
setCenaNetto(float $cena)
), który sprawdza, czy cena jest większa od zera. - Dodaj publiczną metodę
obliczCenaBrutto(): float
, która oblicza i zwraca cenę brutto (cenaNetto * (1 + stawkaVAT)). - Dodaj publiczną metodę
wyswietlSzczegoly(): string
, która zwraca sformatowany ciąg znaków ze wszystkimi informacjami o produkcie (nazwa, producent, cena netto, stawka VAT, cena brutto). - Przetestuj klasę: utwórz obiekt, ustaw i odczytaj jego właściwości za pomocą getterów/setterów, wyświetl szczegóły. Spróbuj ustawić ujemną cenę netto.
Kliknij, aby zobaczyć przykładowe rozwiązanie
<?php
class ProduktElektroniczny
{
private string $nazwa;
private float $cenaNetto;
protected float $stawkaVAT = 0.23;
public string $producent;
public function __construct(string $nazwa, float $cenaNetto, string $producent)
{
$this->nazwa = $nazwa;
$this->setCenaNetto($cenaNetto); // Użycie settera dla walidacji
$this->producent = $producent;
}
public function getNazwa(): string
{
return $this->nazwa;
}
public function getCenaNetto(): float
{
return $this->cenaNetto;
}
public function setCenaNetto(float $cena): void
{
if ($cena > 0) {
$this->cenaNetto = $cena;
} else {
echo "Cena netto musi być większa od zera. Ustawiono na 0.01.<br>";
$this->cenaNetto = 0.01; // Domyślna minimalna wartość lub rzucić wyjątek
}
}
public function getStawkaVAT(): float
{
return $this->stawkaVAT;
}
public function obliczCenaBrutto(): float
{
return $this->cenaNetto * (1 + $this->stawkaVAT);
}
public function wyswietlSzczegoly(): string
{
return sprintf(
"Produkt: %s (%s)<br>Cena netto: %.2f PLN<br>Stawka VAT: %.2f%%<br>Cena brutto: %.2f PLN<br>",
$this->getNazwa(),
$this->producent,
$this->getCenaNetto(),
$this->getStawkaVAT() * 100,
$this->obliczCenaBrutto()
);
}
}
// Testowanie
$laptop = new ProduktElektroniczny("Super Laptop X2000", 3500.00, "TechCorp");
echo $laptop->wyswietlSzczegoly();
echo "<hr>";
$laptop->setCenaNetto(3200.50);
echo "Po zmianie ceny netto:<br>";
echo $laptop->wyswietlSzczegoly();
echo "<hr>";
$telefon = new ProduktElektroniczny("Smartfon Galaxy Z", -1500.00, "MobileInc"); // Test ujemnej ceny
echo $telefon->wyswietlSzczegoly();
?>
Zadanie do samodzielnego wykonania
Stwórz klasę RezerwacjaHotelu
.
- Właściwości:
private string $idRezerwacji
(generowany np. losowy ciąg znaków w konstruktorze)private DateTimeImmutable $dataPrzyjazdu
private DateTimeImmutable $dataWyjazdu
private int $liczbaGosci
public string $nazwiskoGoscia
- Konstruktor przyjmujący nazwisko gościa, datę przyjazdu (jako string np. 'Y-m-d'), datę wyjazdu (jako string) i liczbę gości. W konstruktorze przekonwertuj daty na obiekty
DateTimeImmutable
. Wygeneruj$idRezerwacji
. - Dodaj publiczne gettery dla wszystkich prywatnych właściwości.
- Dodaj publiczny setter dla liczby gości (
setLiczbaGosci(int $liczba)
), który sprawdza, czy liczba jest dodatnia. - Dodaj publiczną metodę
getDlugoscPobytu(): int
, która oblicza i zwraca liczbę nocy pobytu (różnica między datą wyjazdu a przyjazdu). Użyj metodydiff()
obiektówDateTimeImmutable
i właściwościdays
obiektuDateInterval
. - Metoda
__construct
powinna również walidować, czy data wyjazdu jest późniejsza niż data przyjazdu. Jeśli nie, powinna ustawić datę wyjazdu na dzień po dacie przyjazdu i wyświetlić ostrzeżenie. - Przetestuj klasę, tworząc rezerwacje, sprawdzając długość pobytu i modyfikując liczbę gości.
FAQ - Modyfikatory Dostępu i Hermetyzacja
Czy zawsze muszę tworzyć gettery i settery dla prywatnych właściwości?
Nie zawsze. Gettery i settery tworzymy wtedy, gdy chcemy umożliwić kontrolowany dostęp do tych właściwości z zewnątrz. Jeśli właściwość jest czysto wewnętrzna i nie ma potrzeby jej odczytywania ani modyfikowania spoza klasy (nawet pośrednio), to gettery/settery nie są konieczne. Czasem właściwość może mieć tylko getter (jeśli ma być tylko do odczytu) lub tylko setter (rzadziej).
Jaka jest różnica między ukrywaniem informacji a abstrakcją?
Ukrywanie informacji (osiągane przez hermetyzację i modyfikatory dostępu) polega na ukrywaniu wewnętrznych szczegółów implementacji. Abstrakcja to szersze pojęcie, które polega na prezentowaniu użytkownikowi tylko istotnych cech obiektu lub systemu, upraszczając jego złożoność. Hermetyzacja jest jednym ze sposobów osiągania abstrakcji.
Czy użycie protected
nie łamie trochę zasady hermetyzacji?
Użycie protected
rzeczywiście tworzy pewne powiązanie między klasą bazową a jej potomkami, ponieważ klasy potomne uzyskują dostęp do wewnętrznych (choć nie prywatnych) składowych rodzica. Jest to kompromis. Czasem jest to potrzebne dla elastyczności dziedziczenia, ale należy tego używać świadomie, aby nie doprowadzić do sytuacji, gdzie zmiany w klasie bazowej psują działanie wielu klas potomnych (tzw. "fragile base class problem").
Czy mogę zmienić modyfikator dostępu metody podczas jej nadpisywania w klasie potomnej?
Tak, ale z pewnymi ograniczeniami. Możesz zmienić modyfikator dostępu na mniej restrykcyjny (np. z protected
na public
), ale nie na bardziej restrykcyjny (np. z public
na protected
lub private
). To wynika z zasady podstawienia Liskov – obiekt klasy potomnej powinien móc być użyty wszędzie tam, gdzie oczekiwany jest obiekt klasy bazowej, a zawężenie widoczności mogłoby to uniemożliwić.
Co jeśli chcę, aby właściwość była tylko do odczytu z zewnątrz?
Najprostszym sposobem jest zadeklarowanie jej jako private
lub protected
i udostępnienie tylko publicznego gettera (bez settera). W PHP 8.1+ można również użyć modyfikatora readonly
dla publicznych właściwości (public readonly string $nazwa;
), co zapobiega ich modyfikacji po inicjalizacji w konstruktorze.
Czy hermetyzacja ma wpływ na wydajność?
Wywołanie dodatkowej metody (gettera/settera) zamiast bezpośredniego dostępu do właściwości może wprowadzić minimalny, niemal niezauważalny narzut wydajnościowy. Jednak korzyści płynące z hermetyzacji (bezpieczeństwo danych, elastyczność, łatwość utrzymania) zazwyczaj znacznie przewyższają ten minimalny koszt. Nowoczesne silniki PHP są dobrze zoptymalizowane pod kątem wywołań metod.
Czy wszystkie właściwości powinny być domyślnie prywatne?
Jest to często zalecana praktyka jako punkt wyjścia ("private by default"). Następnie świadomie decydujemy, które z nich (lub dostęp do nich poprzez gettery/settery) udostępnić jako protected
lub public
, jeśli jest taka potrzeba. Takie podejście promuje silną hermetyzację od samego początku.