Menu
Coddy logo textTech

Polimorfizm za pomocą interfejsów

Część sekcji Programowanie obiektowe ścieżki PHP w Coddy. Lekcja 34 z 91.

W poprzedniej lekcji poznaliście polimorfizm za pomocą dziedziczenia, w którym klasy potomne nadpisują metody klas nadrzędnych. Interfejsy to kolejny potężny sposób na osiągnięcie polimorfizmu, szczególnie podczas pracy z niezwiązanymi ze sobą klasami, które mają wspólne zachowanie.

W przypadku interfejsów polimorfizm wynika z samego kontraktu. Każda klasa implementująca interfejs gwarantuje, że ma określone metody, niezależnie od swojej hierarchii dziedziczenia:

<?php
interface Notifiable {
    public function send(string $message);
}

class EmailNotifier implements Notifiable {
    public function send(string $message) {
        return "Email: " . $message;
    }
}

class SMSNotifier implements Notifiable {
    public function send(string $message) {
        return "SMS: " . $message;
    }
}

class PushNotifier implements Notifiable {
    public function send(string $message) {
        return "Push: " . $message;
    }
}

function notify(Notifiable $notifier, string $message) {
    return $notifier->send($message);
}

echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");

Wynik:

Email: Hello
SMS: Hello
Push: Hello

Funkcja notify() działa z dowolnym obiektem Notifiable. Te klasy nie mają wspólnej klasy nadrzędnej — są całkowicie niezwiązane, poza tym, że implementują ten sam interfejs. To kluczowa zaleta w porównaniu z polimorfizmem opartym na dziedziczeniu: możesz grupować niezwiązane klasy według ich możliwości, a nie pochodzenia.

Kluczowa kwestia: Polimorfizm oparty na interfejsach pozwala używać niezwiązanych ze sobą klas zamiennie na podstawie wspólnego zachowania, dzięki czemu kod jest bardziej elastyczny i mniej zależny od konkretnych implementacji.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system przechowywania danych, który pokazuje polimorfizm oparty na interfejsach. Utworzysz różne backendy przechowywania danych — system plików i bazę danych — które nie mają ze sobą nic wspólnego poza tym, że oba implementują ten sam interfejs przechowywania danych. Jedna funkcja będzie działać z dowolnym typem przechowywania danych, pokazując, jak interfejsy umożliwiają polimorfizm między całkowicie niezwiązanymi ze sobą klasami.

Rozmieścisz kod w czterech plikach:

  • Storable.php: Zdefiniuj interfejs Storable z dwiema sygnaturami metod: save($key, $data) i retrieve($key). Ten kontrakt zapewnia, że każdy system przechowywania danych może je zapisywać i odczytywać, niezależnie od tego, jak został zaimplementowany wewnętrznie.
  • FileStorage.php: Utwórz klasę FileStorage, która implementuje Storable. Dołącz plik interfejsu. Klasa powinna mieć prywatną właściwość $directory, której wartość jest ustawiana przez konstruktor. Zaimplementuj save($key, $data) tak, aby zwracała "Saving '[data]' to file [directory]/[key].txt". Zaimplementuj retrieve($key) tak, aby zwracała "Reading from file [directory]/[key].txt".
  • DatabaseStorage.php: Utwórz klasę DatabaseStorage, która również implementuje Storable. Dołącz plik interfejsu. Klasa powinna mieć prywatną właściwość $tableName, której wartość jest ustawiana przez konstruktor. Zaimplementuj save($key, $data) tak, aby zwracała "Inserting '[data]' into table [tableName] with key [key]". Zaimplementuj retrieve($key) tak, aby zwracała "Selecting from table [tableName] where key = [key]".
  • main.php: Dołącz oba pliki dotyczące przechowywania danych. Utwórz funkcję o nazwie storeData, która przyjmuje parametr typu Storable, klucz i dane. Funkcja powinna zwracać wynik wywołania save() z kluczem i danymi. Utwórz kolejną funkcję o nazwie fetchData, która przyjmuje parametr typu Storable i klucz, a następnie zwraca wynik wywołania retrieve().

Otrzymasz cztery dane wejściowe: ścieżkę do katalogu, nazwę tabeli, klucz i dane do zapisania. Utwórz obiekt FileStorage z katalogiem i obiekt DatabaseStorage z nazwą tabeli. Użyj funkcji storeData(), aby najpierw zapisać dane w systemie plików, a następnie w bazie danych. Wypisz każdy wynik w osobnym wierszu. Następnie użyj funkcji fetchData(), aby odczytać dane z systemu plików i wypisz ten wynik.

Zwróć uwagę, że funkcje storeData() i fetchData() działają tak samo z oboma typami przechowywania danych. Interesuje je tylko to, czy obiekt implementuje Storable. System plików i baza danych mają całkowicie różne implementacje wewnętrzne, a mimo to można ich używać zamiennie dzięki kontraktowi interfejsu.

Spróbuj swoich sił

<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';

// Odczytaj dane wejściowe
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));

// TODO: Utwórz funkcję storeData, która przyjmuje obiekt typu Storable, klucz i dane
// Powinna zwracać wynik wywołania save() z kluczem i danymi

// TODO: Utwórz funkcję fetchData, która przyjmuje obiekt typu Storable i klucz
// Powinna zwracać wynik wywołania retrieve() z kluczem

// TODO: Utwórz instancję FileStorage, przekazując jej katalog

// TODO: Utwórz instancję DatabaseStorage, przekazując jej nazwę tabeli

// TODO: Użyj storeData(), aby zapisać dane w magazynie plikowym, a następnie wypisz wynik

// TODO: Użyj storeData(), aby zapisać dane w magazynie bazodanowym, a następnie wypisz wynik

// TODO: Użyj fetchData(), aby pobrać dane z magazynu plikowego, a następnie wypisz wynik
?>
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator PHP online