Menu
Coddy logo textTech

Interfejs a klasa abstrakcyjna

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

Skoro znasz już interfejsy i klasy abstrakcyjne, wyjaśnijmy, kiedy używać każdego z tych rozwiązań. Choć mogą wydawać się podobne, służą różnym celom i mają różne możliwości.

Cecha Interfejs Klasa abstrakcyjna
Metody Tylko sygnatury metod Metody abstrakcyjne i konkretne
Właściwości Tylko stałe Może mieć właściwości
Wielokrotne dziedziczenie Klasa może implementować wiele interfejsów Klasa może rozszerzać tylko jedną klasę
Konstruktor Może deklarować sygnaturę __construct (bez ciała) Może definiować konstruktor z ciałem

Użyj interfejsu, gdy chcesz zdefiniować kontrakt, który mogą spełniać niepowiązane ze sobą klasy. Na przykład zarówno Printer, jak i Logger mogą implementować interfejs Writable, mimo że nie mają wspólnego przodka.

Użyj klasy abstrakcyjnej, gdy masz powiązane klasy, które współdzielą wspólny kod. Jeśli klasy Dog i Cat potrzebują tej samej implementacji metody eat(), abstrakcyjna klasa Animal pozwala napisać ten kod tylko raz.

Możesz także połączyć oba podejścia. Klasa może rozszerzać klasę abstrakcyjną, implementując jednocześnie wiele interfejsów:

<?php
abstract class Animal {
    protected $name;
    
    public function eat() {
        return $this->name . " is eating";
    }
}

interface Swimmable {
    public function swim();
}

class Duck extends Animal implements Swimmable {
    public function __construct($name) {
        $this->name = $name;
    }
    
    public function swim() {
        return $this->name . " is swimming";
    }
}

Kluczowa kwestia: Wybieraj interfejsy, aby definiować możliwości w niepowiązanych ze sobą klasach. Wybieraj klasy abstrakcyjne, gdy chcesz współdzielić kod między powiązanymi klasami.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system pojazdów, który pokazuje, kiedy używać klasy abstrakcyjnej, a kiedy interfejsu oraz jak skutecznie łączyć oba podejścia.

Utworzysz system, w którym pojazdy współdzielą wspólne właściwości dzięki dziedziczeniu, ale mogą też zyskiwać dodatkowe możliwości dzięki interfejsom. Odzwierciedla to rzeczywiste decyzje projektowe: samochód "is a" pojazd (dziedziczenie), ale także "has the capability" do tankowania (interfejs).

Rozmieść kod w czterech plikach:

  • Refuelable.php: Zdefiniuj interfejs Refuelable z jedną sygnaturą metody: refuel($amount). Ta możliwość może dotyczyć każdej klasy wymagającej tankowania, niezależnie od jej hierarchii dziedziczenia.
  • Vehicle.php: Zdefiniuj abstrakcyjną klasę Vehicle, która udostępnia wspólną funkcjonalność wszystkim pojazdom. Powinna zawierać chronioną właściwość $brand oraz chronioną właściwość $fuelLevel (początkowa wartość to 0). Konstruktor przyjmuje nazwę marki. Dodaj konkretną metodę getBrand(), która zwraca markę, konkretną metodę getFuelLevel(), która zwraca aktualny poziom paliwa, oraz abstrakcyjną metodę drive(), którą muszą zaimplementować klasy potomne.
  • Car.php: Utwórz klasę Car, która rozszerza klasę Vehicle i implementuje interfejs Refuelable. Na początku dołącz oba wymagane pliki. Konstruktor przyjmuje markę i przekazuje ją do konstruktora klasy nadrzędnej. Zaimplementuj metodę drive(), aby zwracała "[brand] car is driving". Zaimplementuj metodę refuel($amount), aby dodawała podaną wartość do poziomu paliwa i zwracała "[brand] refueled to [fuelLevel] liters".
  • main.php: Dołącz plik Car. Otrzymasz dwa dane wejściowe: nazwę marki i ilość paliwa (przekonwertuj ją na liczbę całkowitą). Utwórz obiekt Car z podaną marką. Wypisz wynik metody drive() w pierwszym wierszu, a następnie w drugim wierszu wypisz wynik metody refuel() z podaną ilością paliwa.

To wyzwanie pokazuje kluczową różnicę: abstrakcyjna klasa Vehicle współdzieli kod i ustanawia relację "is-a", natomiast interfejs Refuelable dodaje możliwość, która może dotyczyć innych, niezwiązanych klas (takich jak generator czy łódź), bez konieczności, by były pojazdami.

Spróbuj swoich sił

<?php
require_once 'Car.php';

// Odczytaj dane wejściowe
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));

// TODO: Utwórz Car z podaną marką

// TODO: Wypisz wynik drive() w pierwszym wierszu

// TODO: Wypisz wynik refuel() z podaną ilością paliwa w drugim wierszu
?>
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