PHP/Interfejsy: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Zyx (dyskusja | edycje)
rozpoczęcie przebudowy
Zyx (dyskusja | edycje)
dokończenie
Linia 111:
# Dwa interfejsy mogą wymagać tej samej metody jedynie pod warunkiem, że ma ona dokładnie identyczną postać w każdym z nich.
# Jeżeli klasa odziedziczyła jakąś metodę, której wymaga implementowany interfejs, jej nagłówek musi być zgodny z zawartością interfejsu.
 
Poniżej pokazany jest przykład błędnego nazewnictwa metod:
 
<source lang="php" line><?php
interface GooInterface
{
public function bar($arg1, $arg2);
} // end GooInterface;
 
class Foo
{
public function foo()
{
echo 'foo';
} // end foo();
public function bar($joe = 'joe')
{
echo 'bar';
} // end bar();
} // end Foo;
 
class Bar extends Foo implements GooInterface
{
public function joe()
{
echo 'joe';
} // end joe();
} // end GooInterface;
</source>
 
Po uruchomieniu tego skryptu zobaczymy:
 
<nowiki>Fatal error: Declaration of Foo::bar() must be compatible with that of GooInterface::bar() in /test.php on line 22</nowiki>
 
Mówi on, że odziedziczona metoda <code>bar()</code> jest niezgodna z interfejsem, który właśnie próbujemy zaimplementować.
 
=== Dziedziczenie interfejsów ===
 
Interfejsy można również dziedziczyć dokładnie tak samo, jak klasy, przy pomocy słowa kluczowego '''extends'''. Tutaj jednak wielokrotne dziedziczenie jest jak najbardziej dozwolone pod warunkiem, że nie ma konfliktów metod. Dziedziczenie interfejsów jest rzadko spotykane w rzeczywistych skryptach, lecz warto wiedzieć, że ono istnieje. Poniżej przedstawiony jest przykładowy skrypt pokazujący sposób wykorzystania i działanie:
 
<source lang="php" line highlight="12"><?php
interface Foo
{
public function foo();
} // end Foo;
 
interface Bar
{
public function bar();
} // end Bar;
 
interface Joe extends Foo, Bar
{
public function joe();
} // end Joe;
 
class Abc implements Joe
{
public function foo()
{
echo 'foo';
} // end foo();
public function bar()
{
echo 'bar';
} // end foo();
public function joe()
{
echo 'joe';
} // end foo();
} // end Abc;
 
$class = new Abc;
if($class instanceof Foo)
{
echo 'Ten obiekt implementuje interfejs Foo<br/>';
}
if($class instanceof Bar)
{
echo 'Ten obiekt implementuje interfejs Bar<br/>';
}
if($class instanceof Joe)
{
echo 'Ten obiekt implementuje interfejs Joe<br/>';
}
</source>
 
{{Uwaga|Dziedziczenie klas i interfejsów jest od siebie niezależne. Interfejs nie może dziedziczyć po klasie, a klasa po interfejsie, która może go jedynie implementować.}}
 
=== Interfejsy wbudowane ===
 
Język PHP posiada kilkanaście specjalnych interfejsów rozpoznawanych przez interpreter i przeznaczonych do dodatkowych zastosowań. Wchodzą one w skład tzw. ''Standard PHP Library'', czyli nowej standardowej biblioteki PHP zbudowanej w całości w oparciu o programowanie obiektowe. Będziemy się z nią zapoznawać po kawałku również w dalszej części podręcznika.
 
W poprzednich rozdziałach poznaliśmy funkcję <code>sizeof()</code> (znaną też jako <code>count()</code>), która zwraca ilość elementów w tablicy. Może ona współpracować także z obiektami, zwracając ilość publicznych pól:
 
<source lang="php" line><?php
class Counter
{
public $foo;
protected $bar;
}
 
$counter = new Counter;
echo sizeof($counter);
</source>
 
Wiedza o ilości pól jest rzadko potrzebna w praktyce, jednak nic nie stoi na przeszkodzie, aby to przeprogramować. Tutaj przyda nam się pierwszy specjalny interfejs o nazwie <code>Countable</code>. Dostarcza on metodę <code>count()</code>, którą PHP wywołuje, gdy chce uzyskać informacje o ilości elementów.
 
Pierwszym specjalnym interfejsem, jaki poznamy, jest '''Countable''', który dostarcza metodę <code>count()</code>. Informuje on interpreter, że klasa, która go implementuje, jest zbiorem elementów, które można policzyć (identycznie, jak elementy w tablicy). Rozbudujmy klasę '''Config''' z pierwszego rozdziału tak, aby można było uzyskać informacje o ilości aktualnie załadowanych opcji. Zakładamy, że wykonałeś ćwiczenie z poprzedniego rozdziału dotyczące jej rozbudowy:
 
