====== Principy objektového stylu. Typické příklady objektových řešení. Návrhové vzory. ====== =====Klíčové koncepty modelování systémů: programovací paradigmata, dekompozice, hierarchie, abstrakce používané v softwarovém vývoji (jmenné, datové, funkcionální, objektové, SOLID, DRY).===== === Programovací paradigmata === * Způsoby, jak strukturovat a přemýšlet o programu. Mezi hlavní patří: * **Procedurální (imperativní)** – program jako posloupnost kroků a manipulace se stavem. * **Funkcionální** – program je složen z funkcí bez side efektů, práce s immutable daty. * **Objektové** – svět tvoří objekty, které spolu komunikují zprávami. * **Logické** – program se skládá z faktů a pravidel, odpovědi se získávají odvozením (např. Prolog). {{statnice:bakalar:omoparadigm.png?700}} === Deklarativní vs imperativní přístup === * **Deklarativní**: popisuje *co* chceme dosáhnout (např. SQL, HTML). * **Imperativní**: popisuje *jak* máme něco provést (např. Java, C). === Dekompozice === * Rozdělení systému na menší části (moduly, komponenty, objekty) pro snadnější pochopení a správu. * Podporována principy: * **Cohesion** – funkce uvnitř modulu by měly logicky souviset. * **Coupling** – minimalizujeme závislosti mezi moduly. * **Reusability** – kód se dá snadno znovu použít. * **DRY (Don't Repeat Yourself)** – stejná funkcionalita není duplikovaná. === Hierarchie === * Uspořádání tříd a objektů do struktur dle vztahů „is-a“, „has-a“ apod. * V objektově orientovaném designu běžně používáme dědičnost, kompozici a polymorfismus. * **Is-a** – dědičnost, třída potomek je podtypem předka. * **Has-a** – kompozice/agregace, objekt obsahuje jiný objekt. {{statnice:bakalar:omohierarchy.png?700}} === Abstrakce v softwarovém vývoji === * **Jmenná abstrakce** – názvy proměnných, funkcí, tříd, modulů. * **Datová abstrakce** – práce s datovými typy bez znalosti jejich vnitřní struktury (např. ADT, interface). * **Procedurální abstrakce** – přístup k funkcím/subrutinám přes rozhraní, bez znalosti implementace. * **Funkcionální abstrakce** – funkce jako hodnoty, vyšší funkce, čisté výpočty. * **Objektová abstrakce** – objekty skrývají stav, interagují přes metody (rozhraní). === Principy návrhu === * **SOLID principy:** * **S – Single Responsibility** – každá třída má mít jeden důvod ke změně. * **O – Open/Closed** – třídy mají být otevřené rozšíření, uzavřené modifikaci. * **L – Liskov Substitution** – podtypy musí být zaměnitelné za své nadtypy. * **I – Interface Segregation** – lépe více specifických rozhraní než jedno obecné. * **D – Dependency Inversion** – závislost má být na abstrakcích, ne konkrétních třídách. * **DRY (Don't Repeat Yourself)** * Každá znalost systému by měla být reprezentována pouze jednou. * Zamezuje duplicitě, usnadňuje údržbu a testování. === Shrnutí === * Modelování systémů vyžaduje správnou kombinaci **abstrakce**, **dekompozice** a **hierarchie**. * Různá **programovací paradigmata** určují, jak systém strukturujeme. * Použití principů jako **SOLID** a **DRY** pomáhá vytvářet kvalitní, udržitelný software. ===== Objektově orientovaný přístup: objekty, třídy a hierarchie tříd, zapouzdření, interface a abstraktní třídy, dědičnost, class diagramy a příklady systémů modelovaných pomocí OOP, encapsulation, rozdíl mezi asociací, agregací a kompozicí, dědičnost versus kompozice, polymorfismus, overloading versus overriding. ===== === Objekty a třídy === * **Objekt** je entita obsahující stav (atributy) a chování (metody). * **Třída** je předpis pro vytvoření objektů – definuje atributy a metody. * Z jedné třídy lze vytvořit více instancí (objektů), které sdílí metody, ale mají vlastní stav. === Zapouzdření (encapsulation) === * Schování vnitřní reprezentace objektu před okolím. * K datům se přistupuje výhradně přes veřejné metody (get/set). * Zvyšuje bezpečnost, modularitu a odolnost vůči změnám. === Interface a abstraktní třídy === * **Interface** – definuje smlouvu: jaké metody musí třída implementovat. - Všechny metody jsou implicitně public a nemají implementaci (kromě default/private metod v novějších verzích). * **Abstraktní třída** – částečná implementace, může obsahovat i neabstraktní metody. - Nelze ji instanciovat, ale lze z ní dědit a rozšiřovat ji. - Abstraktní třída může mít atributy, konstruktor a přístupová práva (public/protected/private). **Interface použijeme, jestliže:** * 1. Vytvářím vnější rozhraní k objektům – tedy veřejné API ke své komponentě. * 2. Chci, aby i objekty z jiné class hierarchie implementovaly stejné rozhraní. * 3. Předepisuju pouze metody a nikoliv jejich implementaci – cílem není sdílená logika. * 4. Chci předepsat třídě, aby implementovala metody z více rozhraní současně. **Abstraktní třídu použijeme, jestliže:** * 1. Chci sdílet kód mezi více třídami a neexistuje „neabstraktní“ předek těchto tříd. * 2. Třídy jsou mezi sebou úzce spjaté – sdílí mnoho proměnných a metod. * 3. Potřebujeme předepsat i metody, které neslouží k vnější komunikaci s objektem, tedy `private` a `protected`. * 4. Chci předefinovat proměnné, které nejsou `static` a `final`. === Dědičnost === * Třída může **zdědit** vlastnosti a chování jiné třídy (superclass → subclass). * Podporuje opětovné použití kódu a hierarchii typů. * `extends` – pro třídy, `implements` – pro rozhraní. === Dědičnost vs. Kompozice === * **Dědičnost** – silné propojení, změna předka ovlivní potomky. * **Kompozice** – objekt má jiné objekty jako součást svého stavu. * Kompozice je flexibilnější, vhodná pro dynamické chování, ale někdy vede k duplicitám. === Class diagramy a příklady === * UML Class Diagram ukazuje třídy, atributy, metody a vztahy (dědičnost, asociace…). * Příklady systémů vhodných pro OOP: - Systém knihovny (knihy, katalogy, účty, členové). - Správa objednávek (objednávka, zákazník, produkt, sklad). {{statnice:bakalar:omouml.png?700}} === Asociace, agregace, kompozice === * **Asociace** – slabý vztah, objekty žijí nezávisle (např. student – kurz). * **Agregace** – silnější, ale objekty stále žijí nezávisle (např. tým – hráči). * **Kompozice** – velmi silný vztah, zánik jednoho způsobí zánik druhého (např. dům – místnost). {{statnice:bakalar:omovazby.png?700}} === Polymorfismus === * Schopnost pracovat s objekty různého typu jednotně (např. podle společného rozhraní). * Umožňuje používat objekty různých tříd pomocí jejich společného nadtypu. === Overloading vs. Overriding === * **Overloading** – více metod se stejným jménem, ale různými parametry. - V rámci jedné třídy. * **Overriding** – přepsání metody předka v potomkovi se stejným jménem a parametry. - Umožňuje polymorfismus. - Přepisující metoda může mít méně restriktivní přístup a návratový typ může být podtypem originálu. === Shrnutí OOP přístupů === * V OOP se program skládá z objektů, které modelují reálné nebo doménové entity. * Vztahy mezi objekty a hierarchie tříd tvoří základ pro efektivní návrh softwaru. * OOP podporuje **modularitu**, **znovupoužitelnost** a **snadnější údržbu**. ===== Klíčové koncepty modelování systémů: abstraktní datové typy, mutabilita, imutabilita, rekurze, datové typy v rekurzi. ===== === Abstraktní datový typ (ADT) === * ADT odděluje **abstraktní vlastnosti** typu (hodnoty a operace) od **konkrétní implementace**. * ADT je matematický model: množina hodnot + operace nad touto množinou. * Umožňuje měnit reprezentaci bez dopadu na uživatele typu. * Příklady: `int`, `List`, `Map`, `String`, `Set`, `boolean`. ** Typy operací v ADT:** * **Creators** – vytvářejí nové instance (`new ArrayList()`) * **Producers** – vytvářejí nové hodnoty z existujících (`concat`, `substring`) * **Observers** – vracejí informace o objektu (`size`, `isEmpty`) * **Mutators** – mění stav objektu (`add`, `remove`) ** Vhodný návrh ADT:** * Má minimum operací, které jdou dobře kombinovat. * Každá operace je definována jednoznačně a univerzálně. * Měl by být buď generický (List, Map) nebo doménový (BalíčekKaret) – ne kombinace. **Příklady generických ADT:** * **int** - Hodnoty: celá čísla - Operace: * Creators: `0`, `1`, `-42` * Producers: `+`, `-`, `*`, `/` * Observers: není třeba – hodnota je přímo dostupná * Mutators: nelze – `int` je imutabilní * **String** - Hodnoty: posloupnosti znaků - Operace: * Creators: `""`, `"abc"` * Producers: `concat`, `substring`, `toUpperCase` * Observers: `length`, `charAt`, `isEmpty` * Mutators: žádné – `String` je imutabilní * **List** - Hodnoty: uspořádané sekvence hodnot - Operace: * Creators: `new ArrayList<>()` * Producers: `addAll`, `subList` * Observers: `size`, `get`, `contains` * Mutators: `add`, `remove`, `set` (u mutabilní implementace) **Příklady doménových ADT:** * **Typ: Balíček karet** - Hodnoty: posloupnosti karet - Operace: zamíchej(), rozdej(), přidejNaSpodek(), početKaret() - Rep invariant: žádné duplicity, max 52 karet * **Typ: Telefonní seznam (Map)** - Hodnoty: množina dvojic (jméno, číslo) - Operace: vlož(jméno, číslo), smaž(jméno), hledej(jméno) - Rep invariant: maximálně jedno číslo na jméno === Mutabilita vs Imutabilita === * **Mutabilní objekt** – může být po svém vytvoření změněn (`ArrayList`, `Date`). * **Imutabilní objekt** – po vytvoření se nemění (`String`, `Integer`, `LocalDate`). * Výhody imutability: * Snadnější důkaz korektnosti (nezmění se „pod rukama“). * Bezpečnější při paralelním zpracování (thread-safe). * Zachování invariant = jednodušší údržba. * Problém: *representation exposure* – možnost, že klient modifikuje vnitřní stav objektu. * Řešení: použití `final`, zapouzdření, kopírování (`defensive copying`), vlastní kontrola (`checkRep()`). === Invarianty === **Invarianta je vlastnost, která je splněna pro jakýkoliv runtime stav programu ve všech jeho stabilních stavech a nezávisí na chování klienta** Pozn. Invariance by měla být zaručena pro volání public metod. Tedy mohou existovat mezistavy, kdy je invariance porušena. * Typy invariantů: * **Rep Invariant (representation invariant)** – musí platit pro vnitřní stav objektu, aby byl platný (např. „žádné duplicity v množině“). * **Konzistenční invarianty systému** – například „zákazník nemůže mít záporný zůstatek“. * **Stavové invarianty** – např. „součet dílčích stavů odpovídá celkovému stavu“. * Účel invariantů: * Pomáhají odhalit chyby (při vývoji i testování). * Vyjadřují, co znamená „platný“ stav objektu. * Umožňují jednodušší dokazování správnosti (zejména u imutabilních typů). * Kontrola invariantů: * Může být prováděna explicitně (např. metodou `checkRep()` v implementaci). * Vhodná také pro automatizované testování a formální verifikaci. === Rekurze a datové typy v rekurzi === * Rekurze = definice objektu prostřednictvím sebe sama. * **Rekurzivní datové typy** – typy, které obsahují sebe samé: * Např. `List`, `Tree`, `Expression`, `Directory`. * Používá se: * K definici strukturovaných dat (složené dokumenty, výrazy). * Při zpracování stromových a sekvenčních struktur. * Výhody: * Přirozené a výstižné modelování. * Umožňuje definovat operace rekurzivně (např. `size()`, `depth()`, `eval()`). === Shrnutí === * ADT představují základní stavební bloky modelování dat. * Imutabilita a správná práce s invarianty zvyšují bezpečnost a čitelnost systému. * Rekurze je klíčový nástroj pro popis složitých typů a operací. ===== Funkcionánlní programování v Java===== === Funkce první třídy (first-class functions) === * Funkce je považována za „objekt první třídy“, pokud ji lze: * předat jako parametr jiné funkci, * přiřadit do proměnné, * vrátit z jiné funkce. * Java nepodporuje funkce jako typ, ale využívá funkcionální rozhraní (např. `Function`, `Predicate`, `Consumer`). * **Příklad:** Predicate isRetired = p -> p.getAge() > 65; === Funkce vyššího řádu (higher-order functions) === * Funkce, která přijímá jinou funkci jako parametr nebo ji vrací jako výsledek. * Java realizuje tento koncept pomocí funkcionálních rozhraní. * **Příklad filtru:** public List filter(List people, Predicate p) { return people.stream().filter(p).collect(Collectors.toList()); } === Lambda expressions === * Zkrácený zápis anonymní funkce. * Syntaxe: `(parametry) -> výraz` * **Příklad:** Comparator comp = (s1, s2) -> s1.length() - s2.length(); === Closures === * Lambda výrazy si mohou „zapamatovat“ hodnoty z okolního kontextu (tzv. *uzávěr*). * Hodnota musí být efektivně finální. * **Příklad:** int base = 10; Function addBase = x -> x + base; === Currying === * Rozdělení funkce s více parametry na posloupnost funkcí s jedním parametrem. * **Příklad:** Function> add = x -> y -> x + y; add.apply(2).apply(3); // vrátí 5 === Referential transparency === * Výraz je referenčně transparentní, pokud jej lze nahradit jeho výsledkem bez změny chování programu. * Týká se *pure functions* – bez vedlejších efektů. * **Výhoda:** umožňuje memoizaci (cache výsledků). === Lazy evaluation === * Výraz se nevyhodnocuje ihned, ale až když je jeho výsledek potřeba. * Java podporuje *lazy* chování např. přes `Stream`, `Optional`, `CompletableFuture`. * **Příklad:** Stream s = Stream.of(1, 2, 3).filter(x -> { System.out.println("filtrování " + x); return x > 1; }); // kód nad tím nic nevytiskne – evaluace je lazy === Shrnutí rozdílů oproti OOP === | Oblast | Funkcionální přístup | Objektový přístup | |-|-|-| | Data | Imutabilní | Mutabilní | | Exekuce | Libovolné pořadí volání | Definované pořadí volání metod | | Iterace | Rekurze | Cykly | | Paralelismus | Snadná podpora díky bezstavovým funkcím | Komplikovanější, vyžaduje synchronizaci | | Stav | Explicitní (parametry, návraty) | Implicitní (instance proměnné, settery) | ===== Creational design patterns: factory a abstract factory, builder, prototype, singleton, dependency injection ===== === Singleton === * Vzor zajišťující, že od dané třídy existuje jen jedna instance. * Používá se např. pro přístup ke konfiguraci, logger, správce připojení. * Problém: ve vícevláknovém prostředí musí být správně synchronizovaný. * Moderní varianta využívá **double-checked locking** a `volatile`. public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } === Factory Method === * Poskytuje rozhraní pro vytváření objektů, ale samotná implementace tvorby je ponechána podtřídám. * Cílem je umožnit instanciaci objektů bez toho, aby klient znal jejich konkrétní typ. * Typický v GUI systémech, kde různé platformy poskytují různé implementace komponent. abstract class Dialog { public void renderWindow() { Button okButton = createButton(); okButton.render(); } public abstract Button createButton(); } class WindowsDialog extends Dialog { public Button createButton() { return new WindowsButton(); } } class WebDialog extends Dialog { public Button createButton() { return new HtmlButton(); } } {{statnice:bakalar:omofactory.png?700}} === Abstract Factory === * **Továrna na továrny** – vytváří celou „rodinu“ příbuzných objektů bez znalosti jejich konkrétních tříd. * Umožňuje snadno měnit konkrétní implementaci celé architektury (např. jiná platforma nebo produkt). interface GUIFactory { Button createButton(); Checkbox createCheckbox(); } class WinFactory implements GUIFactory { public Button createButton() { return new WinButton(); } public Checkbox createCheckbox() { return new WinCheckbox(); } } {{statnice:bakalar:omoabstractfactory.png?700}} === Prototype === * Vytváření nových objektů klonováním existující instance. * Používá se, pokud je konstrukce objektu nákladná nebo složitá. * Může jít o: * **shallow copy** - clone() vytvoří novou instanci té samé třídy a nastaví všechny atributy na hodnoty atributů z instance na které voláme clone(). * **deep copy** - clone() provede shallow copy a pak clone() i všech navázaných objektů. Naklonuje celý objektový graf. Hloubka kopie může být různá. public class Employee implements Cloneable { private String name; private Address address; public Employee(String name, Address address) { this.name = name; this.address = address; } @Override public Employee clone() { return new Employee(this.name, this.address.clone()); } } {{statnice:bakalar:omoprototype.png?700}} === Builder === * Odděluje konstrukci komplexního objektu od jeho reprezentace. * Podporuje **fluent interface** – volání metod v řetězci. * Výhodné při tvorbě objektů s mnoha volitelnými částmi. enum EColor { RED, WHITE, BLACK } public class House { private EColor color; private List windows = new ArrayList<>(); private List walls = new ArrayList<>(); private Door door; public void setColor(EColor color) { this.color = color; } public void addWindow(Window window) { this.windows.add(window); } public void addWall(Wall wall) { this.walls.add(wall); } public void setDoor(Door door) { this.door = door; } } public class HouseBuilder { private House house = new House(); public HouseBuilder addWall(){ house.addWall(new Wall()); return this; } public HouseBuilder addWindow(){ house.addWindow(new Window()); return this; } public HouseBuilder buildDoor() { house.setDoor(new Door()); return this; } public HouseBuilder paint(EColor color){ house.setColor(color); return this; } public House getResult(){ return house; } } HouseBuilder builder = new HouseBuilder(); House yourHouse = builder .addWall() .addWindow() .buildDoor() .paint(EColor.RED) .getResult(); {{statnice:bakalar:omobuilder.png?700}} === Dependency Injection (DI) === * Technika PRO VKLÁDÁNÍ ZÁVISLOSTÍ MEZI JEDNOTLIVÝMI KOMPONENTAMI PROGRAMU TAK, ABY JEDNA KOMPONENTA MOHLA POUŽÍVAT DRUHOU, ANIŽ BY NA NI MĚLA V DOBĚ SESTAVOVÁNÍ PROGRAMU REFERENCI. * Základní přístupy: * **Injekce přes atribut** (`@Autowired`) @Component public class Customer { @Autowired private Person person; private int type; } * **Injekce přes setter** @Component public class Customer { private Person person; @Autowired public void setPerson(Person person) { this.person = person; } } * **Injekce přes konstruktor** @Component public class Customer { private Person person; @Autowired public Customer (Person person) { this.person=person; } } * Umožňuje lepší testovatelnost, volnou vazbu a řízení závislostí frameworkem (např. Spring). {{statnice:bakalar:omodi.png?700}} ===== Structural design patterns: adapter, proxy, bridge, composite, facade, decorator, flyweight ===== === Adapter === * Slouží ke **sladění rozhraní** mezi nekompatibilními třídami. * Vytváří třídu, která **překládá rozhraní očekávané klientem** na rozhraní existující komponenty. * Používá se, pokud chceme znovupoužít třídu, ale její rozhraní neodpovídá očekávání aplikace. class EuropeanSocket { public void connect(String wires) { ... } } interface CzechPlug { void zapoj(); } class Adapter implements CzechPlug { private EuropeanSocket socket; public Adapter(EuropeanSocket socket) { this.socket = socket; } public void zapoj() { socket.connect("fáze + nulák"); } } {{statnice:bakalar:omoadapter.png?700}} === Proxy === * Objekt, který má **stejné rozhraní jako cílový objekt**, ale přidává vlastní logiku (např. bezpečnost, logování, cache). * Typicky se používá pro řízení přístupu, lazy loading, monitorování atd. interface Tower { void enter(Wizard wizard); } class TowerProxy implements Tower { private Tower tower = new WizardTower(); public void enter(Wizard wizard) { if (wizard.getColor() != Color.GREEN) { tower.enter(wizard); } else { System.out.println("Green wizard is not allowed."); } } } {{statnice:bakalar:omoproxy.png?700}} === Facade === * Vytváří **zjednodušené rozhraní** pro komplexní podsystém. * Klient neřeší detaily volání vnitřních služeb a má přístup jen ke stabilnímu API. public class KitchenFacade { private Microwave microwave = new Microwave(); private Fridge fridge = new Fridge(); public void prepareSnack() { fridge.open(); microwave.heat(); fridge.close(); } } {{statnice:bakalar:sinfacade.png?500}} === Composite Design Pattern === * Účel: * Umožňuje jednotné zacházení s **jednotlivými objekty** i s **jejich složením (stromová struktura)**. * Používá se pro reprezentaci **hierarchických struktur** (např. souborový systém, GUI komponenty, organizace). * Struktura: * **Component** – společné rozhraní pro listy i složené objekty. * **Leaf** – jednoduchý objekt (např. soubor). * **Composite** – složený objekt (např. složka obsahující další objekty). * Výhody: * Rekurzivní zpracování hierarchických struktur. * Klientský kód se nemusí starat, zda pracuje s listem nebo composite – volá jednotné metody. * Umožňuje dynamicky přidávat/odebírat prvky. === Příklad použití: souborový systém === interface FileSystemItem { void display(String indent); } class File implements FileSystemItem { private String name; public File(String name) { this.name = name; } public void display(String indent) { System.out.println(indent + "- File: " + name); } } class Folder implements FileSystemItem { private String name; private List children = new ArrayList<>(); public Folder(String name) { this.name = name; } public void add(FileSystemItem item) { children.add(item); } public void display(String indent) { System.out.println(indent + "+ Folder: " + name); for (FileSystemItem item : children) { item.display(indent + " "); } } } public class CompositeDemo { public static void main(String[] args) { Folder root = new Folder("root"); root.add(new File("readme.txt")); Folder src = new Folder("src"); src.add(new File("Main.java")); src.add(new File("Utils.java")); root.add(src); root.display(""); } } === Decorator === * Umožňuje **dynamicky přidat chování** do objektu, aniž bychom měnili jeho třídu. * Vytváří se obalové třídy se stejným rozhraním jako původní objekt. interface Coffee { String getDescription(); double getCost(); } class BasicCoffee implements Coffee { public String getDescription() { return "Coffee"; } public double getCost() { return 1.0; } } class MilkDecorator implements Coffee { private Coffee coffee; public MilkDecorator(Coffee coffee) { this.coffee = coffee; } public String getDescription() { return coffee.getDescription() + ", milk"; } public double getCost() { return coffee.getCost() + 0.5; } } {{statnice:bakalar:omodecorator.png?700}} === Flyweight === * Minimalizuje spotřebu paměti **sdílením společného stavu** mezi instancemi objektů. * Například `Character` nebo `Particle`, kde se vnitřní reprezentace znovupoužívá. class BulletType { private String texture; public BulletType(String texture) { this.texture = texture; } } class BulletFactory { private static Map cache = new HashMap<>(); public static BulletType get(String texture) { return cache.computeIfAbsent(texture, BulletType::new); } } === Bridge === * Do root třídy připojím další hiearachii tříd. * Umožňuje kombinovat dvě nezávislé hierarchie – např. tvary a jejich vykreslení. {{statnice:bakalar:omobridge.png?700}} public interface Color { String fill(); } public class Blue implements Color { @Override public String fill() { return "Color is Blue"; } } public class Red implements Color { @Override public String fill() { return "Color is Red"; } } public abstract class Shape { protected Color color; public Shape(Color color) { this.color = color; } public abstract String draw(); } public class Square extends Shape { public Square(Color color) { super(color); } @Override public String draw() { return "Square drawn. " + color.fill(); } } ==== Srovnání ==== * **Adapter** poskytuje rozdílný interface oproti objektu, který zpřístupňuje, **Proxy** proskytuje shodný interface * **Facade a Proxy** * podobné v tom, že inicializují a poskytují odstínění klienta od komplexních využívaných komplexních objektů * Rozdíl v tom, že proxy poskytuje shodný interface jako servisní objekt * **Decorator a Proxy** * Podobná struktura, ale rozdílný účel * **Proxy** spravuje životní cyklus servisního objektu * Struktura **Dekorátoru** je řízena klientem * **Facade** definuje nové rozhraní,** Adapter** spojuje existující rozhraní ===== Behavioral design patterns ===== === Iterator === * Odděluje způsob iterace od konkrétní datové struktury. * Umožňuje jednotný přístup k prvkům bez znalosti interní reprezentace. * Pouze řídí procházení datové struktury pomocí metod **next() a hasNext()** // Iterátor – definuje rozhraní pro iteraci public interface Iterator { boolean hasNext(); T next(); } // Kontejner – definuje rozhraní, které vrací iterátor public interface Container { Iterator getIterator(); } // Konkrétní implementace kontejneru public class NameRepository implements Container { private String[] names = { "Anna", "Bob", "Charlie" }; @Override public Iterator getIterator() { return new NameIterator(); } // Interní implementace iterátoru private class NameIterator implements Iterator { int index = 0; public boolean hasNext() { return index < names.length; } public String next() { return hasNext() ? names[index++] : null; } } } // Použití iterátoru public class Demo { public static void main(String[] args) { NameRepository repo = new NameRepository(); Iterator it = repo.getIterator(); while (it.hasNext()) { System.out.println("Name: " + it.next()); } } } {{statnice:bakalar:omoiterator.png?700}} === Chain of Responsibility === * Vytváří řetěz objektů, které mohou zpracovat požadavek. * Každý článek rozhoduje, zda požadavek vyřídí nebo předá dál. // Abstraktní handler abstract class Logger { public static int INFO = 1; public static int DEBUG = 2; public static int ERROR = 3; protected int level; protected Logger nextLogger; public void setNext(Logger nextLogger) { this.nextLogger = nextLogger; } public void logMessage(int level, String message) { if (this.level <= level) { write(message); } if (nextLogger != null) { nextLogger.logMessage(level, message); } } protected abstract void write(String message); } // Konkrétní implementace – konzolový logger class ConsoleLogger extends Logger { public ConsoleLogger(int level) { this.level = level; } protected void write(String message) { System.out.println("Standard Console::Logger: " + message); } } // Konkrétní implementace – souborový logger class FileLogger extends Logger { public FileLogger(int level) { this.level = level; } protected void write(String message) { System.out.println("File::Logger: " + message); } } // Konkrétní implementace – chybový logger class ErrorLogger extends Logger { public ErrorLogger(int level) { this.level = level; } protected void write(String message) { System.out.println("Error Console::Logger: " + message); } } // Klient, který sestaví řetězec a provede logování public class ChainPatternDemo { private static Logger getChainOfLoggers() { Logger errorLogger = new ErrorLogger(Logger.ERROR); Logger fileLogger = new FileLogger(Logger.DEBUG); Logger consoleLogger = new ConsoleLogger(Logger.INFO); errorLogger.setNext(fileLogger); fileLogger.setNext(consoleLogger); return errorLogger; } public static void main(String[] args) { Logger loggerChain = getChainOfLoggers(); loggerChain.logMessage(Logger.INFO, "This is an informational message."); loggerChain.logMessage(Logger.DEBUG, "This is a debug level message."); loggerChain.logMessage(Logger.ERROR, "This is an error message."); } } {{statnice:bakalar:omochain.png?700}} === Strategy === * Umožňuje měnit algoritmus nebo chování za běhu. * Klient deleguje operaci na zvolenou strategii. * Používá se, když pro realizaci jednoho tasku chceme flexibilně přepínat mezi různými algoritmy // Společné rozhraní pro strategii public interface PoliceStrategy { void checkDriver(Driver driver); } // Jednodušší strategie public class NicePolice implements PoliceStrategy { @Override public void checkDriver(Driver driver) { System.out.println("You're okay, have a nice day!"); } } // Přísnější strategie public class HardPolice implements PoliceStrategy { @Override public void checkDriver(Driver driver) { if (driver.getAlcoholLevel() > 0) { System.out.println("You are under arrest!"); } else { System.out.println("You're free to go."); } } } // Kontext používající zvolenou strategii public class PoliceOfficer { private PoliceStrategy strategy; public PoliceOfficer(PoliceStrategy strategy) { this.strategy = strategy; } public void setStrategy(PoliceStrategy strategy) { this.strategy = strategy; } public void performCheck(Driver driver) { strategy.checkDriver(driver); } } {{statnice:bakalar:omostrategy.png?700}} === Visitor === * Odděluje algoritmus od struktury dat. * Umožňuje přidat nové operace bez změny datové struktury. * Realizace patternu je pomocí tzv. **double dispatch** principu. * Elementy datové struktury u kterých chci v budoucnosti přidávat operace, mají metodu accept(Visitor) uvnitř které zavolám na vloženém visitoru metodu visitElement(Element) * Tím předám řízení visitoru a zároveň mu předám referenci public interface ComputerPart { void accept(Visitor v); } public class Mouse implements ComputerPart { public void accept(Visitor v) { v.visit(this); } } public class DisplayVisitor implements Visitor { public void visit(Mouse mouse) { System.out.println("Displaying Mouse."); } } {{:statnice:bakalar:omovisitor.png?700}} === Observer === * Umožňuje reakcí více objektů (observers) na změnu stavu jiného objektu (subject). * Subject si spravuje seznam observerů a při změně je notifikuje. // Rozhraní pozorovatele public abstract class Observer { protected Subject subject; public abstract void update(); } // Třída Subject s registrací a notifikací public class Subject { private List observers = new ArrayList<>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; notifyAllObservers(); } public void attach(Observer observer) { observers.add(observer); } private void notifyAllObservers() { for (Observer o : observers) { o.update(); } } } // Konkrétní pozorovatelé public class HexObserver extends Observer { public HexObserver(Subject subject) { this.subject = subject; subject.attach(this); } public void update() { System.out.println("Hex: " + Integer.toHexString(subject.getState())); } } public class BinaryObserver extends Observer { public BinaryObserver(Subject subject) { this.subject = subject; subject.attach(this); } public void update() { System.out.println("Binary: " + Integer.toBinaryString(subject.getState())); } } public class OctalObserver extends Observer { public OctalObserver(Subject subject) { this.subject = subject; subject.attach(this); } public void update() { System.out.println("Octal: " + Integer.toOctalString(subject.getState())); } } // Ukázka použití public class ObserverDemo { public static void main(String[] args) { Subject subject = new Subject(); new HexObserver(subject); new OctalObserver(subject); new BinaryObserver(subject); System.out.println("First state change: 15"); subject.setState(15); System.out.println("Second state change: 10"); subject.setState(10); } } {{:statnice:bakalar:omoobserver.png?700}} === Template Method === * Definuje kostru algoritmu v nadtřídě, variantní části jsou ponechány podtřídám. * Používá se, když je potřeba opakovat strukturu algoritmu, ale některé kroky se liší. abstract class Game { final void play() { initialize(); startPlay(); endPlay(); } abstract void initialize(); abstract void startPlay(); abstract void endPlay(); } {{:statnice:bakalar:omotemplate.png?700}} === State === * Modeluje stav systému jako objekt. * Umožňuje **měnit chování objektu v závislosti na jeho vnitřním stavu**, aniž bychom museli používat podmínky typu `if` nebo `switch`. * Každý stav implementuje vlastní logiku a může přepnout objekt do jiného stavu. // Rozhraní pro stav public interface State { void onPlay(Player player); void onLock(Player player); } // Kontext – přehrávač public class Player { private State state; public Player() { this.state = new ReadyState(); // výchozí stav } public void setState(State state) { this.state = state; } public void pressPlay() { state.onPlay(this); } public void pressLock() { state.onLock(this); } } // Konkrétní stav – Ready public class ReadyState implements State { public void onPlay(Player player) { System.out.println("Start playing music."); player.setState(new PlayingState()); } public void onLock(Player player) { System.out.println("Locked from ready state."); player.setState(new LockedState()); } } // Konkrétní stav – Playing public class PlayingState implements State { public void onPlay(Player player) { System.out.println("Paused."); player.setState(new ReadyState()); } public void onLock(Player player) { System.out.println("Locked while playing."); player.setState(new LockedState()); } } // Konkrétní stav – Locked public class LockedState implements State { public void onPlay(Player player) { System.out.println("Can't play, device is locked."); } public void onLock(Player player) { System.out.println("Unlocked."); player.setState(new ReadyState()); } } // Ukázka použití public class StateDemo { public static void main(String[] args) { Player player = new Player(); player.pressPlay(); // Start playing music. player.pressLock(); // Locked while playing. player.pressPlay(); // Can't play, device is locked. player.pressLock(); // Unlocked. player.pressPlay(); // Start playing music. } } {{:statnice:bakalar:omostate.png?700}} === Memento === * Umožňuje vrátit objekt do předchozího stavu. * Využívá se pro `undo`, `redo` funkce. * Struktura: * `Originator` – objekt, jehož stav se ukládá. * `Memento` – objekt obsahující uložený stav. * `Caretaker` – spravuje historii mement bez znalosti detailů. // Memento – reprezentuje uložený stav public class Memento { private final String state; public Memento(String state) { this.state = state; } public String getState() { return state; } } // Originator – třída s měnitelným stavem public class Originator { private String state; public void setState(String state) { this.state = state; System.out.println("Current state: " + state); } public Memento saveStateToMemento() { return new Memento(state); } public void getStateFromMemento(Memento memento) { state = memento.getState(); System.out.println("State restored to: " + state); } } // Caretaker – uchovává historii stavů public class Caretaker { private List mementoList = new ArrayList<>(); public void add(Memento state) { mementoList.add(state); } public Memento get(int index) { return mementoList.get(index); } } // Použití public class MementoDemo { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("State #1"); originator.setState("State #2"); caretaker.add(originator.saveStateToMemento()); originator.setState("State #3"); caretaker.add(originator.saveStateToMemento()); originator.setState("State #4"); originator.getStateFromMemento(caretaker.get(0)); // State #2 originator.getStateFromMemento(caretaker.get(1)); // State #3 } } {{:statnice:bakalar:omomemento.png?700}} === Interpreter === * Definuje jazyk (syntaxi + sémantiku) pomocí tříd. * Každý prvek výrazu má svou třídu. * Používá se k implementaci jednoduchých jazyků, výrazů, konfigurací, pravidel. * Struktura: * `Expression` – rozhraní nebo abstraktní třída pro všechny výrazy. * `TerminalExpression` – konkrétní výrazy (proměnné, konstanty). * `NonTerminalExpression` – operace (např. sčítání, odečítání). * `Context` – mapa proměnných (např. {"x" → 10}) // Rozhraní pro výraz public interface Expression { int interpret(Map context); } // Terminální výraz – proměnná public class Variable implements Expression { private String name; public Variable(String name) { this.name = name; } public int interpret(Map context) { return context.get(name); } } // Non-terminal – součet public class Plus implements Expression { private Expression left, right; public Plus(Expression left, Expression right) { this.left = left; this.right = right; } public int interpret(Map context) { return left.interpret(context) + right.interpret(context); } } // Non-terminal – rozdíl public class Minus implements Expression { private Expression left, right; public Minus(Expression left, Expression right) { this.left = left; this.right = right; } public int interpret(Map context) { return left.interpret(context) - right.interpret(context); } } // Ukázka použití public class InterpreterDemo { public static void main(String[] args) { Expression expr = new Plus( new Variable("x"), new Minus( new Variable("y"), new Variable("z") ) ); Map context = Map.of("x", 5, "y", 10, "z", 3); int result = expr.interpret(context); // 5 + (10 - 3) = 12 System.out.println("Výsledek: " + result); } } {{:statnice:bakalar:omointerpreter.png?500}} ===== Datové struktury a patterny: lazy loading, object pool, cache map, filter, reduce pattern ===== === Lazy Loading === * Odložené načtení dat z paměti, databáze nebo vzdáleného zdroje. * Zvyšuje efektivitu, pokud některá data nejsou vždy potřeba. * Čtyři hlavní implementace: * **Virtual Proxy** – místo objektu se vrací proxy, která objekt načte při prvním použití. * **Lazy Initialization** – načítání na základě podmínky `if (obj == null)`. * **Ghost** – objekt se načítá po částech podle potřeby (např. jen ID, pak detaily). * **Value Holder** – objekt zabalený v obalu, který řídí načtení hodnoty. public class Company { private ContactList contacts; public ContactList getContacts() { if (contacts == null) { contacts = new ContactListProxy(); // virtual proxy } return contacts; } } public class ContactListProxy implements ContactList { private ContactList realList; public List getCustomers() { if (realList == null) { realList = new RealContactList(); // expensive operation } return realList.getCustomers(); } } {{statnice:bakalar:omolazy.png?300}} === Object Pool === * Vzor, který opakovaně používá drahé objekty místo jejich opakovaného vytváření a likvidace. * Typické použití: * JDBC připojení (`ConnectionPool`) * File handle * HTTP klienti * Pool může mít parametry: minimální/ maximální počet instancí, TTL, idle cleanup. public class ObjectPool { private BlockingQueue pool; public ObjectPool(Supplier creator, int maxSize) { pool = new ArrayBlockingQueue<>(maxSize); for (int i = 0; i < maxSize; i++) { pool.offer(creator.get()); } } public T borrow() throws InterruptedException { return pool.take(); } public void release(T obj) { pool.offer(obj); } } // Použití: ObjectPool connectionPool = new ObjectPool<>(MyConnection::new, 5); Connection conn = connectionPool.borrow(); // ... connectionPool.release(conn); {{statnice:bakalar:omoobjpool.png?300}}{{statnice:bakalar:omoobjpool2.png?300}} === Cache === * Vzor, který ukládá data, která se často opakovaně používají. * Používá se např. pro: * Konfigurace, katalogy, uživatelská data * Základní princip: * `get(key)` → pokud data nejsou nalezena, stáhnout z pomalého zdroje a uložit. * Eviction policy (např. LRU) zabraňuje přetečení paměti. public class SimpleCache { private final int capacity; private final Map cache = new LinkedHashMap<>(); public SimpleCache(int capacity) { this.capacity = capacity; } public synchronized V get(K key, Supplier dataLoader) { if (!cache.containsKey(key)) { if (cache.size() >= capacity) { Iterator it = cache.keySet().iterator(); it.next(); it.remove(); // remove oldest } cache.put(key, dataLoader.get()); } return cache.get(key); } } // Použití: SimpleCache personCache = new SimpleCache<>(100); Person p = personCache.get("123", () -> loadPersonFromDB("123")); {{statnice:bakalar:omocache.png?400}} === Map, Filter, Reduce (stream API) === * Funkcionální operace na sekvencích dat (např. kolekce) umožňující čistý a efektivní styl programování. * Vzniká **pipeline** z řetězených operací, které jsou **lazy** vyhodnocovány. * **Map**: transformace každého prvku. List names = people.stream() .map(Person::getName) .collect(Collectors.toList()); * **Reduce**: agregace prvků do jedné hodnoty. // Příklad z prezentace – nalezení největšího čísla List numbers = Arrays.asList(5, 12, 3, 21, 7); int max = numbers.stream() .reduce(Integer.MIN_VALUE, (a, b) -> a > b ? a : b); \\tady passujeme lambda funkci System.out.println("Maximum: " + max); * **Filter**: výběr pouze těch prvků, které splňují predikát. List fromPraha = people.stream() .filter(p -> p.getCity().equals("Praha")) .collect(Collectors.toList()); * **Kombinování:** List sortedNames = people.stream() .filter(p -> p.getCity().equals("Praha")) .map(Person::getName) .sorted() .collect(Collectors.toList()); ===== Specifikace, návrhy specifikací, web API, Apiary, Swagger, GraphQL ===== ### Specifikace = Formální popis toho, jak se má komponenta v systému chovat - zahrnuje popis jejích rozhraní, funkcí a očekávaného chování - představuje společný kontrakt pro implementátora komponenty a klienta komponenty - význam specifikace: předchází chybám, srozumitelnost, připravenost na změny **Behaviorální ekvivalence** "Dva kódy jsou behaviorálně ekvivalentní, pokud při stejných vstupech ve stejném kontextu dávají stejné výstupy a vedou ke stejnému stavu systému." - odpovídá na otázku zda-li se dva zdrojové kódy chovají stejně (možnost výměny kódu beze změny chování systému) - není obecně testovatelná - faktory ovlivňující behav. ekvivalenci: Vstupní hodnoty, Kontext běhu, Vedlejší efekty, Chybové stavy - hraje roli i kontrakt mezi klientem a implementací: - Preconditions = požadavky, které musí klient splnit, než zavolá funkci - Postconditions = záruky, které funkce poskytne **Změna specifikace** - silná vs. slabá S2 je silnější nebo stejná jako S1 pokud: - Preconditions pro S2 jsou slabší než nebo stejné jako pro S1 - Postcondition pro S2 jsou silnější nebo stejné jako pro S1 => můžeme nahradit S1 za S2 bez ovlivnění stávajících klientů **Přesnost popisu specifikace** - Deterministic: jednoznačně určený výstup - Undetermined: není jednozačně určen výstup Příklad: "vrátí __první__ index i, kde arr[i] = val" vs "vrátí index i, kde arr[i] = val" **Deklarativní vs Operativní specifikace** - Deklarativní: nepopisuje detaily interních kroků implementace, popisuje výstup na základě vstupů - Operativní: popisuje kroky implementace, slouží jako návod pro vývojáře (deklarativní preferovanější) **Testování** - testy musí splňovat podmínky specifikace, testy nesmí být podřízeny implementaci, ale výhradně specifikaci rozhraní ### REST (Representational State Transfer) = architektonický styl pro návrh webových API (nemusí být nutně vázaný na HTTP protokol) - REST API jsou navrženy na základě resources = objekty/data/služby ke kterým klient přistupuje - každý resource má identifikátor - URI - používá se jednotný interface, aby byl zajištěn decoupling klienta a serveru (v HTTP použito verb v hlavičce - get, put, post, delete, patch) - ! je bezestavový, odpovědi mohou být cacheovány Existují různé úrovně implementace: - Level 0: jedno URI pro všechny operace, operace jsou post volání na toto URI - Level 1: separátní URI pro jednotlivé resources - Level 2: využití HTTP metod pro definici operací na resource - Level 3: využití hypermedia odkazů (odpovědi obsahují URI na další resources např. objednávka -> uri pro položky) **Web API** = api na webovém serveru nebo klientu - request response koncept, předávání zpráv ve formátu JSON nebo XML - ve většine řešení postavený na HTTP (využívání response kódů) SOAP (Simple Object Access Protocol) - protokol pro komunikaci mezi 2 zařízeními - pro popis služeb využívá WSDL (web services description language) - formální popis poskytované funkcionality (xml) - zprávy jsou ve formátu XML - zpřístupňuje klientovi funkce a operace (např. může mít funkci CreateEmployee pro vytvoření zaměstnance v systému) -> orientovaná procedurálně - není cachovaný - pomalejší REST API - zpřístupňuje klientovi data -> orientovaná datově - využívá HTTP - metody pro přístup ke zdrojům: get, put, post, delete, patch - podporuje různé formáty - především JSON ale mohou být různé - popis endpointů je méně formální - rychlejší GraphQL - efektivní nástupce REST API - dává možnost specifikace požadovaných dat - když operace vrací objektový graf, tak mohu specifikovat hloubku a podmnožiny atributů jednotlivých entit - zvládá větší zátěž než REST Open API - Open API Initiative - zaměřuje se na vytvoření,zkvalitňování a propagaci formátu pro popis rozhraní, které bude nezávislé na dodavateli - Open API Specification - definuje standard pro popis REST API, nezávislé na programovacím jazyku - může být použita pro generování klientského i serverového kódu pro různé programovací jazyky, generování dokumentace, validace obsahu zpráv, testování Swagger - framework pro definici rozhraní podle specifikace OpenAPI - pokrývá celý životní cyklus REST API: - návrh - Swagger Editor - nástroj pro psaní OpenAPI specifikace v YAML/JSON - dokumentace - Swagger UI - vygeneruje interaktivní dokumentaci z OpenAPI popisu - build - Swagger Codegen - automaticky generuje kód klientů/serverů podle specifikace - testování - Swagger Inspector - umožňuje testovat API volání přímo z prohlížeče - deployment - zakomponování do Docker apod. na datové struktury se v rámci zpráv odkazuje pomocí REF - uri APIARY - obdobný nástroj swaggeru