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 String
u. 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
String
u 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