<source lang="php" line><?php
class Config implements Countable
{
private $_config = array();
private $_awaitingLoaders = array();
 
public function count()
{
return sizeof($this->_config);
} // end count();
 
// pozostała część klasy
} // end Config;
</source>
 
Teraz możemy prosto dowiedzieć się, ile opcji aktualnie znajduje się w konfiguracji:
 
<source lang="php" line><?php
require('./Config.php');
$config = new Config;
echo 'Ilość elementów w konfiguracji: '.sizeof($config);
</source>
 
Obiekty można jeszcze bardziej upodobnić do tablic dzięki interfejsowi '''ArrayAccess'''. Dostarcza on czterech metod:
 
* <code>offsetGet($key)</code> - wywoływana przy próbie odczytu: <code>$obiekt['klucz']</code>
* <code>offsetSet($key, $value)</code> - wywoływana przy próbie zapisu: <code>$obiekt['klucz'] = 5</code>
* <code>offsetExists($key)</code> - wywoływana przy próbie sprawdzenia, czy element o podanym kluczu istnieje: <code>isset($obiekt['klucz'])</code>
* <code>offsetUnset($key)</code> - wywoływana przy próbie usunięcia elementu o podanym kluczu: <code>unset($obiekt['klucz'])</code>
 
Aby przećwiczyć interfejsy w praktyce, cofnijmy się do przykładu z systemem formularzy z poprzedniego rozdziału. Do klasy abstrakcyjnej '''FormElement''' dodamy specjalne chronione pole <code>$_attributes</code> będące tablicą przechowującą dodatkowe atrybuty dla elementu formularza (np. klasa CSS, ID itd.). Klasa musi implementować interfejsy '''ArrayAccess''' oraz '''Countable''', które pozwolą na proste zarządzanie atrybutami tak, jakby obiekt elementu był tablicą. Zaimplementuj wszystkie wymagane metody tak, aby odpowiednio modyfikowały pole <code>$_attributes</code>. Zmodyfikuj klasy odpowiednich elementów tak, aby uwzględniały dodane atrybuty w generowanym kodzie HTML. Poniżej znajduje się przykładowy plik testowy:
 
<source lang="php" line><?php
require('./FormElements.php');
require('./FormBuilder.php');
$form = new FormBuilder;
$element = $form->addElement(new FormInput('name'));
$element['class'] = 'name';
$element['id'] = 'f_name';
 
if(isset($element['id']))
{
unset($element['id']);
}
$form->display();
</source>
 
Interfejsów specjalnych jest jeszcze więcej. Wśród nich specjalną grupę stanowią tzw. ''iteratory''. Poświęcimy im cały osobny rozdział.
 
=== Kiedy stosować? ===
 
Nie ma jednej, uniwersalnej reguły mówiącej, kiedy należy stosować interfejsy, a kiedy dziedziczenie klas. Zasadniczo gdy chcemy dostarczyć klasie użytkownika pewną, gotową część implementacji, jesteśmy skazani na dziedziczenie, gdyż PHP nie udostępnia żadnych innych mechanizmów jej wstrzykiwania. Jednak gdy pragniemy jedynie zdefiniować listę zachowań, których oczekujemy, bez wnikania w szczegóły ich działania, interfejsy są o wiele lepszym pomysłem, gdyż mogą być implementowane niezależnie oraz nie zamykają drogi do dziedziczenia. To do nas, jako projektantów architektury aplikacji, należy odpowiedź na jakiej funkcjonalności najbardziej nam zależy i odpowiednio wybrać dostępne środki. Polecamy analizować obiektowo napisane skrypty i biblioteki, aby zapoznać się z ich budową. Naśladowanie dobrych wzorców to jedna z najlepszych szkół.
 
Musimy mieć świadomość, że interfejsy w połączeniu z dziedziczeniem nie gwarantują nam pełnej swobody wielokrotnego wykorzystania kodu. Dzięki interfejsom możemy swobodnie przenosić listę wymaganych zachowań, lecz nie da się przenieść implementacji metod bez użycia dziedziczenia, które ma ograniczenia. Istnieje szansa, że przyszłe wersje PHP będą oferować jeszcze jeden mechanizm do obejścia tego problemu.
 
== Zakończenie ==
 
Poznaliśmy już prawie wszystkie główne mechanizmy obiektowe, które dostarcza nam PHP. Interfejsy znacząco poszerzyły nasze możliwości wyrażania zależności między klasami. Kolejny rozdział poświęcony będzie profesjonalnym mechanizmom raportowania błędów przy pomocy ''wyjątków''. Od strony technicznej nie są one częścią programowania obiektowego, lecz w PHP silnie na nim bazują i dlatego ich omówienie znajduje się właśnie tutaj.