Lekce 14 - Interface (rozhraní v PHP)
V předešlém cvičení, Řešené úlohy k 12.-13. lekci OOP v PHP, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Dnes se uvedeme do rozhraní.
Interface (rozhraní)
Rozhraním se v programování myslí veřejné metody, které nám třída vystavuje zvenčí. Pomocí tohoto rozhraní s instancí třídy poté komunikujeme.
Rozhraní je však možné definovat samostatně, mimo třídu, stejně jako
již dříve zmíněný výčtový typ (enum). Takové rozhraní neobsahuje
implementaci, ale pouze hlavičky metod. Je to zkrátka jen rozhraní Když si takové rozhraní
vytvoříme, můžeme ho nechat implementovat v nějaké třídě. Zkusme si
to.
Rozhraní si vytvoříme ve složce tridy/
, i když třídou
vlastně není. Půjde o rozhraní programátora. Založte si nový soubor s
názvem Programator.php
. Do něj vložte následující obsah:
interface Programator { public function programuj(): void; }
Rozhraní Programator
obsahuje jednu metodu
programuj()
, která nenavrací žádnou hodnotu (návratový typ
void
). Mohlo by jich samozřejmě obsahovat více a metody by mohly
mít i parametry. Pro naše účely to však bohatě stačí.
Přesuňme se do naší třídy Javista.php
. Pro úplnost
zopakuji jak její kód vypadá:
class Javista extends Clovek { public function __construct(string $jmeno, string $prijmeni, int $vek, string $heslo, public string $ide) { parent::__construct($jmeno, $prijmeni, $vek, $heslo); } public function pozdrav(): void { echo('Hello world! Jsem ' . $this->jmeno); } public function programuj(): void { echo("Programuji v {$this->ide}..."); } }
Třída dědí z třídy Clovek
, volá rodičovský konstruktor,
přepisuje metodu pozdrav()
a přidává novou metodu
programuj()
.
My již víme, že v PHP můžeme dědit pouze z jedné
třídy. Můžeme však zároveň implementovat i libovolný
počet rozhraní. V deklaraci třídy pomocí klíčového slova
implements
určíme, že implementuje rozhraní
Programator
.
class Javista extends Clovek implements Programator
Co se týká názvosloví, třídy se dědí, rozhraní se implementují. Je to logické, jelikož dědění třídu obohatí i novou implementací, interface třídě pouze přikáže, že má obsahovat nějaké metody. Rozhraní můžeme ve třídě implementovat kolik chceme, oddělují se čárkou.
V index.php
si vytvoříme nějakého Javistu:
{PHP} require_once('tridy/Clovek.php'); require_once('tridy/Programator.php'); require_once('tridy/Javista.php'); mb_internal_encoding("UTF-8"); $javista = new Javista('Tomáš', 'Marný', 28, 'mamradsvyheslo', 'NetBeans'); echo("Vše OK");
{PHP} class Clovek { const MINIMALNI_DELKA_HESLA = 5; private int $unava = 0; public int $id; private static int $pocetLidi = 0; public function __construct(public string $jmeno, public string $prijmeni, public int $vek, private string $heslo) { self::$pocetLidi++; $this->id = self::$pocetLidi; } public function spi(int $doba): void { $this->unava -= $doba * 10; if ($this->unava < 0) $this->unava = 0; } public function behej(int $vzdalenost): void { if ($this->unava + $vzdalenost <= 20) $this->unava += $vzdalenost; else echo('Jsem příliš unavený.'); } public function pozdrav(): void { echo('Ahoj, já jsem ' . $this->jmeno); } protected function celeJmeno(): string { return $this->jmeno . ' ' . $this->prijmeni; } public function __toString(): string { return $this->jmeno . ' - ' . $this->id; } public static function validniHeslo(string $heslo): bool { return (mb_strlen($heslo) >= self::MINIMALNI_DELKA_HESLA); } }
{PHP} interface Programator { public function programuj(): void; }
{PHP} class Javista extends Clovek implements Programator { public function __construct(string $jmeno, string $prijmeni, int $vek, string $heslo, public string $ide) { parent::__construct($jmeno, $prijmeni, $vek, $heslo); } public function pozdrav(): void { echo('Hello world! Jsem ' . $this->jmeno); } public function programuj(): void { echo("Programuji v {$this->ide}..."); } }
Když aplikaci zkusíme spustit, vše funguje stejně dobře, jako předtím.
Nyní se přesuňte do třídy Javista
a metodu
programuj()
zakomentujte. Když znovu přejdeme na index, zobrazí
se nám následující chybová hláška:
Hláška nám sděluje, že třída Javista
implementuje
rozhraní a přitom v implementaci nemá všechny metody, které toto rozhraní
obsahuje. Chybu bychom dostali i v případě, že bychom metodu
programuj()
implementovali, avšak předepsali jí jiný
návratový typ, než ten předepsaný v rozhraní (void
), dali jí
jiný počet parametrů, nebo jim předepsali jiný datový typ. Metodu opět
odkomentujeme.
Význam rozhraní
Rozhraní má v programování hned několik významů.
Předpis metod
Jak jsme si nyní vyzkoušeli, umožňuje předepsat třídě jaké
metody musí obsahovat. Podobně jako např. modifikátory viditelnosti
(private
) je to další z nástrojů, který nám umožňuje
hlídat správnou funkčnost kódu.
Zjištění přítomnosti rozhraní
Vraťme si do index.php
naše pole lidí s několika instancemi
Javistů a lidí. V cyklu tyto instance projedeme a necháme je pozdravit. Pokud
je daná instance programátor, necháme ho programovat. Všimněte si, že
říkám programátor, ne Javista. Můžeme se totiž zeptat zda instance
implementuje určité rozhraní:
{PHP} require_once('tridy/Clovek.php'); require_once('tridy/Programator.php'); require_once('tridy/Javista.php'); mb_internal_encoding("UTF-8"); $lide = array(); $lide[] = new Clovek('Karel', 'Novák', 30, 'felix15'); $lide[] = new Javista('Jan', 'Nový', 24, 'mamradsvykolo', 'Eclipse'); $lide[] = new Clovek('Josef', 'Nový', 50, 'password'); $lide[] = new Javista('Tomáš', 'Marný', 28, 'mamradsvyheslo', 'NetBeans'); $lide[] = new Clovek('Marie', 'Nová', 32, 'tohlen3uhodn3s'); foreach ($lide as $clovek) { echo($clovek); echo('<br />'); if ($clovek instanceof Programator) { $clovek->programuj(); echo('<br />'); } }
{PHP} class Clovek { private int $unava = 0; public int $id; private static int $pocetLidi = 0; public function __construct(public string $jmeno, public string $prijmeni, public int $vek, private string $heslo) { self::$pocetLidi++; $this->id = self::$pocetLidi; } public function spi(int $doba): void { $this->unava -= $doba * 10; if ($this->unava < 0) $this->unava = 0; } public function behej(int $vzdalenost): void { if ($this->unava + $vzdalenost <= 20) $this->unava += $vzdalenost; else echo('Jsem příliš unavený.'); } public function pozdrav(): void { echo('Ahoj, já jsem ' . $this->jmeno); } protected function celeJmeno(): string { return $this->jmeno . ' ' . $this->prijmeni; } public function __toString(): string { return $this->jmeno . ' - ' . $this->id; } public static function validniHeslo(string $heslo): bool { return (mb_strlen($heslo) >= 5); } }
{PHP} interface Programator { public function programuj(): void; }
{PHP} class Javista extends Clovek implements Programator { public function __construct(string $jmeno, string $prijmeni, int $vek, string $heslo, public string $ide) { parent::__construct($jmeno, $prijmeni, $vek, $heslo); } public function pozdrav(): void { echo('Hello world! Jsem ' . $this->jmeno); } public function programuj(): void { echo("Programuji v {$this->ide}..."); } }
Aplikaci spustíme:
Ve výsledku vidíme, že jsme všechny lidi vypsali a pokud s
člověkem můžeme pracovat jako s programátorem, tak ho necháme i
programovat. Ke zjištění přítomnosti rozhraní jsme použili
operátor instanceof
.
Tato technika je velmi zajímavá a umožňuje nám ptát se jen zda objekt v
proměnné obsahuje jen nějakou část, kterou zrovna potřebujeme. Je nám
docela jedno, jestli je v proměnné $clovek
zrovna Javista nebo
programátor v PHP, děrných štítcích a podobně. Zajímá nás zkrátka
jestli objekt vystavuje rozhraní programátor.
Zjištění typu proměnné
S instanceof
vlastně zjišťujeme typ instance v proměnné.
Můžeme se jednoduše ptát na typ instance v proměnné jen s využitím OOP.
Bez instanceof
bychom si museli na javistovi např. definovat
nějakou konstantu TYP_CLOVEKA
s hodnotou "programator". A
kdybychom chtěli, aby byl zároveň programátor a šachista, opět bychom
měli problém. Takto jen implementujeme další rozhraní.
Když se ptáme na přítomnost rozhraní, nemusíme uvést pouze interface,
ale klidně třídu. Kód by fungoval podobně i s touto podmínkou, avšak
volal by pouze instance třídy Javista
:
if ($clovek instanceof Javista)
Samotná třída se tedy chová také jako rozhraní. instanceof
funguje i při dědění. Pokud několikrát podědíme a zeptáme se pomocí
instanceof
na nějakého z předků, bude podmínka platit.
V silně typovaných jazycích (např. v Javě) má rozhraní ještě mnohem větší význam, v PHP se bez něj v podstatě obejdete a sám se přiznám, že ho příliš nepoužívám. Občas je však vhodné ho použít, zejména pokud pracujeme s nějakou složitější hierarchií tříd.
Pojmenování rozhraní
Na závěr si krátce řekněme i něco o tom, jak se rozhraní
pojmenovávají. Pokud se budeme držet konvencí Javy, platí pro pojmenování
rozhraní PascalCase, stejně jako u tříd. Existuje konvence, která
předepisuje, že by všechna rozhraní v PHP měla končit na "Interface".
Měli bychom tedy např. rozhraní ProgramatorInterface
.
V angličtině se rozhraní často pojmenovávají s koncovkou -able, např.
Drawable
(něco, co jde vykreslit, předepisuje např. metodu
paint()
), Sortable
a podobně. Česky by to bylo asi
Vykreslitelny
, což také nezní špatně. Programátor je zrovna
případ, kde se název rozhraní vymýšlí špatně, možná by se mohlo
jmenovat Programujici
.
Zdrojové kódy naleznete jako vždy ke stažení pod článkem.
V příští lekci, Rozhraní a abstraktní třída v PHP, budeme s rozhraním pokračovat a uvedeme si tzv. abstraktní třídu.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 619x (4.97 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP