Interfejs a klasa abstrakcyjna

Interfejsy i klasy abstrakcyjne zostały wprowadzone do php od wersji 5.0. Konstrukcje te znacznie pomagają w uporządkowaniu struktury aplikacji, pozwalają na zachowanie kontroli typów, integralność oraz logiczną budowę aplikacji.

Dzięki wdrożeniu tych konstrukcji do aplikacji możemy w lepszy i logiczniejszy sposób odwzorować rzeczywistość a zarazem ustrzec się potencjalnych błędów, poza tym zyskujemy większą kontrolę nad modelowaną obiektową rzeczywistością.

Klasy abstrakcyjne
Klasy abstrakcyjne służą do modelowania rzeczywistości, którą chcemy przenieść do logiki swojej aplikacji. Klasa abstrakcyjna definiuje w dużej ogólności pewien podstawowy model i zachowanie obiektu.

Powiedzmy, że chcemy chcemy stworzyć wirtualne zoo. W zoo znajdować się będą różnorakie zwierzęta np: małpy, lisy, wiewiórki, motyle, pszczoły, żaby, jaszczurki.

Każde zwierzę opisywane jest przez podstawowe atrybuty jak: wiek, rozmiar oraz cechy taki jak: rodzenie, chodzenie, jedzenie.

Tworzymy zatem podstawową klasę abstrakcyjną 'Zwierze’ zawierającą abstrakcyjne metody: born, go, eat:

abstract class Animal {

	protected $age;
	protected $width;
	protected $height;

	abstract protected function born();
	abstract protected function go();
	abstract protected function eat();

	protected function setAge( $years ) {
		$this->age = $years;
	}

	protected function setSize( $width, $height ) {
		$this->width = $width;
		$this->height = $height;
	}
}

Dzięki użyciu klasy abstrakcyjnej mamy pewność, że nikt nie stworzy obiektu klasy 'Zwierze’. Z logicznego punktu jest to niemożliwe gdyż nie ma zwierzęcia 'Zwierze’ – taki byt w rzeczywistości nie istnieje gdyż jest to zbyt ogólne pojęcie.

Dodatkowo zadeklarowaliśmy metody born, go, eat jako abstrakcyjne co wymusi ich implementacje w klasach dziedziczących – to też jest jak najbardziej logiczne gdyż nie ma zwierzęcia, które nie może wykonywać tych czynności. Metody te deklarujemy jako abstrakcyjne gdyż nie znając zwierzęcia nie wiemy jeszcze jak je zaimplementować. Metody abstrakcyjne muszą być puste.

Zadeklarowaliśmy również podstawowe atrybuty ($age, $width, $height), które każde zwierze posiadać musi, oraz settery które je ustawiają. Przy setterach ustawiamy specyfikator dostępu protected, dzięki temu będą mogły być wywoływane jedynie z poziomu metod klas dziedziczących. Nie ustawiamy ich jako abstrakcyjnych gdyż ich implementacja w klasie 'Animal’ jest wystarczająca i klasy potomne będą z nich korzystać.

Tworzymy teraz klasę konkretnego zwierzęcia np. małpy:

require_once 'animal.php';

class Monkey extends Animal {

	public function born() {/*@TODO*/}
	public function go() {/*@TODO*/}
	public function eat() {/*@TODO*/}

	public function __construct( $age, $size ) {
		$this->setAge( $age );
		$this->setSize( $size[0], $size[1] );
	}
}

$monkey1 = new Monkey( 2, array( 100, 20 ) );
var_dump( $monkey1 );

Musi ona posiadać metody abstrakcyjne, w ciele klasy 'Monkey’, powinny one przybrać docelową formę. Jak widać nie ma setterów, jest za to konstruktor przyjmujący parametry obiektu i wywołuje odpowiednie settery z klasy bazowej 'Animal’.

Interfejs
Klasa abstrakcyjna jest dość mocno związana logicznie z obiektami z niej dziedziczącymi. Widać jasno, że małpa i lis są integralnie związane z definicją zwierzęcia. Interfejs natomiast jedynie narzuca klasie jakie metody powinna posiadać, interface definiuje pewne właściwości, które klasa musi spełniać. Sam w sobie nie definiuje logicznej zależności pomiędzy sobą a klasą go implementującą, definiuje natomiast powiązanie pomiędzy wszystkimi klasami go implementującymi. Agreguje klasy obiektów na których można wykonać pewne operacje.

Kontynuując przykład zoo… załóżmy, że zoo może sprzedawać i kupować zwierzęta zatem możemy stworzyć interface 'Trade’:

interface Trade {
	public function sell();
	public function buy();
}

Dodatkowo, zwierzęta podlegają ubezpieczeniom zatem tworzymy interface 'Insurance’:

interface Insurance {
	public function make();
	public function withdrawn();
}

Jedna klasa może implementować wiele interfejsów, w przykładzie zoo 'Animal’ będzie implementował zarówno 'Trade’, jak i 'Insurance’. Jak widać kolejne interfejsy nie są ze sobą związane jednak wpływają na zachowanie się obiektu zwierzęcia.

class Monkey extends Animal implements Trade, Insurance {
	public function born() {/*@TODO*/}
	public function go() {/*@TODO*/}
	public function eat() {/*@TODO*/}
	
	public function sell() {/*@TODO*/}
	public function buy() {/*@TODO*/}
	
	public function make() {/*@TODO*/}
	public function withdrawn() {/*@TODO*/}
	

	public function __construct( $age, $size ) {
		$this->setAge( $age );
		$this->setSize( $size[0], $size[1] );
	}
}

Ale przecież w zoo są nie tylko zwierzęta ale także budynki i ludzie. Budynki mogą być odprzedawane a ludzie ubezpieczani zatem klasy reprezentujące te obiekty także powinny implementować te interfejsy.

Klasa 'People’ powinna implementować 'Insurance’ a klasa 'Building’ interfejs 'Trade’.

class People implements Insurance {
	public function make() {/*@TODO*/}
	public function withdrawn() {/*@TODO*/}
}
class Building implements Trade {
	public function sell() {/*@TODO*/}
	public function buy() {/*@TODO*/}
}

Widzimy zatem, że klasy reprezentujące zupełnie różne obiekty (zwierzęta, budynki) w pewnym zakresie zachowują się tak podobnie (można je kupić i sprzedać) zatem implementują interface 'Trade’.

Dzięki zapisowi tych zależności nasza aplikacja nabiera logicznego kształtu a dodatkowo dzięki parserowi zyskujemy darmową walidację, gdyż w przypadku braku metody z interfejsu php zwróci błąd – zatem nie zapomnimy o jej implementacji.