2010-11-24 20:04:50

Interfejs czy abstrakcja?

Wielu programistów stawiających pierwsze kroki w programowaniu obiektowym ma problem z decyzją, kiedy stosować dziedziczenie po klasie abstrakcyjnej, a kiedy implementację interfejsu. Dodatkowo dużo zamieszania wprowadza często powtarzane twierdzenie, że mechanizm implementacji interfejsów jest receptą na brak możliwości bezpośredniego dziedziczenia po kilku klasach jednocześnie.

Załóżmy, że mamy dwie klasy abstrakcyjne:

abstract class Uzytkownik
{
  protected $imie='';
  protected $nazwisko='';
 
  public abstract function zapisz_imie($imie)
  {
    $this->imie=$imie;
  }
 
  public abstract function zapisz_nazwisko($nazwisko)
  {
    $this->nazwisko=$nazwisko;
  }
}
 
abstract class Uprawnienia
{
  protected $poziom=0;
  protected $zakres=array();
 
  public abstract function ustaw_poziom($poziom)
  {
    $this->poziom=$poziom;
  }
 
  public abstract function dodaj_zakres($zakres)
  {
    $this->zakres[]=$zakres;
  }
 
  public abstract function sprawdz_dostep($zakres)
  {
    return in_array($zakres, $this->zakres);
  }
}

Nie możemy utworzyć klas Administrator, Kierownik, Handlowiec itp. dziedziczących po tych klasach. Zapis: „class Admin extends Uzytkownik, Uprawnienia” spowoduje przerwanie programu i wyświetlenie odpowiedniego komunikatu błędu. Próba rozwiązania tego problemu przy pomocy interfejsów wyglądałaby tak:

interface Uzytkownik
{
  public function zapisz_imie($imie);
  public function zapisz_nazwisko($nazwisko);
}
 
interface Uprawnienia
{
  public function ustaw_poziom($poziom);
  public function dodaj_zakres($zakres);
  public function sprawdz_dostep($zakres);
}
 
class Administrator implements Uzytkownik, Uprawnienia
{
  protected $imie='';
  protected $nazwisko='';
  protected $poziom=0;
  protected $zakres=array();
 
  public function zapisz_imie($imie)
  {
    $this->imie=$imie;
  }
 
  public function zapisz_nazwisko($nazwisko)
  {
    $this->nazwisko=$nazwisko;
  }
 
  public function ustaw_poziom($poziom)
  {
    $this->poziom=$poziom;
  }
 
  public function dodaj_zakres($zakres)
  {
    $this->zakres[]=$zakres;
  }
 
  public function sprawdz_dostep($zakres)
  {
    return in_array($zakres, $this->zakres);
  }
}
 
class Kierownik implements Uzytkownik, Uprawnienia
{
  protected $imie='';
  protected $nazwisko='';
  protected $poziom=0;
  protected $zakres=array();
 
  public function zapisz_imie($imie)
  {
    $this->imie=$imie;
  }
 
  public function zapisz_nazwisko($nazwisko)
  {
    $this->nazwisko=$nazwisko;
  }
 
  public function ustaw_poziom($poziom)
  {
    $this->poziom=$poziom;
  }
 
  public function dodaj_zakres($zakres)
  {
    $this->zakres[]=$zakres;
  }
 
  public function sprawdz_dostep($zakres)
  {
    return in_array($zakres, $this->zakres);
  }
}

To co od razu rzuca się w oczy, to identyczne metody w klasach Administarator i Kierownik. Taki zapis jest rozwiązaniem fatalnym i niwelującym większość zalet programowania obiektowego. Na tym przykładzie wyraźnie widać, że interfejsy nie zastąpią dziedziczenia. Zaś na dziedziczenie bezpośrednio po kilku klasach programistom PHP przyjdzie poczekać pewnie jeszcze kilka lat. Są sposoby symulowania takiego dziedziczenia już dzisiaj przy pomocy metod magicznych. Jeden z takich sposobów omawiam w szkoleniu Programowanie Obiektowe w PHP. Nie to jednak jest tematem tego artykułu.

Na podstawie powyższych przykładów mogę stwierdzić, jakie są korzyści z dziedziczenia.

  • Metody zdefiniowane w jednej klasie mogę użyć w innych klasach.
  • Mogę podzielić kod całego elementu na małe części (klasy) zawierające dane i funkcje związane z konkretną częścią systemu lub aspektem działania systemu.

A jakie są zalety interfejsów? Wyjaśnię to na przykładzie poniżej.

interface Cache
{
  public function zapisz($klucz, $dane);
  public function odczytaj($klucz);
}
 
class Pliki implements Cache
{
  public function zapisz($klucz, $dane)
  {
    //zapis danych w plikach na dysku serwera
    //...
  }
 
  public function odczytaj($klucz)
  {
    //odczyt danych z plików serwera
    //...
  }
}
 
class Baza_danych implements Cache
{
  public function zapisz($klucz, $dane)
  {
    //zapis danych w bazie danych
    //...
  }
 
  public function odczytaj($klucz)
  {
    //odczyt danych z bazy danych
    //...
  }
}

Dzięki zastosowaniu interfejsu możemy tworzyć program, a na samym końcu zdecydować czy będziemy korzystać z cachowania w plikach czy bazie danych. Implementacja tego samego interfejsu przez klasy Pliki i Baza_danych wymusza na nich posiadanie metod o takich samych nazwach i tej samej liczbie argumentów. Korzyści prezentuję na poniższym przykładzie

//$cache = new Pliki;
$cache = new Baza_danych;
 
$cache->zapisz('czas', '11:20');
//...
$czas=$cache->odczytaj('czas');

Możemy dowolnie wybrać jakiej klasy ma być obiekt $cache bez wpływu na dalszy kod. Ponadto możemy zlecić innemu programiście napisanie klasy, która będzie implementowała interfejs Cache i ona również będzie pasowała do napisanego wcześniej kodu.

Tagi: , , , , ,

Dodaj odpowiedź