IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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:

let kostka = Kostka() // v tuto chvíli se zavolá konstruktor
print(kostka.vratPocetSten())
class Kostka {
    private var pocetSten : Int

    init() {
        pocetSten = 6
    }

    func vratPocetSten() -> Int {
        return pocetSten
    }
}

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:

let kostka = Kostka(aPocetSten: 10)
print(kostka.vratPocetSten())
class Kostka {
    private var pocetSten : Int

    init(aPocetSten: Int) {
        pocetSten = aPocetSten
    }

    func vratPocetSten() -> Int {
        return pocetSten
    }
}

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):

let sestistenna = Kostka()
let desetistenna = Kostka(aPocetSten: 10)
print(sestistenna.vratPocetSten())
print(desetistenna.vratPocetSten())
class Kostka {
    private var pocetSten : Int

    init() {
        pocetSten = 6
    }

    init(aPocetSten: Int) {
        pocetSten = aPocetSten
    }

    func vratPocetSten() -> Int {
        return pocetSten
    }
}

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:

Napovídání přetížení metod v Xcode - Objektově orientované programování ve Swift

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:

let sestistenna = Kostka()
let desetistenna = Kostka(pocetSten: 10)

print(sestistenna)
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
    }
}

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 :-)

class Kostka : CustomStringConvertible {

    var description: String {
        return "Kostka s \(pocetSten) stěnami"
    }

    // ... zbytek třídy Kostka
let sestistenna = Kostka()
let desetistenna = Kostka(pocetSten: 10)

print(sestistenna)

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á:

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: " ")
}
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
    }
}

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 16x (21.09 kB)
Aplikace je včetně zdrojových kódů v jazyce Swift

 

Předchozí článek
Řešené úlohy k 1.-2. lekci OOP ve Swift
Všechny články v sekci
Objektově orientované programování ve Swift
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 3. lekci OOP ve Swift
Článek pro vás napsal Filip Němeček
Avatar
Uživatelské hodnocení:
7 hlasů
Autor se věnuje vývoji iOS aplikací (občas macOS)
Aktivity