Skip to content

Commit

Permalink
oop: improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 5, 2024
1 parent 4fcc406 commit fd6b154
Show file tree
Hide file tree
Showing 16 changed files with 2,329 additions and 383 deletions.
168 changes: 145 additions & 23 deletions nette/bg/introduction-to-object-oriented-programming.texy

Large diffs are not rendered by default.

168 changes: 145 additions & 23 deletions nette/cs/introduction-to-object-oriented-programming.texy
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ $osoba = new Osoba(25);
echo $osoba->kolikJeTiLet(); // Vypíše: 25
```

V tomto příkladě třída `Osoba` má vlastnost `$vek` a dále konstruktor, který nastavuje tuto vlastnost. Metoda `kolikJeTiLet()` pak umožňuje přístup k věku osoby.
V tomto příkladě třída `Osoba` má vlastnost (proměnnou) `$vek` a dále konstruktor, který nastavuje tuto vlastnost. Metoda `kolikJeTiLet()` pak umožňuje přístup k věku osoby.

Pseudoproměnná `$this` se používá uvnitř třídy pro přístup k vlastnostem a metodám objektu.

Klíčové slovo `new` se používá k vytvoření nové instance třídy. Ve výše uvedeném příkladu jsme vytvořili novou osobu s věkem 25.

Expand Down Expand Up @@ -80,7 +82,7 @@ echo $osoba->kolikJeTiLet(); // Vypíše: 20

V tomto příkladě, pokud nezadáte věk při vytváření objektu `Osoba`, bude použita výchozí hodnota 20.

A nakonec, definice vlastnosti s její inicializací přes konstruktor se dá takto zkrátit a zjednodušit:
Příjemné je, že definice vlastnosti s její inicializací přes konstruktor se dá takto zkrátit a zjednodušit:

```php
class Osoba
Expand All @@ -92,6 +94,8 @@ class Osoba
}
```

Pro úplnost, kromě konstruktorů mohou mít objekty i destruktory (metoda `__destruct`), které se zavolají před tím, než je objekt uvolněn z paměti.


Jmenné prostory
---------------
Expand Down Expand Up @@ -152,9 +156,9 @@ class Osoba
$this->vek = $vek;
}

function kolikJeTiLet()
function vypisInformace()
{
return $this->vek;
echo "Věk: {$this->age} let\n";
}
}

Expand All @@ -170,8 +174,8 @@ class Student extends Osoba

function vypisInformace()
{
echo 'Věk studenta: ', $this->kolikJeTiLet();
echo 'Obor studia: ', $this->obor;
parent::vypisInformace();
echo "Obor studia: {$this->obor} \n";
}
}

Expand All @@ -183,12 +187,26 @@ Jak tento kód funguje?

- Použili jsme klíčové slovo `extends` k rozšíření třídy `Osoba`, což znamená, že třída `Student` zdědí všechny metody a vlastnosti z `Osoby`.

- Klíčové slovo `parent::` nám umožňuje volat metody z nadřazené třídy. V tomto případě jsme volali konstruktor z třídy `Osoba` před přidáním vlastní funkcionality do třídy `Student`.
- Klíčové slovo `parent::` nám umožňuje volat metody z nadřazené třídy. V tomto případě jsme volali konstruktor z třídy `Osoba` před přidáním vlastní funkcionality do třídy `Student`. A obdobně i metodu `vypisInformace()` předka před vypsáním informací o studentovi.

Dědičnost je určená pro situace, kdy existuje vztah "je" mezi třídami. Například `Student` je `Osoba`. Kočka je zvíře. Dává nám možnost v případech, kdy v kódu očekáváme jeden objekt (např. "Osoba"), použít místo něj objekt zděděný (např. "Student").

Je důležité si uvědomit, že hlavním účelem dědičnosti **není** zabránit duplikaci kódu. Naopak, nesprávné využití dědičnosti může vést k složitému a těžko udržitelnému kódu. Pokud vztah "je" mezi třídami neexistuje, měli bychom místo dědičnosti uvažovat o kompozici.

Všimněte si, že metody `vypisInformace()` ve třídách `Osoba` a `Student` vypisují trochu jiné informace. A můžeme doplnit další třídy (například `Zamestnanec`), které budou poskytovat další implementace této metody. Schopnost objektů různých tříd reagovat na stejnou metodu různými způsoby se nazývá polymorfismus:

```php
$osoby = [
new Osoba(30),
new Student(20, 'Informatika'),
new Zamestnanec(45, 'Ředitel'),
];

