This is an old revision of the document!
Table of Contents
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).
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.
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).
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).
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<T,R>`, `Predicate<T>`, `Consumer<T>`).
- Příklad:
Predicate<Person> 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<Person> filter(List<Person> people, Predicate<Person> p) { return people.stream().filter(p).collect(Collectors.toList()); }
Lambda expressions
- Zkrácený zápis anonymní funkce.
- Syntaxe: `(parametry) → výraz`
- Příklad:
Comparator<String> 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<Integer, Integer> addBase = x -> x + base;
Currying
- Rozdělení funkce s více parametry na posloupnost funkcí s jedním parametrem.
- Příklad:
Function<Integer, Function<Integer, Integer>> 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<Integer> 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(); } }
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(); } }
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()); } }
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<Window> windows = new ArrayList<>(); private List<Wall> 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();
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).
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"); } }
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."); } } }
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(); } }
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; } }
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<String, BulletType> 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í.
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<T> { boolean hasNext(); T next(); } // Kontejner – definuje rozhraní, které vrací iterátor public interface Container<T> { Iterator<T> getIterator(); } // Konkrétní implementace kontejneru public class NameRepository implements Container<String> { private String[] names = { "Anna", "Bob", "Charlie" }; @Override public Iterator<String> getIterator() { return new NameIterator(); } // Interní implementace iterátoru private class NameIterator implements Iterator<String> { 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<String> it = repo.getIterator(); while (it.hasNext()) { System.out.println("Name: " + it.next()); } } }
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."); } }
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); } }
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."); } }
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<Observer> 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); } }
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(); }
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. } }
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<Memento> 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 } }
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<String, Integer> 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<String, Integer> 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<String, Integer> 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<String, Integer> 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<String, Integer> context = Map.of("x", 5, "y", 10, "z", 3); int result = expr.interpret(context); // 5 + (10 - 3) = 12 System.out.println("Výsledek: " + result); } }