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: HelloFunkcja 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.
Wyzwanie
ŁatwyZbudujmy 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 interfejsStorablez dwiema sygnaturami metod:save($key, $data)iretrieve($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 implementujeStorable. Dołącz plik interfejsu. Klasa powinna mieć prywatną właściwość$directory, której wartość jest ustawiana przez konstruktor. Zaimplementujsave($key, $data)tak, aby zwracała"Saving '[data]' to file [directory]/[key].txt". Zaimplementujretrieve($key)tak, aby zwracała"Reading from file [directory]/[key].txt".DatabaseStorage.php: Utwórz klasęDatabaseStorage, która również implementujeStorable. Dołącz plik interfejsu. Klasa powinna mieć prywatną właściwość$tableName, której wartość jest ustawiana przez konstruktor. Zaimplementujsave($key, $data)tak, aby zwracała"Inserting '[data]' into table [tableName] with key [key]". Zaimplementujretrieve($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 nazwiestoreData, która przyjmuje parametr typuStorable, klucz i dane. Funkcja powinna zwracać wynik wywołaniasave()z kluczem i danymi. Utwórz kolejną funkcję o nazwiefetchData, która przyjmuje parametr typuStorablei klucz, a następnie zwraca wynik wywołaniaretrieve().
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
?>Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe $thisMetodyWłaściwościKonstruktor (__construct)Destruktor (__destruct)Powtórka — prosty kalkulator4Dziedziczenie
Podstawy dziedziczeniaSłowo kluczowe parent::Przesłanianie metodSłowo kluczowe finalKlasy abstrakcyjnePowtórzenie – hierarchia pracowników7Enkapsulacja
Public, protected, privateModyfikatory dostępu w szczegółachGettery i setteryUkrywanie informacjiPromocja parametrów konstruktora (8.0)Podsumowanie — system ewidencji studentów2Przestrzenie nazw i autoloading
Wprowadzenie do przestrzeni nazwSłowo kluczowe useStandard autoloadingu PSR-4Autoloader ComposeraPodsumowanie – uporządkowany projekt5Interfejsy i kontrakty
Wprowadzenie do interfejsówImplementowanie interfejsówImplementowanie wielu interfejsówInterfejs a klasa abstrakcyjnaPodpowiadanie typów za pomocą interfejsówPodsumowanie — kalkulator kształtów8Metody magiczne
Wprowadzenie do metod magicznych__toString i __debugInfo__get, __set, __isset, __unset__call i __callStatic__clone i klonowanie obiektów__serialize i __unserializePodsumowanie — własna kolekcja11System typów i obsługa błędów
Deklaracje typówTypy nullableTypy sumy i przecięciaKlasy wyjątkówNiestandardowa hierarchia wyjątkówTry, Catch, FinallyPodsumowanie — walidator formularzy14Projekt: Zarządzanie biblioteką
Przegląd projektuKlasy książki i użytkownika3Właściwości klas
Właściwości instancji a właściwości statyczneStałe w klasachMetody i właściwości statyczneWłaściwości prywatne i chronioneWłaściwości tylko do odczytu (PHP 8.1)Podsumowanie — menedżer kont bankowych6Polimorfizm
Ponowne omówienie nadpisywania metodPolimorfizm za pomocą interfejsówPodpowiedzi typów i typy sumyPóźne wiązanie statycznePodsumowanie — procesor płatności9Cechy
Wprowadzenie do cechKorzystanie z wielu cechRozwiązywanie konfliktów cechMetody abstrakcyjne w cechachCechy a dziedziczenie12Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec StrategyPoćwicz samodzielnie: Kompilator PHP online