foreach ($osoby as $osoba) {
$osoba->vypisInformace();
}
```


Kompozice
---------
Expand All @@ -202,7 +220,7 @@ class Motor
{
function zapni()
{
echo "Motor běží.";
echo 'Motor běží.';
}
}

Expand All @@ -218,7 +236,7 @@ class Auto
function start()
{
$this->motor->zapni();
echo "Auto je připraveno k jízdě!";
echo 'Auto je připraveno k jízdě!';
}
}

Expand Down Expand Up @@ -386,7 +404,29 @@ $kocka = new Kocka;
$kocka->vydejZvuk();
```

Pokud třída implementuje rozhraní, ale nejsou v ní definované všechny očekávané metody, PHP vyhodí chybu. Třída může implementovat více rozhraní najednou, což je rozdíl oproti dědičnosti, kde může třída dědit pouze od jedné třídy.
Pokud třída implementuje rozhraní, ale nejsou v ní definované všechny očekávané metody, PHP vyhodí chybu.

Třída může implementovat více rozhraní najednou, což je rozdíl oproti dědičnosti, kde může třída dědit pouze od jedné třídy:

```php
interface Hlidac
{
function hlidejDum();
}

class Pes implements Zvire, Hlidac
{
public function vydejZvuk()
{
echo 'Haf';
}

public function hlidejDum()
{
echo 'Pes bedlivě střeží dům';
}
}
```


Abstraktní třídy
Expand Down Expand Up @@ -422,6 +462,8 @@ $instance->abstraktniMetoda();

V tomto příkladu máme abstraktní třídu s jednou obyčejnou a jednou abstraktní metodou. Poté máme třídu `Potomek`, která dědí z `AbstraktniTrida` a poskytuje implementaci pro abstraktní metodu.

Jak se vlastně liší rozhraní a abstraktních tříd? Abstraktní třídy mohou obsahovat jak abstraktní, tak konkrétní metody, zatímco rozhraní pouze definují, jaké metody musí třída implementovat, ale neposkytují žádnou implementaci. Třída může dědit jen od jedné abstraktní třídy, ale může implementovat libovolný počet rozhraní.


Typová kontrola
---------------
Expand Down Expand Up @@ -450,7 +492,7 @@ class Osoba

public function vypisVek(): void
{
echo "Této osobě je " . $this->vek . " let.";
echo "Této osobě je {$this->vek} let.";
}
}

Expand All @@ -465,6 +507,21 @@ function vypisVekOsoby(Osoba $osoba): void

Tímto způsobem jsme zajistili, že náš kód očekává a pracuje s daty správného typu, což nám pomáhá předcházet potenciálním chybám.

Některé typy nelze v PHP přímo zapsat. V takovém případě se uvádí v phpDoc komentáři, což je standardní formát pro dokumentaci PHP kódu začínající `/**` a končící `*/`. Umožňuje přidávat popisy tříd, metod a tak dále. A také uvádět komplexní typy pomocí tzv. anotací `@var`, `@param` a `@return`. Tyto typy pak využívají nástroje pro statickou analýzu kódu, ale samotné PHP je nekontroluje.

```php
class Seznam
{
/** @var array<Osoba> zápis říká, že jde o pole objektů Osoba */
private array $osoby = [];

public function pridatOsobu(Osoba $osoba): void
{
$this->osoby[] = $osoba;
}
}
```


Porovnávání a identita
----------------------
Expand Down Expand Up @@ -635,31 +692,47 @@ Traity vám umožní snadno a efektivně sdílet kód mezi třídami. Přitom ne
Výjimky
-------

Výjimky v OOP nám umožňují řídit a ošetřovat chyby, které mohou vzniknout během provádění našeho kódu. Jsou to vlastně objekty určené k zaznamenání chyb nebo nečekaných situací ve vašem programu.

V PHP máme pro tyto objekty k dispozici vestavěnou třídu `Exception`. Ta má několik metod, které nám umožňují získat více informací o výjimce, jako je zpráva o chybě, soubor a řádek, kde k chybě došlo, atd.
Výjimky v OOP nám umožňují elegantně zpracovávat chyby a neočekávané situace v našem kódu. Jsou to objekty, které nesou informace o chybě nebo neobvyklé situaci.

Když se objeví problém, můžeme "vyhodit" výjimku (použitím `throw`). Chceme-li tuto výjimku "chytit" a nějak zpracovat, použijeme bloky `try` a `catch`.
V PHP máme vestavěnou třídu `Exception`, která slouží jako základ pro všechny výjimky. Ta má několik metod, které nám umožňují získat více informací o výjimce, jako je zpráva o chybě, soubor a řádek, kde k chybě došlo, atd.

