1 Příklady pro druhý semestr PGL Jiří Fišer 1.1 Formátovač 1.1.1 Vytvoření aplikace pro snadné vytváření jednoduše strukturovaných dokumentů. Strukturované dokumenty musí podporovat sekce (=kapitoly) a odstavce s případnými nadpisy resp. jinými dekoracemi. Dokumenty lze serializovat do HTML, Wiki-textu resp. jiného textového formátu (např. LaTeX). Na příkladu lze ukázat různé návrhové vzory především Bridge resp. Abstract factory. Východiskem je jednoduché sdílené rozhraní, které by měly implementovat textové objekty, jež lze ukládat vypisovat do objektu třídy TextWriteru: i n t e r f a c e I W r i t a b l e T e x t { void Write ( T e x t W r i t e r w r i t e r ) ; Nejjednodušší implementací je objekt prostého textu, jenž vypisuje neformátovaný řetězec, jenž je v něm uložen. c l a s s Text : I W r i t a b l e T e x t { p u b l i c s t r i n g C o n t e n t { g e t ; p r i v a t e s e t ; p u b l i c Text ( s t r i n g c o n t e n t ) { C o n t e n t = c o n t e n t ; p u b l i c Write ( T e x t W r i t e r w r i t e r ) { w r i t e r. Write ( t h i s. C o n t e n t ) ; Jen o málo složitější je objekt vypisující text tučně. Tučný text nelze vypisovat na konzoli přímo (resp. nelze to udělat přenositelně). Proto zvolíme výpis ve strukturovaném formátu např. HTML. Nově vytvořená třída BoldText se bude lišit jen v těle metody Write. w r i t e r. Write ( <b> + t h i s. C o n t e n t + </b> ) ; Pro vytváření složitěji strukturovaných testů však takto jednoduché třídy nestačí, nebot se skládají i z složených struktur např. odstavců. Odstavec je v našem zjednodušeném modelu tvořen posloupností jednoduchých resp. tučných textů (přidání dalších typů inline textu je snadné, ale ještě počkáme). c l a s s P a r a g r a p h : I W r i t a b l e T e x t { p r i v a t e L i s t <I W r i t a b l e T e x t > p a r t s ; p u b l i c P a r a g r a p h ( params I W r i t a b l e T e x t [ ] p ) { p a r t s = new L i s t <I W r i t a b l e T e x t >(p ) ; p u b l i c AppendText ( I W r i t a b l e T e x t t ) {.... p u b l i c AppendText ( s t r i n g t ) { AppendText ( new Text ( t ) ) ; p u b l i c void Write ( T e x t W r i t e r w r i t e r ) { w r i t e r. Write ( ) ; f o r e a c h ( I W r i t a b l e T e x t t e x t i n p a r t s ) t e x t. Write ( w r i t e r ) ; w r i t e r. W r i t e L i n e ( ) ; 1
Podobně je možno vytvořit třídu Section, která representuje kapitolu a od odstavce se liší tím, že má nadpis (tvořený holým textem). Při serializaci je nadpis opatřen tagem H1. HTML však není jedinou možností representace strukturovaného textu. Např. na Wikipedii se používá jednodušší textový formát, kde je např. tučnost vyjádřena pomocí apostrofů tučný text. Detailnější informace lze získat na h t t p : / / cs. w i k i p e d i a. org / w i k i / W i k i p e d i e : Pr%C5%AFvodce %28form%C3%A1tov%C3%A1n%C3%AD%29 h t t p : / / cs. w i k i p e d i a. org / w i k i / W i k i p e d i e : Průvodce ( f o r m á t o v á n í ) Náš současný model však s alternativním formátováním nepočítá. Jednou z možností je vytvoření nových tříd pro nové formátování (typu WikiBoldText, WikiParagraph). To však není rozumné řešení, nebot umožňuje vytvářet dokumenty se smíšeným formátováním (např. v HTML odstavci by byl tučný wiki-text). Navíc strukturovaných textových formátů je téměř neomezené množství. Hlavním problémem je však nemožnost měnit výstupní formát u již vytvořených objektů tj. nemožnost jednoduchého vypsání jediného dokumentu v různých formátech. Řešením je oddělení abstrakce a implementace tj. použití návrhového vzoru bridge. Nejjednodušší možností je přidání dodatečného parametru do metody IWritableText.Write, což může být řetězec, ale lepší je formátovací objekt, tj. objekt implementující (naše nové) rozhraní ITextFormater. i n t e r f a c e I T e x t F o r m a t t e r { s t r i n g F o r m a t I n l i n e T e x t ( s t r i n g t e x t, FontShape shape ) ; s t r i n g F o r m a t P a r a g r a p h ( s t r i n g c o n t e n t s ) ; s t r i n g FormatHeader ( s t r i n g t e x t, i n t l e v e l ) ; kde FontShape je výčtový typ s hodnotami například Italics a Bold. Pro testování se vytvoří dvě implementace tohoto rozhraní HtmlTextFormatter a WikiText- Formatter a abstraktní továrna, která poskytuje odpovídající třídu podle označení formátu resp. zvolí implicitní formát (zadán při vytvoření továrny). Tento formát je použit v případě, že požadovaný formát není vytvořen. c l a s s T e x t F o r m a t t e r F a c t o r y { p r i v a t e s t a t i c D i c t i o n a r y <s t r i n g, I T e x t F o r m a t t e r > formatmapper = new... p u b l i c s t a t i c T e x t F o r m a t t e r F a c t o r y ( ) { formatmapper. Add ( html, new H t m l T e x t F o r m a t t e r ( ) ) ;........... p u b l i c T e x t F o r m a t t e r F a c t o r y ( I T e x t F o r m a t t e r d e f a u l t F o r m a t ) { p u b l i c I T e x t F o r m a t t e r C r e a t e ( s t r i n g f o r m a t ) {.... Samozřejmě je nutno ještě přepsat implementace metody IWritableText.Write, např. takto (pro třídu BoldText) p u b l i c void Write ( T e x t W r i t e r w r i t e r, I T e x t F o r m a t t e r f o r m a t t e r ) { w r i t e r. Write ( f o r m a t t e r. F o r m a t I n l i n e T e x t ( t h i s. Content, FontShape. Bold ) ) ; 2
1.2 Sklad 1.2.1 Vytvoření modelu prodejního skladu, do něhož lze ukládat objekty různých tříd. Třídy by měly podporovat rozhraní i ISkladovatelne a rozhraní IProdatelne. Součástí modelu by měly být i implementace tříd konkrétních skladovatelných výrobků. Základní rozhraní skladovatelných výrobků (zjednodušeno): i n t e r f a c e I S k l a d o v a t e l n e { Sklad Sklad { g e t ; void VlozDoSkladu ( Sklad s ) ; Skladovatelný výrobek musí nabízet jednoznačný identifikátor. Příslušnou vlastnost je však vhodnější vyčlenit do speciálního rozhraní. i n t e r f a c e I I d e n t i f i k o v a t e l n e { s t r i n g U n i k a t n i I d { g e t ; i n t e r f a c e I S k l a d o v a t e l n e : I I d e n t i f i k o v a t e l n e... Produkty ve skladu by měly být prodávatelné tj. měly by být opatřeny cenou a lze testovat zda nepřekročily maximální dobu trvanlivosti (je otázkou, zda by metoda neměla být spíše v rozhraní ISkladovatelne, ale trvanlivost primárně ovlivňuje prodej a cenu nikoliv skladování). Cena je primárně uvedena i n t e r f a c e I P r o d a t e l n e { d e c i m a l CenaKus { g e t ; d e c i m a l PocetKusu { g e t ; d e c i m a l CenaCelkova { g e t ; bool J e P o u z i t e l n e ( DateTime datum ) ; Jaký je vztah mezi navrženými rozhraními: je prodatelná věc nutně i jednoznačně identifikovatelná (prodejní model, vrácení, záruka), je skladovatelná věc prodávatelnou (asi ano, sklad je prodejní). Výsledná hierarchie tak mít tvar: I I d e n t i f i k o v a t e l n e > I P r o d a t e l n e > I S k l a d o v a t e l n e ; nebo méně lineární: I I d e n t i f i k o v a t e l n e > I S k l a d o v a t e l n e ; I P r o d a t e l n e > I S k l a d o v a t e l n e ; Jako příklad třídy implementující rozhraní ISkladovatelne lze uvést například třídu Potravina. c l a s s P o t r a v i n a : I S k l a d o v a t e l n é { s t a t i c i n t i d C o u n t e r = 1 ; p u b l i c P o t r a v i n a ( s t r i n g druh, DateTime spotrebado, d e c i m a l cenacelkem, i n t pocetkusu ) { U n i k a t n i I d = s t r i n g. Format ( {0 {1:0000, druh, i d C o u n t e r ) ; i d C o u n t e r ++; t h i s. Druh = druh ; t h i s. s p o t r e b a = s p o t r e b a D o ; 3
p u b l i c P o t r a v i n a ( s t r i n g druh, DateTime spotrebado, d e c i m a l cenacelkem ) : t h i s ( druh, spotrebado, cenacelkem, 1) { \\ pomocný k o n s t r u k t o r pro j e d n o k u s o v é z b o ž í \\ v C# 4. 0 l z e p o u ž í t i m p l i c i t n í p a r a m e t r y p u b l i c U n i k a t n i I d { g e t ; p r i v a t e s e t ;... p u b l i c d e c i m a l CenaKus { g e t { return CenaCelkova / PocetKusu ;... Důležité je rozlišení mezi třídou (všechny potraviny), druhem potraviny (podmnožina třídy), balením (= to co je skladováno = objekt) a jednotlivým kusem (není identifikovatelný a jediným jeho nepřímým atributem je cena (za kus). Identifikace se musí vztahovat k balení tj. objektu nikoliv druhu. Presentované řešení pro rozlišení balení využívá čítače objektů, jenž je sdílen všemi potravinami (tj. nikoliv jen druhem). Generováno je tak např. pořadí: Susenky-0000 Mleko-0001 Susenky-0002 Kontrakt rozhraní (jedinečnost identifikátorů) je však splněn. Vazba čítače na jednotlivé druhy potravin by program zbytečně komplikovala (bylo by nutné použít slovník mapující identifikátor druhu na hodnotu příslušného čítače). Implementace kontraktu by měla být co možná nejjednodušší. Výjimkou jsou případy, kdy je jednoduchá (triviální) implementace neefektivní (má např. exponenciální složitost, zatímco nejeoptimálnější implementace je kvadratická či dokonce lineární) Implementace skladu by se měla dít postupně v následujících fázích: list type= enum Implementace typu černá díra tj. pouze vkládání zboží do skladu. c l a s s Sklad { L i s t <I S k l a d o v a t e l n e > polozky = new L i s t <I S k l a d o v a t e l n e > ( ) ; void Vloz ( I S k l a d o v a t e l n e vyrobek ) { vyrobek. VlozDoSkladu ( t h i s ) ; polozky. Add ( vyrobek ) ; Při vkládání je nutno zajistit obousměrné provázání skladu a zboží, tj. musí být volána i metoda ISkladovatelne.VlozDoSkladu. Druhou fází je implementace vlastností pro výpočet celkových charakteristik skladu jako Sklad.CelkovyPocetKu resp. Sklad.CelkovaCena. Pro implementaci lze využít i LINQ (funkcionální zápis). 4
p u b l i c i n t CelkovyPocetKusu { g e t { return polozky. Sum ( p => p. PocetKusu ) ; Mezifáze: uzavřený sklad, z něhož se záhadně ztrácí zboží tj. implementace metody Sklad.NahodneUkradni. I S k l a d o v a t e l n e NahodneUkradni ( ) {... Testování funkčnosti metody na základě sumární metody (např. celkové ceny). Jak klesá cena při náhodné volbě resp. při kradení nejdražšího zboží (otázka jak krást efektivně to nejdražší = využití pomocné struktury a flexibilní přizpůsobení změnám). Implementace vyjímání zboží ze skladu. Pozornost je nutno zaměřit na konzistenci (musí být odstraněny odkazy v obou směrech). 5