← Přehled všech hodin

Dědičnost — bestiář

Cíl hodiny: Přestaneš psát stejný kód třikrát. Společné věci dáš do jedné třídy a každý druh příšery si z ní jen vezme, co potřebuje, a zbytek udělá po svém.
Jak stránku používat: vpravo nahoře je tlačítko Prezentace — schová všechno kromě jednoho bloku a přepíná se šipkami. U ukázek a cvičení je kód vlevo editovatelný: uprav ho a klikni na Spustit (nebo Ctrl+Enter). Program se přeloží a spustí přímo v prohlížeči a vpravo se objeví to, co vypsal do konzole. Nic se neinstaluje.
Kam to dnes směřuje:

1. Vysvětlení a souvislosti

Poznámky pro učiteleJak celou hodinu vést1. Vysvětlení a souvislosti

Tenhle blok je jen pro vás — dětem ho nepromítejte.

Fáze Co se děje Čas
Rozjezd Zvířata na tabuli: co mají společné, čím se liší 10 min
Kopírka Blok 2 — tři třídy, jeden kód 10 min
Dědičnost Bloky 3–4 + ukázky 1–2 20 min
virtual/override Bloky 5–6 + ukázka 3 20 min
abstract a polymorfismus Bloky 7–8 + ukázky 4–5 20 min
Cvičení Děti pracují samy, vy obcházíte 55 min
Závěr Kdo chce, ukáže svůj bestiář 5 min
Hlavní pedagogický cíl

Dnešek má jedinou myšlenku: „Skřet JE příšera." Ne „skřet má příšeru", ne „skřet používá příšeru" — je to příšera, jen zvláštní druh.

Rozjezd dělejte bez počítače. Napište na tabuli tři zvířata (pes, kočka, papoušek) a nechte třídu vypsat, co mají společné (jméno, věk, jí, spí) a čím se liší (zvuk, počet nohou). Společné věci zakroužkujte — to je základní třída. Rozdíly podtrhněte — to jsou override.

Až budou děti umět odpovědět na otázku „je tohle 'je', nebo 'má'?", je hotovo. Syntaxe je zbytek hodiny jen technický detail.

Tři věci, které dnes NEVYSVĚTLUJTE
  1. Rozhraní (interface). Je to blízké téma, ale dnes by se pletlo s abstraktní třídou. Zmínka pro nejrychlejší je v učitelském bloku na konci.
  2. Vícenásobnou dědičnost. C# ji nemá a děti se na ni většinou nezeptají. Když ano: „třída smí mít jen jednoho rodiče, tečka."
  3. Jak funguje volání virtuální metody uvnitř. Stačí: „C# se za běhu podívá, co je to doopravdy za objekt, a zavolá jeho verzi."

Jak pracovat se smíšenou úrovní. Cvičení 1 a 2 jsou mechanická — zvládne je každý. Cvičení 3 (virtual/override) je jádro hodiny, tam se zdržte nejdéle. Cvičení 4 (abstract) je náročnější a slabším stačí, když ho jen opíšou a spustí. Cvičení 5 je odměna — funguje efektně a děti si u něj vymýšlejí vlastní nestvůry.

Nejčastější zádrhel hodiny: CS0534 — potomek neimplementoval abstraktní metodu. Napište to na tabuli i s vysvětlením: „rodič slíbil, že to každý potomek umí. Ty to neumíš, takže tě překladač nepustí."

Blok 1Tři příšery, jedna kopírka1. Vysvětlení a souvislosti

Přečti si obě třídy a najdi rozdíl. Není žádný. Kromě názvu je to dvakrát ten samý kód.

Co se stane, až budeš chtít přidat vlastnost Odmena? Musíš ji dopsat na obě místa. A až přidáš třetího a čtvrtého netvora, tak na čtyři místa. Jednou zapomeneš a hra se začne chovat divně.

Je tu ještě horší problém: nejde je dát do jednoho seznamu. List<Skret> umí jen skřety. Takže bys potřeboval seznam skřetů, seznam vlkodlaků, seznam obrů… a pak tři skoro stejné cykly.

C# na to má jedno klíčové slovo. Vejde se na jeden řádek a vyřeší oboje.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 2Dědičnost: skřet JE příšera1. Vysvětlení a souvislosti

Dvojtečka znamená „je to". class Skret : Prisera čti jako „skřet je příšera".

Skřet nemá nikde napsané Jmeno, Zivoty, Sila ani Popis() — a přesto je má. Zdědil je. Všechno, co je v Prisera veřejné, má automaticky každý potomek.

Slovník, který se ti bude hodit:

  • rodič / základní třída / předek = Prisera,
  • potomek / odvozená třída = Skret, Vlkodlak.

Operátor is se ptá „je tenhle objekt tohoto druhu?" a vrátí true/false. Skřet je zároveň Skret i Prisera — a právě díky tomu půjde dát skřety i vlkodlaky do jednoho List<Prisera>.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 3base(...) — rodič si své údaje vyplní sám1. Vysvětlení a souvislosti

: base(...) zavolá konstruktor rodiče. Čti to jako „nejdřív ať se postará příšera o to svoje, a pak dodělám já to svoje".

Pořadí je vždycky takové: nejdřív rodič, pak potomek. Proto může Vlkodlak v konstruktoru rovnou nastavit VUplnku — v tu chvíli už má jméno, životy i sílu vyplněné.

Bez base(...) by C# hledal u rodiče konstruktor bez parametrů, nenašel by ho a ohlásil CS7036. To je dnes druhá nejčastější chyba.

Potomek smí mít věci navíc. VUplnku má jen vlkodlak — skřet o něm neví a nepotřebuje ho. Dědičnost je jednosměrná: potomek vidí na rodiče, rodič na potomka ne.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 4virtual a override — stejný název, jiné chování1. Vysvětlení a souvislosti

virtual je povolení. Rodič říká: „takhle to dělám já, ale klidně to udělej po svém." Bez virtual potomek metodu přepsat nesmí.

override je využití toho povolení. Potomek napíše metodu se stejným názvem a stejnými parametry a C# bude u jeho objektů volat tuhle verzi.

Obě slova musí sedět k sobě:

  • virtual chybí u rodiče → CS0506,
  • override chybí u potomka → varování a metoda rodiče se schová, což je skoro vždycky chyba,
  • název nebo parametry nesedí → CS0115 (není co přepisovat).

Tohle je chvíle, kdy začne být dědičnost užitečná: jeden název metody, tolik různých chování, kolik máš druhů nestvůr.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 5base.Metoda() — chci původní chování a ještě něco navíc1. Vysvětlení a souvislosti

base.Karta() zavolá verzi rodiče. Potomek tak nemusí opisovat, co už rodič umí — jen k tomu přidá své.

Přečti si vlkodlakovu metodu nahlas: „vezmi kartu, jakou by udělala obyčejná příšera, a připiš k ní řádek o úplňku."

Pozor na past

Kdyby vlkodlak napsal Karta() místo base.Karta(), volal by sám sebe pořád dokola a program by spadl na StackOverflowException. Tečka za base je tu opravdu důležitá.

Skřet žádný override nemá, takže mu zůstala rodičovská verze. Dědičnost je vždycky nabídka, ne povinnost — dokud nepřijde abstract.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 6abstract — plán, který se nedá vyrobit1. Vysvětlení a souvislosti

abstract class se nedá vyrobit. Řádek new Prisera(...) by skončil chybou CS0144. A to je přesně to, co chceme: „obecná příšera" ve hře neexistuje, existuje jen skřet, vlkodlak nebo obr.

abstract metoda nemá tělo — má jen středník. Je to slib: „každý potomek tohle umí, ale každý jinak."

Kdo slib nesplní, nepřeloží se: CS0534: ... does not implement inherited abstract member.

Všimni si, že Karta() klidně volá Zvuk(), i když rodič vůbec neví, co vrátí. Za běhu se použije verze toho objektu, který metodu zavolal.

Kdy abstract a kdy virtual

virtual = rodič má rozumné výchozí chování, potomek ho může změnit. abstract = rodič žádné rozumné chování nemá a nechce hádat.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Blok 7Polymorfismus — jeden seznam, různé chování1. Vysvětlení a souvislosti

Tohle je odměna za celou hodinu. V seznamu jsou tři různé druhy nestvůr, ale cyklus je jeden jediný a nemá v sobě žádné if.

Jak to, že každá vypsala jiný zvuk? Protože C# se při volání Zvuk() podívá, co je objekt doopravdy zač, a zavolá jeho verzi metody. Tomu se říká polymorfismus — mnohotvarost.

List<Prisera> unese cokoliv, co je příšera. Až příště přidáš class Drak : Prisera, stačí ho přidat do seznamu — cyklus měnit nemusíš. To je ten pravý důvod, proč dědičnost existuje.

Operátorem is se dá kdykoliv doptat na konkrétní druh: p is Vlkodlak vrátí true jen u vlkodlaků.

Klikni vlevo na Spustit a objeví se tu výstup programu.
Poznámky pro učiteleTechnické pozadí — jen pro vás1. Vysvětlení a souvislosti

Proč virtual a override explicitně. Java má virtuální metody implicitně, C# ne. Je to schválně: v C# musí autor rodiče dovolit přepsání. Dětem stačí „rodič musí dát svolení", ale je dobré vědět, že za tím stojí snaha zabránit nechtěnému přepsání metody, na kterou se spoléhá zbytek knihovny.

new místo override. Kdo napíše metodu se stejným názvem bez override, dostane varování CS0108 a metoda se jen schová (method hiding). Polymorfismus pak nefunguje — přes proměnnou typu Prisera se zavolá verze rodiče. Je to klasická past a stojí za to ji dětem jednou ukázat naživo.