Ukážeme si, jak to funguje:
Když v kódu nastane chyba, můžeme "vyhodit" výjimku pomocí klíčového slova `throw`.

```php
try {
throw new Exception('Zpráva vysvětlující důvod výjimky');
function deleni(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Dělení nulou!');
}
return $a / $b;
}
```

// Tento kód se neprovede
echo 'Jsem zpráva, kterou nikdo nepřečte';
Když funkce `deleni()` dostane jako druhý argument nulu, vyhodí výjimku s chybovou zprávou `'Dělení nulou!'`. Abychom zabránili pádu programu při vyhození výjimky, zachytíme ji v bloku `try/catch`:

```php
try {
echo deleni(10, 0);
} catch (Exception $e) {
echo 'Výjimka zachycena: '. $e->getMessage();
}
```

Důležité je, že výjimka může být vyhozena hlouběji, při volání jiných metod.
Kód, který může vyhodit výjimku, je zabalen do bloku `try`. Pokud je výjimka vyhozena, provádění kódu se přesune do bloku `catch`, kde můžeme výjimku zpracovat (např. vypsat chybovou zprávu).

Pro jeden blok `try` lze uvést více bloků `catch`, pokud očekáváte různé typy výjimek.
Po blocích `try` a `catch` můžeme přidat nepovinný blok `finally`, který se provede vždy, ať už byla výjimka vyhozena nebo ne (dokonce i v případě, že v bloku `try` nebo `catch` použijeme příkaz `return`, `break` nebo `continue`):

Zároveň můžeme vytvořit hierarchii výjimek, kde každá třída výjimky dědí od té předchozí. Jako příklad si představme jednoduchou bankovní aplikaci, která umožňuje provádět vklady a výběry:
```php
try {
echo deleni(10, 0);
} catch (Exception $e) {
echo 'Výjimka zachycena: '. $e->getMessage();
} finally {
// Kód, který se provede vždy, ať už byla výjimka vyhozena nebo ne
}
```

Můžeme také vytvořit vlastní třídy (hierarchii) výjimek, které dědí od třídy Exception. Jako příklad si představme jednoduchou bankovní aplikaci, která umožňuje provádět vklady a výběry:

```php
class BankovniVyjimka extends Exception {}
Expand Down Expand Up @@ -691,7 +764,11 @@ class BankovniUcet
return $this->zustatek;
}
}
```

Pro jeden blok `try` lze uvést více bloků `catch`, pokud očekáváte různé typy výjimek.

```php
$ucet = new BankovniUcet;
$ucet->vlozit(500);

Expand All @@ -709,6 +786,51 @@ try {
V tomto příkladu je důležité si všimnout pořadí bloků `catch`. Protože všechny výjimky dědí od `BankovniVyjimka`, pokud bychom tento blok měli první, zachytily by se v něm všechny výjimky, aniž by se kód dostal k následujícím `catch` blokům. Proto je důležité mít specifičtější výjimky (tj. ty, které dědí od jiných) v bloku `catch` výše v pořadí než jejich rodičovské výjimky.


Iterace
-------

V PHP můžete procházet objekty pomocí `foreach` smyčky, podobně jako procházíte pole. Aby to fungovalo, objekt musí implementovat speciální rozhraní.

První možností je implementovat rozhraní `Iterator`, které má metody `current()` vracející aktuální hodnotu, `key()` vracející klíč, `next()` přesouvající se na další hodnotu, `rewind()` přesouvající se na začátek a `valid()` zjišťující, zda ještě nejsme na konci.

Druhou možností je implementovat rozhraní `IteratorAggregate`, které má jen jednu metodu `getIterator()`. Ta buď vrací zástupný objekt, který bude zajišťovat procházení, nebo může představovat generátor, což je speciální funkce, ve které se používá `yield` pro postupné vracení klíčů a hodnot:

```php
class Osoba
{
public function __construct(
public int $vek,
) {
}
}

class Seznam implements IteratorAggregate
{
private array $osoby = [];

public function pridatOsobu(Osoba $osoba): void
{
$this->osoby[] = $osoba;
}

public function getIterator(): Generator
{
foreach ($this->osoby as $osoba) {
yield $osoba;
}
}
}

$seznam = new Seznam;
$seznam->pridatOsobu(new Osoba(30));
$seznam->pridatOsobu(new Osoba(25));

foreach ($seznam as $osoba) {
echo "Věk: {$osoba->vek} let \n";
}
```


Správné postupy
---------------

Expand Down
Loading

0 comments on commit fd6b154

Please sign in to comment.