Section outline

  • Vítejte v kurzu PGM2P

    Vítejte v kurzu Sada rozšiřujících úloh v Blazor pro předmět PGM2P. Tento kurz slouží jako doplňkový vzdělávací materiál, který navazuje na semináře PGM2P na platformě .NET.

    Jeho cílem je pokrýt témata z oblasti objektově orientovaného programování, na která v běžné výuce nezbyl dostatek prostoru, a objasnit problematiky, které se ukázaly jako obtížnější na pochopení.

    Metodika a organizace

    Kurz je koncipován s důrazem na efektivitu – veškeré praktické úlohy jsou navrženy tak, aby byly jednoduché, srozumitelné a časově nenáročné. Můžete si tak rychle a bez stresu ověřit, zda dané látce rozumíte.


    Autor: Luboš Dokulil

    • Co to je dědičnost?

      Dědičnost je jeden ze základních principů OOP [1]. Umožňuje vytvořit novou třídu, která přebírá vlastnosti a metody jiné třídy (tzv. rodičovské třídy). Hlavní myšlenkou dědičnosti je znovupoužití kódu – v souladu s didaktickými principy programování nemusíme psát stejné vlastnosti a metody v různých třídách [2]. Místo toho je stačí mít definované jednou v základní třídě a potomci je automaticky zdědí [1].

      V jazyce C# a na platformě .NET [3] se dědičnost zapisuje velmi stručně pomocí dvojtečky : hned za název nové třídy. Zápis public class Potomek : Rodic říká kompilátoru: "Vytvoř novou třídu Potomek a zkopíruj do ní všechno, co umí třída Rodič."

      Klíčové pojmy

      Abstraktní třída – třída, ze které nelze vytvořit instanci [1]. Slouží pouze jako "šablona" a ostatní třídy z ní mohou dědit. Zapisujeme způsobem public abstract class NazevTridy.

      Abstraktní metoda – metoda, která nemá žádné tělo. Slouží jako povinný předpis pro potomky, kteří ji musejí přepsat a definovat si vlastní chování [1]. Zapisujeme způsobem public abstract typ NazevMetody(); Pokud ve třídě máme alespoň jednu abstraktní metodu, musí být celá třída definována také jako abstraktní [3].

      Virtuální metoda – metoda, která má definované tělo (kód). Slouží jako výchozí chování pro potomky, kteří ji mohou (ale nemusejí) přepsat, pokud chtějí změnit její funkčnost [1]. Zapisujeme způsobem public virtual typ NazevMetody() { ... }.

      Override – klíčové slovo, které se používá v třídě potomka. Slouží k přepsání původní metody, která byla v rodičovské třídě označena jako virtual nebo abstract [1]. Zapisujeme způsobem public override typ NazevMetody() { ... }. Tímto explicitně říkáme, že chceme změnit zděděné chování na naše vlastní [3].

    • V Zadání úkolu najdete jednoduchý úkol, který objasňuje základní principy dědičnosti v OOP.

    • Zdroje použité v textu

    • Co to je polymorfismus?

      Polymorfismus je jeden ze základních principů OOP [1]. Umožňuje přistupovat k objektům různých tříd jednotným způsobem skrze jejich společnou rodičovskou třídu. Hlavní myšlenkou polymorfismu je, že objekty mohou mít mnoho forem, ale navenek se tváří stejně – v souladu s principy efektivního návrhu kódu nemusíme v programu zjišťovat konkrétní typ objektu, abychom provedli určitou akci [2]. Místo toho zavoláme metodu definovanou v rodičovské třídě a systém automaticky zajistí spuštění té správné verze metody, kterou si definoval konkrétní potomek [1].

      V jazyce C# a na platformě .NET [3] se polymorfismus nejčastěji projevuje přiřazením instance potomka do proměnné typu rodiče. Zápis Rodic obj = new Potomek(); říká kompilátoru: "Zacházej s tímto objektem jako s obecným rodičem, ale pokud zavoláme přepsanou metodu, použij při běhu programu skutečné chování potomka" [3].

      Přetěžování metod (Method Overloading)

      Další formou polymorfismu je přetěžování metod [1]. To nám umožňuje definovat v jedné třídě více metod se stejným názvem, které se však liší počtem nebo typem parametrů. Funguje to na principu rozpoznání parametrů již při kompilaci [3]. Typickým příkladem může být metoda Hledat. Můžeme mít jednu verzi Hledat(int id) pro vyhledávání podle čísla a druhou verzi Hledat(string jmeno) pro vyhledávání podle textu. Ačkoliv mají obě metody stejný název, kompilátor podle toho, zda do závorky vložíme číslo nebo text, přesně ví, kterou variantu má použít [1].

    • V zadání najdete jednoduchý úkol, který objasňuje polymorfismus a přetěžování metod.

    • Zdroje použité v textu

    • Co jsou to kolekce objektů?

      Kolekce objektů (List<>) jsou specializované datové struktury, které slouží k seskupování a správě většího množství souvisejících objektů [1]. Umožňují efektivně ukládat data, ke kterým potřebujeme přistupovat hromadně, a v souladu se správnými programátorskými praktikami nabízejí nástroje pro jejich třídění či prohledávání [2]. Hlavní myšlenkou kolekcí je flexibilita a dynamická velikost – na rozdíl od klasických polí (arrays) se velikost kolekce automaticky přizpůsobuje, můžeme do ní za běhu programu libovolně přidávat nebo odebírat položky a následně je snadno vykreslit v uživatelském rozhraní [1].

      V jazyce C# a na platformě .NET [3] se kolekce nejčastěji definují pomocí generické třídy List<T>, kde T nahrazujeme konkrétním typem objektu. Zápis List<Uzivatel> seznam = new List<Uzivatel>(); říká kompilátoru: "Vytvoř nový dynamický seznam, do kterého budeme moci vkládat pouze objekty typu Uživatel" [3].

      Základní metody a vlastnosti

      Metoda Add() – metoda, která přidává nový objekt na konec kolekce [1]. Pokud seznam ještě neexistoval (nebo přesáhl svou původní kapacitu), automaticky se zvětší. Zapisujeme způsobem nazevSeznamu.Add(novyObjekt);.

      Metoda Remove() – metoda, která vyhledá konkrétní objekt v seznamu a odstraní ho [3]. Ostatní prvky se po odstranění posunou, aby v seznamu nevznikla mezera. Zapisujeme způsobem nazevSeznamu.Remove(objekt);.

      Vlastnost Count – vlastnost, která vrací aktuální počet položek uložených v kolekci [1]. Slouží k rychlé kontrole, zda je seznam prázdný nebo kolik obsahuje dat. Zapisujeme způsobem nazevSeznamu.Count.

      Metoda Clear() – metoda, která okamžitě odstraní všechny položky ze seznamu a velikost kolekce vrátí na nulu [3]. Zapisujeme způsobem nazevSeznamu.Clear();.

      Metoda Where() – vyfiltruje prvky z kolekce na základě zadané podmínky a vrátí jen ty, které ji splňují. Zapisujeme například kolekce.Where(x => x.Cena > 1000);.

      Metody OrderBy() a OrderByDescending() – slouží k řazení prvků. OrderBy() řadí vzestupně, zatímco OrderByDescending() sestupně (od největšího).

      Metoda Select() – takzvaná projekce. Umožňuje z celého složitého objektu "vytáhnout" pouze konkrétní vlastnost (např. pouze název) a vytvořit z ní novou, zjednodušenou kolekci. Zapisujeme kolekce.Select(x => x.Nazev);.

      Metoda GroupBy() – rozdělí velkou kolekci do několika menších podskupin na základě společného klíče (např. produkty podle kategorie). Zapisujeme například kolekce.GroupBy(x => x.Kategorie);.

      Metoda FirstOrDefault() – najde a vrátí první objekt, který splňuje danou podmínku (např. shodu ID). Pokud ho nenajde, vrátí hodnotu null, čímž zabrání pádu aplikace. Zapisujeme například kolekce.FirstOrDefault(x => x.Id == 3);.

      Metoda Any() – zkontroluje seznam a vrátí logickou hodnotu true, pokud alespoň jeden prvek splňuje zadanou podmínku (jinak false). Zapisujeme například kolekce.Any(x => x.Cena > 10000);.

      Agregační metody (Sum, Average, Min, Max) – slouží k rychlým matematickým výpočtům nad kolekcí. Sum() vypočítá celkový součet, Average() průměr, Min() nejmenší a Max() nejvyšší hodnotu specifikované vlastnosti.

      Metoda ToList() – většina LINQ příkazů vrací specifický datový typ. Přidáním této metody na úplný konec dotazu se výsledek převede zpět na klasický seznam, se kterým už bezpečně umíme pracovat. Zapisujeme .ToList();.

    • V Zadání úkolu najdete jednoduchý úkol, který objasňuje principy práce s kolekcemi objektů.

    • V Zadání úkolu najdete jednoduchý úkol, který objasňuje příkazy jako Where, Select, GroupBy apod. 

    • Kolekce objektů obsahující kolekce objektů

      Kolekce objektů obsahující další kolekce (často nazývané vnořené kolekce) jsou strukturovaná data, která reprezentují hierarchické vztahy mezi objekty [1]. Umožňují modelovat složitější vazby typu "rodič-potomek", kde jeden nadřazený prvek obsahuje celou skupinu podřízených prvků. Hlavní myšlenkou je organizace dat do víceúrovňové nebo stromové struktury – v souladu s didaktickými principy logické organizace informací [2] namísto jediného plochého seznamu vytváříme systém, který logicky seskupuje data (například seznam kategorií, kde každá kategorie má svůj vlastní seznam produktů) a umožňuje je vykreslit například jako rozbalovací menu [1].

      V jazyce C# a na platformě .NET [3] se tato struktura realizuje definováním vlastnosti typu List<T> uvnitř jiné třídy. Zápis public List<Produkt> Produkty { get; set; } = new List<Produkt>(); uvnitř třídy Kategorie říká kompilátoru: "Každý jeden objekt kategorie bude mít svůj vlastní seznam na produkty, který musíme ihned vytvořit" [3].

      Základní pojmy a konstrukce

      Vnořená vlastnost – vlastnost třídy, která je definována jako seznam jiných objektů. Slouží k fyzickému propojení rodiče s jeho dětmi [1]. Zapisujeme způsobem public List<TypDitete> NazevSeznamu { get; set; }.

      Vnořený cyklus – programovací konstrukce, kdy je jeden cyklus umístěn uvnitř druhého. Slouží k vykreslení hierarchie, kdy vnější cyklus prochází kategorie a vnitřní cyklus vypisuje produkty dané kategorie [1]. Zapisujeme vložením příkazu foreach do těla jiného foreach.

    • V zadání úkolu najdete jednoduchý úkol, který objasňuje principy kolekcí objektů vnořených do kolekcí objektů.

    • Zdroje použité v textu

    • Co to je asynchronní programování?

      Asynchronní programování umožňuje spouštět časově náročné úkoly, aniž by došlo k zaseknutí celé aplikace [1]. Hlavní myšlenkou je neblokování uživatelského rozhraní – v souladu s principy návrhu responzivních aplikací [2] program nemusí nečinně čekat na dokončení operace a "zamrznout". Místo toho může uživatel s aplikací dále pracovat a kód bude pokračovat ve vykonávání až ve chvíli, kdy je úkol na pozadí úspěšně dokončen [3].

      V jazyce C# a na platformě .NET [3] se asynchronní programování realizuje pomocí dvojice klíčových slov async a await. Zápis public async Task StahujData() říká kompilátoru: "Tato metoda bude trvat dlouho, ale chci, aby se prováděla na pozadí a neblokovala zbytek aplikace" [3].

      Klíčová slova a třídy

      Klíčové slovo async – modifikátor, který se píše do hlavičky metody. Označuje, že daná metoda bude obsahovat asynchronní operace [1]. Zapisujeme způsobem public async Task NazevMetody() { ... }.

      Klíčové slovo await – příkaz, který se používá uvnitř asynchronní metody před voláním dlouhotrvající operace. Slouží k tomu, aby program na tomto řádku "počkal" na výsledek, ale zároveň uvolnil procesor pro jinou práci (např. vykreslování UI) [3]. Zapisujeme způsobem await DlouhaOperace();.

      Třída Task – speciální návratový typ pro asynchronní metody, který reprezentuje "úlohu", která se právě vykonává [1]. Nahrazuje klasický void (pro metody bez návratové hodnoty) nebo balí výsledek do "obálky" (např. Task<int>) [3]. Zapisujeme způsobem public async Task<string> NactiText().

    • Jednoduchý úkol, který objasňuje základní principy asynchronního programování

    • Jednoduchý úkol, který ukazuje rozdíl mezi asynchronním a synchronním programováním

    • Zdroje použité v textu

    • Co to je Drag & Drop?

      Drag and Drop (táhni a pusť) je interaktivní technika ovládání uživatelského rozhraní. Umožňuje uživateli uchopit vizuální prvek myší, přetáhnout ho a upustit na jiné cílové místo [3]. Hlavní myšlenkou je intuitivní manipulace s daty – v souladu s moderními principy tvorby uživatelského zážitku [2] namísto klikání na tlačítka nebo vyplňování formulářů uživatel přímo na obrazovce mění uspořádání objektů. Aplikace na základě této události automaticky provede příslušnou logiku v kódu (například přesun objektu z jedné kolekce List<> do druhé) [1].

      V moderních webových aplikacích propojených s jazykem C# (jako je framework Blazor) [3] se Drag & Drop realizuje pomocí sady speciálních HTML5 událostí. Zápis <div @ondrop="ZpracujUpusteni"> říká kompilátoru: "Jakmile nad tímto prvkem uživatel uvolní tlačítko myši s taženým objektem, spusť metodu ZpracujUpusteni v C# kódu."

      Základní atributy a události

      Atribut draggable – HTML atribut, který musíme nastavit na hodnotu true, aby webový prohlížeč vůbec dovolil vizuální prvek uchopit myší [3]. Zapisujeme způsobem <div draggable="true">.

      Událost ondragstart – událost, která se spustí v okamžiku, kdy uživatel začne prvek táhnout. Slouží k uložení informace o tom, co konkrétně přesouváme (např. si do dočasné proměnné uložíme ID taženého objektu) [1]. Zapisujeme způsobem @ondragstart="MetodaZacatekTazeni".

      Událost ondragover – událost, která se volá neustále, zatímco se tažený prvek vznáší nad cílovou oblastí. Ve výchozím nastavení prohlížeče blokují upouštění prvků, proto musíme tuto událost zachytit a výchozí chování zrušit, aby se z oblasti stala platná cílová zóna [3]. Zapisujeme způsobem @ondragover:preventDefault="true".

      Událost ondrop – událost, která nastane v momentě uvolnění tlačítka myši nad povolenou cílovou zónou. Slouží ke spuštění finální programové logiky – například k vyhledání objektu v původním seznamu, jeho odstranění metodou Remove() a přidání do nového seznamu metodou Add() [1]. Zapisujeme způsobem @ondrop="MetodaUpusteni".

    • V zadání najdete jednoduchý úkol, který objasňuje principy Drag and Drop.

    • Zdroje použité v textu

    • Co to je JSON a jak se s ním pracuje?

      JSON (JavaScript Object Notation) je textový formát pro ukládání a výměnu dat [4]. Umožňuje poskytnout standardizovaný způsob, jakým si mohou různé systémy vyměňovat informace ve formě, která je snadno čitelná pro lidi i stroje [4]. Hlavní myšlenkou JSONu je jednoduchá a efektivní reprezentace strukturovaných dat, díky čemuž je tento formát zcela nezávislý na konkrétním programovacím jazyce [4].

      V jazyce C# a na platformě .NET [3] se s JSON daty pracuje pomocí systémové třídy HttpClient a metod z oboru názvů System.Net.Http.Json. Zápis await Http.GetFromJsonAsync<TypObjektu>("cesta/k/souboru.json") říká kompilátoru: "Pošli HTTP požadavek na danou adresu, stáhni textový JSON a rovnou ho přelož do předpřipravené C# struktury." [3].

      Technické pojmy a nástroje

      JSON Objekt – základní datová struktura v JSON souboru, která reprezentuje jednu konkrétní entitu [4]. Slouží k uložení sady vlastností pomocí formátu párů klíč-hodnota. Zapisujeme do složených závorek způsobem { "klic": "hodnota", "cislo": 123 } [4].

      JSON Pole (Array) – uspořádaný seznam hodnot nebo objektů [4]. Slouží k uložení kolekce více prvků, například celého seznamu načítaných položek. Zapisujeme do hranatých závorek způsobem [ { ... }, { ... } ] [4].

      C# Model (Třída) – datová struktura v jazyce C#, do které se textový JSON v paměti aplikace překládá [1]. Slouží jako "překladová šablona", která zachytí strukturu objektu, aby s ním bylo možné v objektově orientovaném kódu pracovat [1]. Zapisujeme jako třídu, např. public class Model { public string Vlastnost { get; set; } }.

      HttpClient – systémová třída na platformě .NET, bez které by aplikace nemohla komunikovat s okolím [3]. Slouží k odesílání HTTP požadavků a přijímání dat z webových serverů [3]. V Blazor komponentě se obvykle připojuje zápisem @inject HttpClient Http.

      GetFromJsonAsync – asynchronní metoda, která rozšiřuje možnosti platformy .NET pro práci se sítí [3]. Slouží k odeslání požadavku a automatickému převodu přijatého textového JSONu přímo do podoby C# objektu [3]. Zapisujeme způsobem var data = await Http.GetFromJsonAsync<TypObjektu[]>("data.json");.

    • Zdroje použité v textu

    • Co je to API?

      API (Application Programming Interface) umožňuje dvěma softwarovým komponentám vzájemně komunikovat pomocí sady definic a protokolů [4]. V jazyce C# a na platformě .NET [3] se s daty z API pracuje pomocí systémové třídy HttpClient a metod z oboru názvů System.Net.Http.Json [3].

      Zápis await Http.GetFromJsonAsync<TypObjektu>("https://api.vydavatel.cz/data") říká kompilátoru: "Pošli HTTP požadavek na danou externí adresu, stáhni textový JSON a rovnou ho přelož do předpřipravené C# struktury." [3].

      Technické pojmy a nástroje

      HTTP metoda GET – typ požadavku, který se používá k načtení (nebo získání) dat z daného prostředku [5]. Slouží výhradně ke čtení informací, přičemž v těle tohoto požadavku se neodesílají žádná data [5].

      C# Model (Třída) – datová struktura v jazyce C#, do které se textový JSON stažený z API v paměti aplikace překládá [1]. Slouží jako "překladová šablona", jejíž vlastnosti musí přesně odpovídat klíčům vráceným ze serveru, aby s nimi bylo možné pracovat [1]. Zapisujeme jako třídu, např. public class Model { public string Vlastnost { get; set; } }.

      HttpClient – systémová třída na platformě .NET, která odesílá HTTP požadavky a přijímá HTTP odpovědi z prostředku identifikovaného pomocí URI [3]. Slouží jako relace ke komunikaci s webovými servery [3]. V Blazor komponentě se obvykle připojuje zápisem @inject HttpClient Http.

      GetFromJsonAsync – asynchronní rozšiřující metoda, která se používá k automatické deserializaci (převodu) GET požadavků do silně typovaného C# objektu [3]. Slouží k rychlému stažení a zpracování dat jedním příkazem [3]. Zapisujeme způsobem var data = await Http.GetFromJsonAsync<TypObjektu>("https://adresa-api...");.

    • Zdroje použité v textu

    • Co je to validace dat?

      Validace dat je proces kontroly uživatelského vstupu před jeho uložením nebo dalším zpracováním. Pochopení a správné využití těchto mechanismů představuje jeden z klíčových principů při výuce tvorby spolehlivého softwaru [2]. Slouží k tomu, abychom zabránili odeslání nekompletních, nesprávných nebo nesmyslných údajů do systému. V objektově orientovaném jazyce C# [1] a v rámci platformy .NET [3] se k tomuto účelu nejčastěji využívá jmenný prostor System.ComponentModel.DataAnnotations, který umožňuje definovat pravidla přímo v datovém modelu pomocí takzvaných atributů [4].

      Zápis [Required] těsně nad vlastností třídy například říká kompilátoru: "Tato vlastnost nesmí zůstat prázdná. Pokud se tak stane, nedovol uložení a upozorni uživatele." [4]

      Jak se pravidla zapisují (Příklady)

      Pravidla se píšou do hranatých závorek přímo nad konkrétní vlastnost objektu [1]. Každé pravidlo by mělo obsahovat parametr ErrorMessage, kterým definujete vlastní srozumitelnou chybovou hlášku. Zde jsou příklady pro model uživatele [4]:

      • Povinné pole: Zápis [Required(ErrorMessage = "Jméno je povinné.")] nad vlastností public string Jmeno zajistí, že uživatel nesmí toto pole ve formuláři vynechat [4].
      • Číselný rozsah: Zápis [Range(18, 120, ErrorMessage = "Musíte být plnoletí.")] nad vlastností public int Vek automaticky zkontroluje, zda zadané číslo spadá do povoleného intervalu [4].
      • Správný formát e-mailu: Zápis [EmailAddress(ErrorMessage = "Chybný formát.")] nad vlastností public string Email sám na pozadí ověří, zda zadaný text obsahuje zavináč (@) a má logickou strukturu platné e-mailové adresy [4].

      Klíčové pojmy a komponenty

      Základní atributy (např. [Required]) – vestavěná systémová pravidla na platformě .NET [3]. Přidávají se do definice datového modelu nad ty vlastnosti, u kterých chceme vynutit určitý formát nebo povinnost vyplnění [4].

      Vlastní validační pravidla – pokud vestavěná pravidla nestačí, lze naprogramovat vlastní. Tvoří se na principu dědičnosti jako nová třída [1], která dědí ze systémové třídy ValidationAttribute. Uvnitř se přepíše metoda IsValid, která vyhodnotí data a vrátí buď úspěch, nebo zprávu o chybě [4].

      <EditForm> – Blazor komponenta nahrazující klasický HTML formulář. Napojuje se na konkrétní datový objekt a díky události OnValidSubmit zajistí, že se metoda pro uložení dat spustí pouze tehdy, pokud všechna pole prošla kontrolou [5].

      <DataAnnotationsValidator /> – nezbytná značka vkládaná dovnitř formuláře. Aktivuje proces validace a říká formuláři, aby začal sledovat a vymáhat pravidla definovaná v datovém modelu [5].

      <ValidationMessage> – komponenta pro vizuální zpětnou vazbu. Pokud uživatel poruší některé z pravidel, automaticky na daném místě vykreslí příslušnou chybovou hlášku [5].

    • Zdroje použité v textu

    • Úvod ke komplexním úlohám

      V této části se nacházejí komplexní úlohy, které spojují více témat z kurzu dohromady.