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ź

Musisz się zalogować aby dodać komentarz.