protected. Mezi public a private je třetí úroveň: vidí na ni potomci, ale ne okolní kód. Používáme ji u konstruktoru abstraktní třídy. Do výkladu ji netlačte, ale kdo se zeptá, ať ji zná — v reálném kódu je běžná.

sealed. Opak dědičnosti: sealed class Drak říká „ze mě už nikdo dědit nebude". Používá se hlavně kvůli výkonu a bezpečnosti návrhu.

interface. Alternativa k abstraktní třídě: čistý seznam schopností bez jakéhokoliv kódu, a třída jich smí mít víc. Až budou děti psát větší projekty, narazí na IComparable nebo IEnumerable. Dnes by to ale téma rozmělnilo.

Kompozice místo dědičnosti. Profesionální pravidlo zní „preferuj kompozici". Skřet zbraň, ale je příšera. Dobrá kontrolní otázka na dítě: „dává větší smysl 'je', nebo 'má'?" Kdo řekne „skřet má příšeru", ten to ještě nemá.

Pozor na hloubku. Víc než dvě úrovně dědičnosti (PriseraSkret) v téhle hře nepotřebujeme. Kdo si postaví pětipatrovou hierarchii, ztratí se v ní — a je to dobrá příležitost mu ukázat, že jednoduchost je vlastnost, ne lenost.

Pořadí v souboru. Jako minule: všechny třídy patří pod top-level příkazy. Na pořadí tříd mezi sebou nezáleží — potomek může být napsaný nad rodičem.

2. Ukázky pro projekci

Blok 8Ukázka 1 — Jeden rodič, tři potomci2. Ukázky pro projekci
Studenti zkuste změnit
  • Přidej čtvrtý druh nestvůry Netopyr.
  • Dej Prisera novou vlastnost Odmena a sleduj, že ji mají hned všichni.
  • Změň text v Popis() a vypiš znovu — změní se u všech tří.
  • Zkus u skřeta napsat vlastní Jmeno a zjisti, že už ho má.
demo/demo1_rodic_potomci.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: tři druhy nestvůr, ale Popis() je napsaný jednou. Dvojtečka za názvem třídy udělala všechnu práci.

Blok 9Ukázka 2 — Potomek má něco navíc2. Ukázky pro projekci
Studenti zkuste změnit
  • Přepni vlkodlakovi VUplnku na false a sleduj rozdíl.
  • Dej obrovi vlastnost Brneni a odečti ji od poškození.
  • Přidej skřetovi vlastnost Zbran s textem a vypiš ji.
  • Zkus se zeptat skřeta na VUplnku — a přečti si chybu.
demo/demo2_navic.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: : base(...) vyplní společné údaje a potomek se pak stará jen o to, čím je zvláštní.

Blok 10Ukázka 3 — virtual, override a base2. Ukázky pro projekci
Studenti zkuste změnit
  • Smaž u vlkodlaka override a přečti si varování.
  • Změň obrovi násobek poškození z 2 na 3.
  • Přidej skřetovi override, který každou pátou ránu mine.
  • Vyměň base.Utok() za Utok() a zjisti, co se stane (pak to vrať!).
demo/demo3_override.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: Utok() má každý jiný, ale volá se pořád stejně. A base.Karta() znamená „to, co by udělal rodič — a ještě něco navíc".

Blok 11Ukázka 4 — abstract: rodič, který se nedá vyrobit2. Ukázky pro projekci
Studenti zkuste změnit
  • Zkus napsat new Prisera("X", 10, 1) a přečti si chybu CS0144.
  • Smaž skřetovi metodu Zvuk() a přečti si chybu CS0534.
  • Přidej do abstraktní třídy další abstraktní metodu Odmena().
  • Uděl z Karta() normální (neabstraktní) metodu — už to tak je, projdi si proč.
demo/demo4_abstraktni.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: abstract je slib, který překladač vymáhá. Díky němu nemůže v bestiáři vzniknout nestvůra, která neumí to, co má.

Blok 12Ukázka 5 — CsQuest: výprava celým bestiářem2. Ukázky pro projekci
Studenti zkuste změnit
  • Přidej vlastní druh nestvůry a zařaď ho do bestiáře.
  • Změň semínko v new Random(5) a uvidíš jiný průběh výpravy.
  • Dej hrdinovi po každém vítězství +1 k síle.
  • Seřaď bestiář podle síly útoku (OrderBy(p => p.Utok())).
demo/demo5_vyprava.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

Co si z toho odnést: hlavní smyčka neví, jestli bojuje se skřetem, nebo s obrem. Ptá se pořád stejně — nestvura.Utok(), nestvura.Odmena() — a každý druh odpoví po svém. Přidat čtvrtou nestvůru znamená přidat třídu, ne měnit hru.

Blok 13Když se něco pokazí2. Ukázky pro projekci
error CS0534: 'Skret' does not implement inherited
  abstract member 'Prisera.Zvuk()'

Chyba číslo jedna dnešní hodiny. Rodič je abstract a slíbil, že každý potomek umí Zvuk(). Skřet ho ale nenapsal.

Oprava je vždycky stejná — doplnit do potomka metodu se stejným názvem, stejnými parametry i návratovým typem a se slovem override:

public override string Zvuk()
{
    return "Grrrmbl!";
}

Druhá nejčastější chyba je CS7036 u konstruktoru: potomek zapomněl : base(...) a rodič nemá konstruktor bez parametrů. Doplň dvojtečku a předej rodiči, co po tobě chce.

A pozor na StackOverflowException: uvnitř override metody musí být base.Neco(), ne Neco() — jinak metoda volá sama sebe donekonečna.

Hláška / projev Co to znamená Jak to opravit
CS0534 potomek neimplementoval abstraktní metodu dopiš ji s override
CS0144 Cannot create an instance of the abstract type new na abstraktní třídě vyrob konkrétního potomka
CS7036 v konstruktoru potomka chybí : base(...) public Skret(...) : base(...)
CS0115 no suitable method found to override název nebo parametry nesedí s rodičem zkontroluj podpis metody znak po znaku
CS0506 cannot override inherited member ... not virtual rodičovská metoda není virtual dopiš virtual k rodiči
CS0108 (varování) potomek metodu jen schoval dopiš override
CS1729 u rodiče rodič nemá konstruktor s takovými parametry uprav base(...) podle rodiče
StackOverflowException override volá sám sebe použij base.Metoda()
CS0122 u vlastnosti rodiče je private uděl z ní protected nebo public
potomek dělá totéž co rodič chybí override nebo se volá přes špatný typ zkontroluj dvojici virtual + override
CS0266 u List<Prisera> do seznamu dáváš něco, co není potomek třída musí mít : Prisera

3. Cvičení

Jak dnes pracovat: všech pět cvičení staví jeden bestiář. Každé cvičení začíná tam, kde skončilo předchozí, a nejpozději u čtvrtého uvidíš, proč se to celé vyplatilo.
Blok 14Cvičení 1 — Rodina příšer (15 min)3. Cvičení

Zadání:

Postav základní třídu Prisera a odvoď z ní tři druhy nestvůr.

  1. Napiš třídu Prisera s jménem, životy, silou a metodou Popis()
    💡 Poradit
    Zatím obyčejná třída, jako minulou hodinu — a jako vždycky až pod příkazy:
    class Prisera
    {
    public string Jmeno { get; set; }
    public int Zivoty { get; set; }
    public int Sila { get; set; }
    public string Popis() { return $"{Jmeno}: {Zivoty} HP"; }
    }
  2. Odvoď z ní Skret a vyrob prvního skřeta
    💡 Poradit
    Dvojtečka znamená „je to":
    class Skret : Prisera
    {
    }
    Skřet hned umí Jmeno, Zivoty, Sila i Popis(), i když je nemá napsané.
  3. Přidej Vlkodlak a KamennyObr a vypiš všechny tři
    💡 Poradit
    Úplně stejně jako skřet — jen jiný název třídy.
    Ověř si dědičnost operátorem is:
    Console.WriteLine(vlk is Prisera);
  4. Dej rodiči konstruktor a všem potomkům : base(...)
    💡 Poradit
    Jakmile dá rodič konstruktor s parametry, musí ho zavolat každý potomek — jinak CS7036. Uprav proto všechny tři najednou:
    public Skret(string jmeno, int zivoty, int sila) : base(jmeno, zivoty, sila)
    {
    }
  5. Přidej každému potomkovi jeden vlastní údaj navíc
    💡 Poradit
    Například public string Zbran { get; set; } u skřeta,
    public bool VUplnku { get; set; } u vlkodlaka,
    public int Brneni { get; set; } u obra.
    Nastav je v konstruktoru potomka za voláním base(...).
Úroveň Co má zvládnout
Minimum Body 1–3, tři potomci dědí Popis()
Rozšíření Bod 4, konstruktor rodiče a : base(...)
Výzva Bod 5 a k tomu čtvrtý druh nestvůry s vlastním údajem
cviceni/cviceni1_rodina.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: tři druhy nestvůr, ani jeden nemá vlastní Popis(), a přesto ho všichni umí.

Kde se zaseknou:

  1. Krok 4 se musí udělat celý najednou. Jakmile rodič dostane konstruktor s parametry, přestane existovat ten prázdný — a všichni tři potomci se rozbijí naráz. Upozorněte na to dřív, než to nastane, ať to není překvapení. Je to přesně ta situace, kterou v programování popisuje věta „změna v rodiči se dotkne všech potomků".
  2. Dvojtečka na dvou místech. class Skret : Prisera a : base(...) vypadají podobně, ale znamenají něco jiného. Napište obojí na tabuli vedle sebe: první je „jsem to a to", druhé je „rodiči, vyplň si své".
  3. Kopírování vlastností do potomka. Někdo si do Skret zopakuje Jmeno. Program se přeloží (a rozbije polymorfismus). Ptejte se: „a proč to tam máš, když už to je v rodiči?"

Rychlíkům zadejte čtvrtý druh (např. Prizrak s vlastností Neviditelny).

Řešení 1 — jedna z možných variant

Toto je jedna z možných variant. Tvoje řešení se může lišit a to je v pořádku.

reseni/reseni1_rodina.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 15Cvičení 2 — Každý netvor po svém (20 min)3. Cvičení

Zadání:

Nestvůry z minula zatím útočí a mluví úplně stejně. To spravíme pomocí virtual a override.

  1. Dej rodiči virtual int Utok(), který vrátí Sila
    💡 Poradit
    Slovo virtual znamená „potomek to smí přepsat":
    public virtual int Utok()
    {
    return Sila;
    }
    Rodič má normální tělo, takže zatím funguje všem.
  2. Vlkodlak v úplňku ať útočí dvojnásobně
    💡 Poradit
    Do třídy Vlkodlak přidej:
    public override int Utok()
    {
    return VUplnku ? Sila * 2 : Sila;
    }
    Stejné jméno, stejné parametry, stejný návratový typ — jinak CS0115.
  3. Přepiš Utok() i skřetovi a obrovi
    💡 Poradit
    Skřet má bonus za zbraň, obr počítá i brnění:
    return Sila + 2;
    return Sila + Brneni / 2;
    Každý potomek si sahá na své vlastní údaje — rodič o nich nic neví.
  4. Přidej virtual string Zvuk() a přepiš ho ve všech třech
    💡 Poradit
    Rodič vrátí něco neutrálního ("...něco zavrčí..."), potomci svoje:
    public override string Zvuk() { return "Gruaah!"; }
    Protože je rodičovská verze virtual (ne abstract), nemusíš přepsat všechny naráz.
  5. Přidej Karta() a u obra použij base.Karta()
    💡 Poradit
    base.Karta() zavolá rodičovskou verzi a ty k ní jen něco přilepíš:
    public override string Karta()
    {
    return base.Karta() + $"\n brnění: {Brneni}";
    }
    Nepiš this.Karta() — to by se zacyklilo donekonečna.
Úroveň Co má zvládnout
Minimum Body 1–2, vlkodlak útočí jinak než rodič
Rozšíření Body 3–4, všichni tři mají svůj Utok() i Zvuk()
Výzva Bod 5, base.Karta() a k tomu vlastní řádek u skřeta
cviceni/cviceni2_po_svem.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: stejné volání Utok() dává u každého druhu jiné číslo a base.Karta() ukáže, že potomek může rodiče doplnit, ne jen nahradit.

Kde se zaseknou:

  1. Zapomenutý virtual u rodiče. Překladač hlásí CS0506 („metoda není virtual"). Napište na tabuli dvojici: virtual = rodič dovolí, override = potomek využije. Bez prvního nefunguje druhé.
  2. Nesedící podpis. public override string Utok() místo intCS0508. Ať si podpisy porovnají znak po znaku.
  3. this.Karta() místo base.Karta() v kroku 5. Program se přeloží, ale zacyklí se (v prohlížeči to vypadá jako „stránka zamrzla"). Ukažte to schválně — je to nezapomenutelný zážitek — a hned vysvětlete proč.
  4. Vlkodlak bez úplňku. Dobrá otázka do třídy: „proč je útok najednou jen 9?" Odpověď: metoda se rozhoduje podle stavu objektu, ne podle třídy.

Rychlíkům zadejte Zvuk() u skřeta, který se mění podle životů (pod 15 HP kňourá, jinak řve).

Řešení 2 — jedna z možných variant

Toto je jedna z možných variant. Tvoje řešení se může lišit a to je v pořádku.

reseni/reseni2_po_svem.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 16Cvičení 3 — Slib, který se musí splnit (20 min)3. Cvičení

Zadání:

virtual je nabídka, abstract je příkaz. Dnes rodič přestane být sám o sobě použitelný.

  1. Udělej z Prisera abstraktní třídu a zkus ji vyrobit
    💡 Poradit
    Stačí jedno slovo před class:
    abstract class Prisera
    Řádek new Prisera(...) teď skončí chybou CS0144. To je správně — „příšera obecně" neexistuje, existuje jen skřet, vlkodlak nebo obr.
  2. Přidej public abstract int Odmena(); a hned ji doplň všem třem
    💡 Poradit
    Pozor: abstraktní metoda nemá tělo, jen středník — a od té chvíle ji musí mít každý potomek, jinak se nepřeloží nic. Přidej proto rodiči i všem třem potomkům naráz:
    public abstract int Odmena();
    a u potomka
    public override int Odmena() { return 12; }
  3. Udělej konstruktor rodiče protected
    💡 Poradit
    protected Prisera(string jmeno, int zivoty, int sila)
    Znamená to „smí volat jen potomek přes base(...)". Zvenku už se k němu nikdo nedostane.
  4. Předělej Zvuk() z virtual na abstract
    💡 Poradit
    V rodiči smaž tělo a nech jen:
    public abstract string Zvuk();
    Jestli má některý potomek Zvuk() ještě nepřepsaný, musíš mu ho ve stejném kroku dopsat. Jinak CS0534.
  5. Přidej čtvrtou nestvůru Prizrak
    💡 Poradit
    Překladač ti přesně řekne, co jí chybí — Odmena() i Zvuk(). Přidej obojí:
    class Prizrak : Prisera
    a v konstruktoru nezapomeň na : base(...).
Úroveň Co má zvládnout
Minimum Body 1–2, abstract třída a povinná Odmena()
Rozšíření Body 3–4, protected konstruktor a abstraktní Zvuk()
Výzva Bod 5 a k tomu pátý druh nestvůry na jeden pokus bez chyby
cviceni/cviceni3_slib.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: rodič, který se nedá vyrobit, a čtyři potomci, kteří všichni splnili oba sliby (Zvuk() a Odmena()).

Kde se zaseknou — a tohle je dnes to hlavní:

  1. Abstraktní metoda se musí přidat „jedním tahem". Jakmile napíšete public abstract int Odmena();, přestanou se překládat všichni potomci naráz (CS0534). Studenti si myslí, že něco rozbili. Řekněte předem: „teď uděláme jednu změnu na čtyřech místech a mezitím to bude červené." Tohle je nejčastější důvod paniky v celé hodině.
  2. new Prisera(...) v kroku 1. CS0144 je záměr, ne chyba studenta. Nechte je to schválně zkusit a chybu si přečíst nahlas.
  3. abstract metoda s tělemCS0500. Abstraktní = jen podpis a středník.
  4. Rozdíl virtual vs. abstract shrňte jednou větou: virtual = rodič něco umí a ty to smíš změnit; abstract = rodič to neumí a ty to musíš dodělat."

Rychlíkům zadejte abstraktní string Slabina() — a ať ji doplní všem čtyřem.

Řešení 3 — jedna z možných variant

Toto je jedna z možných variant. Tvoje řešení se může lišit a to je v pořádku.

reseni/reseni3_slib.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 17Cvičení 4 — Bestiář v jednom seznamu (20 min)3. Cvičení

Zadání:

Teď přijde odměna za všechnu tu práci: jeden cyklus obslouží všechny druhy nestvůr.

  1. Dej všechny nestvůry do List<Prisera>
    💡 Poradit
    Do seznamu typu rodič se vejde kterýkoli potomek:
    List<Prisera> bestiar = new List<Prisera>();
    bestiar.Add(new Skret("Skřet zvěd", 40, 6));
    bestiar.Add(new Vlkodlak("Vlkodlak", 60, 9));
  2. Projdi je jedním foreach a vypiš kartu každé
    💡 Poradit
    foreach (Prisera p in bestiar)
    {
    Console.WriteLine(p.Popis());
    }
    Ani jedno if, ani jeden dotaz na typ. C# si sám najde tu správnou verzi metody.
  3. Spočítej, kolik je v bestiáři vlkodlaků
    💡 Poradit
    Operátor is se ptá „jsi tenhle druh?":
    int vlku = bestiar.Count(p => p is Vlkodlak);
    nebo klasicky přes foreach a if (p is Vlkodlak) pocet++;
  4. Zjisti celkovou odměnu a nejnebezpečnější nestvůru
    💡 Poradit
    int zlato = bestiar.Sum(p => p.Odmena());
    Prisera nejhorsi = bestiar.OrderByDescending(p => p.Utok()).First();
    Metody z 13. hodiny fungují i na objektech.
  5. Přidej Drak a nesahej na cyklus
    💡 Poradit
    Napiš třídu Drak : Prisera, doplň povinné metody, přidej ji do seznamu — a výpis nech úplně beze změny. To je celý smysl dnešní hodiny.
Úroveň Co má zvládnout
Minimum Body 1–2, jeden foreach přes celý bestiář
Rozšíření Body 3–4, počty přes is a součty přes LINQ
Výzva Bod 5 — nový druh bez jediné změny ve výpisu
cviceni/cviceni4_bestiar.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: pět druhů nestvůr, jeden foreach, žádné if podle typu a celková odměna 359 zlatých.

Kde se zaseknou:

  1. List<Skret> místo List<Prisera>. Do seznamu potomka se rodič ani sourozenec nevejde (CS1503). Pravidlo: seznam se typuje na rodiče.
  2. Volání metody, kterou má jen potomek. p.Zbran na proměnné typu Prisera je CS1061 — rodič o ní neví. Tady se hodí ukázat if (p is Skret s) (pattern matching), ale jen rychlíkům; pro většinu stačí konstatování, že přes rodiče vidíme jen společné věci.
  3. Count bez using System.Linq. V našem prostředí je LINQ zapnutý, ale ve Visual Studio Code doma si ho musí přidat — zmiňte to.
  4. Krok 5 je pointa celé hodiny. Zdůrazněte, že se cyklus nezměnil. Ať si zkusí spočítat, kolik ifů by museli přidat bez dědičnosti.

Rychlíkům zadejte bestiar.Where(p => p.Odmena() >= 25).ToList() a výpis jen „hodnotných" cílů.

Řešení 4 — jedna z možných variant

Toto je jedna z možných variant. Tvoje řešení se může lišit a to je v pořádku.

reseni/reseni4_bestiar.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 18Cvičení 5 — CsQuest: výprava bestiářem (25 min)3. Cvičení

Zadání:

Finále hodiny: hrdina se vydá bestiářem a bude bojovat se vším, co potká. A ty nenapíšeš ani jedno if podle druhu nestvůry.

  1. Přidej třídu Hrdina a postav proti ní bestiář
    💡 Poradit
    Hrdina není potomek Prisera — je to samostatná třída se svým Jmeno, Zivoty, Sila a Zlato.
    Hodí se i vlastnost public bool Zije { get { return Zivoty > 0; } }.
  2. Napiš jeden souboj s jednou nestvůrou
    💡 Poradit
    Cyklus while, dokud oba žijí, s pojistkou na počet kol:
    while (hrdina.Zije && prisera.Zivoty > 0 && kolo <= 20)
    Uvnitř: hrdina ubere hrdina.Utok(), pak nestvůra ubere prisera.Utok().
  3. Přesuň souboj do lokální funkce a projdi celý bestiář
    💡 Poradit
    void Souboj(Hrdina h, Prisera p, Random r) napiš mezi příkazy (ne do třídy) a pak jen:
    foreach (Prisera p in bestiar) { Souboj(hrdina, p, kostka); }
    Parametr má typ rodiče — proto funguje na všechny druhy.
  4. Přičítej zlato a ukonči výpravu, když hrdina padne
    💡 Poradit
    Po vítězství hrdina.Zlato += p.Odmena();
    a v foreach:
    if (!hrdina.Zije) break;
    Nezapomeň, že break patří do cyklu s bestiářem, ne do soubojového while.
  5. Přidej závěrečnou bilanci a jednu zvláštní schopnost
    💡 Poradit
    Na konci vypiš zlato, přeživší nestvůry a hodnocení.
    Schopnost udělej přes override — třeba zuřivý vlkodlak:
    public override int Utok() { return Zivoty < 20 ? Sila * 2 : Sila + 3; }
    Žádné if v hlavním cyklu nepřibude.
Úroveň Co má zvládnout
Minimum Body 1–2, jeden funkční souboj
Rozšíření Body 3–4, výprava celým bestiářem a počítání zlata
Výzva Bod 5 a k tomu vlastní druh nestvůry se zvláštní schopností
cviceni/cviceni5_vyprava.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Pro učitele — na co si dát pozor

Očekávaný výsledek: hrdina projde bestiář, v hlavním cyklu není ani jedno if podle druhu nestvůry, a přesto se každá chová jinak. Se new Random(5) vychází výsledek všem stejně — což je fajn pro společnou kontrolu.

Kde se zaseknou:

  1. Hrdina : Prisera. Někdo hrdinu podědí od příšery, protože „mají to samé". Zeptejte se: „je hrdina příšera?" Dědičnost se řídí větou „je to", ne tím, že dvě třídy mají podobné vlastnosti.
  2. Chybějící pojistka v while. Bez kolo <= 20 umí souboj běžet věčně (například když útok vyjde 0). V prohlížeči to vypadá jako zaseknutá stránka. Trvejte na pojistce ve všech soubojových cyklech.
  3. Lokální funkce uvnitř třídy. Souboj patří mezi příkazy nahoře, ne dovnitř class Hrdina. Připomeňte pravidlo: příkazy nahoře, třídy dole.
  4. break ve špatném cyklu. Bod 4 přeruší výpravu, ne souboj. Ať si nakreslí dva obdélníky do sebe a ukážou, kde break skáče.
  5. Objekty se předávají odkazem. Uvnitř Souboj se opravdu mění ten samý hrdina, co je venku. Pro někoho je to překvapení — navažte na 15. hodinu.

Rychlíkům: Prizrak, který se po prvním zásahu jednou „uhne" (bool vlastnost + override Utok()), nebo lektvar mezi souboji.

Řešení 5 — jedna z možných variant

Toto je jedna z možných variant. Tvoje řešení se může lišit a to je v pořádku.

reseni/reseni5_vyprava.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.

4. Domácí úkol

Blok 19Domácí úkol na příští hodinu4. Domácí úkol

Postav ze svého CsQuestu bestiář na dědičnosti.

  • abstraktní (nebo aspoň společná) třída Prisera s údaji, které mají všichni,
  • alespoň tři různí potomci, každý s : base(...) konstruktorem,
  • alespoň dvě metody přepsané přes override (např. Utok() a Zvuk()),
  • jedno místo, kde použiješ base.Metoda() a k výsledku rodiče něco přidáš,
  • celý bestiář v List<Prisera> a jediný foreach, který ho vypíše.

Až to budeš mít, zkus přidat čtvrtý druh nestvůry. Pokud jsi nemusel sáhnout do výpisu, je to hotové správně.

Pro učitele

Kontrolní otázka při odevzdání zní vždycky stejně: „kolik ifů máš v hlavním cyklu?" Správná odpověď je nula. Když jich tam je pět, student napsal dědičnost, ale používá ji jako switch podle typu.

Co uznat jako splněné: i verze bez abstract (jen virtual s výchozím tělem) je v pořádku. Pointa úkolu je jeden cyklus nad různými typy, ne formální dokonalost.

Čekejte jednu typickou chybu: potomek s vlastní kopií Jmeno a Zivoty. Program běží, ale rodičovské metody vidí prázdné hodnoty. Je to skvělá příležitost si znovu nakreslit, že potomek má jen jednu sadu vlastností — tu zděděnou.

Zobrazit ukázkové řešení domácího úkolu

Toto je jedna z možných variant, jak mohl úkol dopadnout. Tvoje řešení se může lišit a to je v pořádku.

domaci/domaci_ukol16.cs
Tady se objeví výstupKlikni na Spustit. Kód vlevo můžeš měnit a spouštět opakovaně klávesami Ctrl+Enter.
Kód vlevo jde upravovat. Tab odsazuje o čtyři mezery.
Blok 20Oficiální dokumentace4. Domácí úkol

Oficiální dokumentace je místo, kam se programátoři chodí podívat, když si chtějí něco ověřit nebo zjistit víc detailů. Je většinou v angličtině, ale to nevadí — zkus se orientovat podle názvu příkazu a podle ukázek kódu.

Co jsme použili Oficiální dokumentace Co tam najdeš
dědičnost : Rodic Dědičnost jak potomek přebírá vlastnosti a metody
virtual Klíčové slovo virtual kdy rodič dovolí přepsání
override Klíčové slovo override pravidla pro přepsání metody
abstract Klíčové slovo abstract třída i metoda bez těla
base Klíčové slovo base volání rodičovské verze a : base(...)
polymorfismus Polymorfismus proč jeden cyklus stačí na všechny druhy
protected Modifikátory přístupu co vidí jen potomek
is Operátor is dotaz na typ objektu
abstraktní a zapečetěné třídy Abstract a sealed souhrn obojího na jednom místě
List<T> List<T> seznam objektů a jeho metody
LINQ (Sum, Count, Where) Přehled LINQ dotazy nad seznamy objektů
Jak s dokumentací pracovat na hodině

Stránka o polymorfismu má hned v úvodu ukázku se tvary (Shape, Circle, Rectangle). Je to přesně náš bestiář, jen bez příšer — dobrý důkaz, že to, co jsme dnes dělali, není školní vymyšlenost, ale standardní postup.

Na stránce o virtual si všimněte tabulky rozdílů virtual / abstract / override / new. Slovo new u metod zatím neučte — jen řekněte, že existuje a že se mu vyhýbáme.

5. Umíme informatiku

Blok 21Navazující cvičení na Umíme informatiku5. Umíme informatiku

Pro rychlejší studenty, kteří dokončí cvičení dřív, nebo na posledních 10–15 minut.

Cvičení Odkaz Proč se hodí
Programování — základy umimeinformatiku.cz čtení cizího kódu a předvídání výstupu
Funkce a procedury umimeinformatiku.cz která verze metody se vlastně zavolá
Seznamy a pole umimeinformatiku.cz průchod seznamem, dnes seznamem objektů

Nejlepší bonus dneška je ale tužka a papír: ať studenti nakreslí strom dědičnosti pro něco úplně jiného než pro hru — dopravní prostředky, zvířata v ZOO, hudební nástroje. U každé úrovně musí platit věta „potomek je rodič". Kdo to zvládne bez chyby, pochopil dnešní hodinu líp než z jakéhokoli kódu.

Jak to zařadit

Osvědčený trik na závěr: napište na tabuli špatný strom (Auto : Kolo, protože „obojí má kola") a nechte třídu, ať ho vyvrátí. Věta „je to" je jediné pravidlo, které si z dědičnosti musí odnést všichni.