Lekce 3 - Hrací kostka ve Swift - Zapouzdření, konstruktor a Random
V předešlém cvičení, Řešené úlohy k 1.-2. lekci OOP ve Swift, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V dnešním Swift tutoriálu začneme pracovat na slíbené aréně, ve které budou proti sobě bojovat dva bojovníci. Boj bude tahový (na přeskáčku) a bojovník vždy druhému ubere život na základě síly jeho útoku a obrany druhého bojovníka. Simulujeme v podstatě stolní hru, budeme tedy simulovat i hrací kostku, která dodá hře prvek náhodnosti. Začněme zvolna a vytvořme si dnes právě tuto hrací kostku. Zároveň se naučíme jak definovat vlastní konstruktor.
Vytvoření projektu
Vytvořme si novou Command Line Tool aplikaci a pojmenujme ji
Arena. K projektu si přidejme novou class s názvem
Kostka. Zamysleme se nad vlastnostmi, které kostce dáme. Jistě
by se hodilo, kdybychom si mohli zvolit počet stěn kostky (klasicky 6 nebo 10
stěn, jak je zvykem u tohoto typu her). Dále bude kostka potřebovat tzv.
generátor náhodných čísel. Ten nám samozřejmě poskytne Swift, který k
těmto účelům obsahuje speciální funkci. Naše třída bude mít nyní
zatím vlastnost pocetStran, kterou pro výchozí stav rovnou
nastavíme na 6, ať nemusíme řešit Optional nebo
psát konstruktor.
Zapouzdření
Minule jsme kvůli jednoduchosti nastavovali všechny vlastnosti naší
třídy jako public, tedy jako veřejně přístupné. Většinou
se však spíše nechce, aby se daly zvenčí modifikovat a používá se
modifikátor private. Vlastnost je poté viditelná jen uvnitř
třídy a zvenčí se Swift tváří, že vůbec neexistuje. Při návrhu
třídy tedy nastavíme vše na private a v případě, že něco
bude opravdu potřeba vystavit, smažeme private a tím se použije
internal (takže pro celý modul). Naše třída nyní vypadá asi
takto:
class Kostka { private var pocetSten = 6 }
Konstruktory
Až doposud jsme neuměli zvenčí nastavit jiné vlastnosti než
public, protože např. private nejsou zvenčí
viditelné. Již jsme si říkali něco málo o konstruktoru objektu. Je to
metoda, která se zavolá ve chvíli vytvoření instance
objektu. Slouží samozřejmě k nastavení vnitřního stavu objektu a
k provedení případné inicializace. Kostku bychom nyní v
main.swift vytvořili takto:
let kostka = Kostka()
Právě Kostka() je konstruktor. Protože v naší třídě
žádný není, Swift si dogeneruje prázdnou metodu. My si však nyní
konstruktor do třídy přidáme. Deklaruje se jako metoda, ale nemá
návratový typ a musí mít název
init(). V konstruktoru nastavíme počet stěn na pevnou
hodnotu a a tím pádem už nemusíme mít pocetSten nastaven
přímo v deklaraci, je ale třeba přidat datový typ. Třída
Kostka bude vypadat následovně:
private var pocetSten : Int init() { pocetSten = 6 }
Pokud kostku nyní vytvoříme, bude mít ve vlastnosti
pocetSten hodnotu 6. Vypišme si počet stěn do
konzole, ať vidíme, že tam hodnota opravdu je. Není dobré vlastnost
nastavit na public/internal, protože nebudeme chtít,
aby nám někdo mohl již u vytvořené kostky měnit počet stěn.
Přidáme do třídy tedy metodu vratPocetSten(), která nám
vrátí hodnotu vlastnosti pocetSten. Docílili jsme tím v
podstatě toho, že je vlastnost read-only (vlastnost není viditelná a lze ji
pouze číst metodou, změnit ji nelze). Swift má k tomuto účelu ještě
další konstrukce, ale tím se zatím nebudeme zabývat. Nová metoda bude
vypadat asi takto:
func vratPocetSten() -> Int { return pocetSten }
Přesuňme se do main.swift a vyzkoušejme si vytvořit kostku a
vypsat počet stěn:
{SWIFT} let kostka = Kostka() // v tuto chvíli se zavolá konstruktor print(kostka.vratPocetSten()) {/SWIFT}{SWIFT} class Kostka { private var pocetSten : Int init() { pocetSten = 6 } func vratPocetSten() -> Int { return pocetSten } } {/SWIFT}
Výstup:
6
Vidíme, že se konstruktor opravdu zavolal. My bychom ale chtěli, abychom mohli u každé kostky při vytvoření specifikovat, kolik stěn budeme potřebovat. Dáme tedy konstruktoru parametr:
init(aPocetSten: Int) {
pocetSten = aPocetSten
}
Všimněte si, že jsme před název parametru přidali a,
abychom jej odlišili od stejnojmenné vlastnosti třídy. Vraťme se k
main.swift a zadejme tento parametr do konstruktoru:
{SWIFT} let kostka = Kostka(aPocetSten: 10) print(kostka.vratPocetSten()) {/SWIFT}{SWIFT} class Kostka { private var pocetSten : Int init(aPocetSten: Int) { pocetSten = aPocetSten } func vratPocetSten() -> Int { return pocetSten } } {/SWIFT}
Výstup:
10
Vše funguje, jak jsme očekávali. Swift nám již v tuto chvíli
nevygeneruje prázdný (tzv. bezparametrický konstruktor), takže kostku bez
parametru vytvořit nelze. My to však můžeme umožnit, vytvořme si další
konstruktor a tentokrát bez parametru. V něm nastavíme počet stěn na
6, protože takovou hodnotu asi uživatel naší třídy u kostky
očekává jako výchozí:
init() { pocetSten = 6 }
Zkusme si nyní vytvořit 2 instance kostky, každou jiným konstruktorem (v
main.swift):
{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(aPocetSten: 10) print(sestistenna.vratPocetSten()) print(desetistenna.vratPocetSten()) {/SWIFT}{SWIFT} class Kostka { private var pocetSten : Int init() { pocetSten = 6 } init(aPocetSten: Int) { pocetSten = aPocetSten } func vratPocetSten() -> Int { return pocetSten } } {/SWIFT}
Výstup:
6 10
Swiftu nevadí, že máme 2 metody se stejným názvem, protože jejich
parametry jsou různé. Hovoříme o tom, že metoda init() (tedy
zde konstruktor) má přetížení (overload). Toho můžeme
využívat i u všech dalších metod, nejen u konstruktorů. Xcode nám
přetížení u metod přehledně nabízí (ve chvíli, kdy za název metody
napíšeme levou závorku), variantami metody si můžeme listovat pomocí
šipek. V nabídce vidíme naše 2 konstruktory:

Mnoho metod ve Swift má hned několik přetížení, zkuste se podívat
např. na metodu append() na Stringu. Je dobré si u
metod projít jejich přetížení, abyste neprogramovali něco, co již někdo
udělal před vámi.
Ukážeme si ještě, jak jde obejít nepraktický název atributu u
parametrického konstruktoru (v našem případě aPocetSten) a
potom konstruktory na chvíli opustíme. Problém je samozřejmě v tom, že
když napíšeme:
init(pocetSten: Int) {
pocetSten = pocetSten
}
Swift neví, kterou z proměnných myslíme, jestli parametr nebo atribut. V
tomto případě přiřazujeme do parametru znovu ten samý parametr. Xcode nás
na tuto skutečnost dokonce upozorní. Uvnitř třídy se máme možnost
odkazovat na její instanci, je uložena v proměnné
self. Využití si můžeme představit např.
kdyby kostka měla metodu dejHraci(hrac: Hrac) a tam by volala
hrac.seberKostku(self). Zde bychom hráči pomocí
self předali sebe sama, tedy tu konkrétní
kostku, se kterou pracujeme. My se tím zde nebudeme zatěžovat, ale využijeme
odkazu na instanci při nastavování atributu:
init(pocetSten: Int) { self.pocetSten = pocetSten }
Pomocí self jsme specifikovali, že levá proměnná
pocetSten náleží instanci, pravou Swift chápe jako z parametru.
Máme tedy 2 konstruktory, které nám umožňují tvořit různé hrací
kostky. Přejděme dál.
Náhodná čísla
Definujme na kostce metodu hod(), která nám vrátí náhodné
číslo od 1 do počtu stěn. Je to velmi jednoduché, metoda bude
internal (půjde volat zvenčí) a nebude mít žádný parametr.
Návratová hodnota bude typu Int. Swift nám nabízí hezké
metody random() na číslech a dokonce i na typu bool
(kde dostaneme náhodně true nebo false). My chceme
celá čísla, takže zavoláme metodu random() na typu
Int následovně: Int.random(in: 1...pocetSten).
Metoda hod() tedy bude vypadat následovně:
func hod() -> Int { return Int.random(in: 1...pocetSten) }
Výpis informací o instanci třídy Kostka
Kostka je téměř hotová, ukažme si ještě jednu užitečnou vlastnost,
kterou ji přidáme a kterou budeme hojně používat i ve většině našich
dalších objektů. Swift využívá vlastnost description a je
určena k tomu, aby vrátila tzv. textovou reprezentaci
instance. Hodí se ve všech případech, kdy si instanci potřebujeme
vypsat nebo s ní pracovat jako s textem. Tuto vlastnost mají např. i čísla
a využívá ji Swift, když tyto proměnné použijeme v interpolovaném
Stringu nebo v metodě print(). Pokud si děláme
vlastní třídu, měli bychom zvážit, zda se nám takováto vlastnost
nehodí. Nikdy bychom si neměli dělat vlastní metodu, např. něco jako
vypis(), když máme ve Swiftu připravenou cestu, jak toto
řešit. U kostky nemá description vyšší smysl, ale u
bojovníka bude jistě vracet jeho jméno. My si ji ke kostce stejně přidáme,
bude vypisovat, že se jedná o kostku a vrátí i počet stěn. Nejprve si
zkusme vypsat naši instanci kostky:
{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(pocetSten: 10) print(sestistenna) {/SWIFT}{SWIFT} class Kostka { private var pocetSten : Int init() { pocetSten = 6 } init(pocetSten: Int) { self.pocetSten = pocetSten } func vratPocetSten() -> Int { return pocetSten } func hod() -> Int { return Int(arc4random_uniform(UInt32(pocetSten))) + 1 } } {/SWIFT}
Do konzole se vypíše pouze cesta k naší třídě, tedy
Arena.Kostka. Níže si ukážeme jak vlastnost description
definovat. Kód je pro nás zatím komplexní, takže ho moc neřešte, techniky
budou vysvětleny v dalších lekcích 
{SWIFT} class Kostka : CustomStringConvertible { var description: String { return "Kostka s \(pocetSten) stěnami" } // ... zbytek třídy Kostka private var pocetSten : Int init() { pocetSten = 6 } init(pocetSten: Int) { self.pocetSten = pocetSten } func vratPocetSten() -> Int { return pocetSten } func hod() -> Int { return Int(arc4random_uniform(UInt32(pocetSten))) + 1 } } {/SWIFT}{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(pocetSten: 10) print(sestistenna) {/SWIFT}
description jsem implementovali jako speciální vlastnost,
která umožňuje pouze čtení. V definici třídy můžete vidět přidané
: CustomStringConvertible - jedná se o protokol, který
specifikuje, co třída umí. Protokoly si vysvětlíme v jedné z
následujících lekcí.
Nyní opět zkusíme do konzole vypsat přímo instanci kostky.
Výstup:
Kostka s 6 stěnami
Ještě si naše kostky vyzkoušíme. Zkusíme si v programu s našima dvěma kostkami v cyklech házet a podíváme se, jestli fungují tak, jak se očekává:
{SWIFT} let sestistenna = Kostka() let desetistenna = Kostka(pocetSten: 10) // hod šestistěnnou print(sestistenna) for _ in 1...10 { print(sestistenna.hod(), terminator: " ") } // hod desetistěnnou print("\n\n \(desetistenna)") for _ in 1...10 { print(desetistenna.hod(), terminator: " ") } {/SWIFT}{SWIFT} class Kostka : CustomStringConvertible { var description: String { return "Kostka s \(pocetSten) stěnami" } private var pocetSten : Int init() { pocetSten = 6 } init(pocetSten: Int) { self.pocetSten = pocetSten } func vratPocetSten() -> Int { return pocetSten } func hod() -> Int { return Int(arc4random_uniform(UInt32(pocetSten))) + 1 } } {/SWIFT}
Výstup může vypadat nějak takto:
Kostka s 6 stěnami 3 6 6 1 6 3 6 2 6 3 Kostka s 10 stěnami 5 9 9 2 10 4 9 3 10 5
Máme hotovou poměrně hezkou a nastavitelnou třídu, která reprezentuje hrací kostku. Bude se nám hodit v naší aréně, ale můžete ji použít i kdekoli jinde. Vidíme, jak OOP umožňuje znovupoužívat komponenty.
V následujícím cvičení, Řešené úlohy k 3. lekci OOP ve Swift, si procvičíme nabyté zkušenosti z předchozích lekcí.
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 17x (21.09 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift
