SOLID princípy a JavaScript
2026-10
SOLID princípy a JavaScript
Milan Herda
2026-10
SOLID princípy a TypeScript
Milan Herda
2026-10
Čo je SOLID?
Skratka, pod ktorou sa skrýva
päť základných princípov
dobrej softvérovej architektúry
Čo je SOLID?
Päť základných princípov = mali by sme všetci poznať
Poznáme? 😅
- Single Responsibility Principle
- Open-Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Koľkí z vás by vedeli kolegovi vysvetliť:
Čo je SOLID?
Nepoznáme definíciu ❌
Ale aplikujeme intuitívne ✔️
Je to zle? 🤷♂️
Nemusíme poznať definíciu na to, aby sme niečo úspešne používali.
Čo je SOLID?
Nemusíme poznať definíciu na to, aby sme niečo úspešne používali.
Avšak, keď tú definíciu poznáme, tak:
- premýšľame jasnejšie
- pýtame sa a hľadáme presnejšie
- vidíme ďalej
SOLID a OOP
SOLID princípy sú často vysvetľované na triedach.
Takže vzniká dojem, že sa týkajú iba OOP programovania.
Sú však aplikovateľné aj mimo tried a objektov.
1. Single Responsibility Principle
Princíp jedinej zodpovednosti
Definícia
Modul by mal mať jeden (a iba jeden) dôvod pre svoju zmenu.
SRP
Definícia
Modul by mal mať jeden (a iba jeden) dôvod pre svoju zmenu.
💡Pomôcka pre uvažovanie:
Veci, ktoré sa menia z rovnakých dôvodov, dávame k sebe.
Tie, ktoré sa menia z odlišných dôvodov, patria do odlišných modulov.
📝 Príklady:
- zmena dizajnu by nemala ovplyvniť databázovú štruktúru
- výmena frameworku nesmie vyžadovať zmeny v biznis modeli firmy
SRP
Definícia
Modul by sa mal zodpovedať jednému (a iba jednému) aktérovi.
🤔 Kto je aktér?
Aktér je jedna alebo viac osôb,
ktoré chcú rovnakú zmenu v systéme.
📝 Príklady:
- finančné oddelenie
- právne oddelenie
- UX oddelenie
- IT bezpečnosť
- technický architekt
- používatelia
SRP
SRP
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
const topTenBooks = [];
const result = await fetch("/api/books.json");
if (result.ok) {
const data = await result.json();
topTenBooks.push(...data.items);
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App topTenBooks={topTenBooks} />
</StrictMode>,
);Koľko zodpovedností alebo dôvodov na zmenu vidíte?
🔍
- zodpovednosť: získanie dát
- dôvod zmeny: spôsob získavania dát
- zodpovednosť: inicializácia aplikácie
- dôvod zmeny: výmena frameworku
SRP
Ako refaktorovať?
🤔
Oddelenie do samostatných modulov
// src/main.ts
import loadTopTenBooks from './books/repo/loadTopTen';
import mountApp from './infra/fw/mountApp';
const books = loadTopTenBooks();
mountApp(books);SRP
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks() {
const topTenBooks: Book[] = [];
const result = await fetch("/api/books.json");
if (result.ok) {
const data = await result.json();
topTenBooks.push(...data.items);
}
return topTenBooks;
}🎉
// src/infra/fw/mountApp.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { type Book } from "./dto/Book";
export function mountApp(
topTenBooks: Book[]
) {
const root = createRoot(
document.getElementById("root")!
);
root.render(
<StrictMode>
<App topTenBooks={topTenBooks} />
</StrictMode>
),
};Po oddelení do vlastných modulov
SRP
// src/main.ts
import loadTopTenBooks from './books/repo/loadTopTen';
import mountApp from './infra/fw/mountApp';
const books = loadTopTenBooks();
mountApp(books);👍
Čo sme týmto získali?
- jasnejšie oddelenie hraníc
- bezpečnejšie zmeny
- potenciál na jednoduchšie výmeny modulov alebo celých aplikačných vrstiev
2. Open-Closed Principle
Princíp otvorenej uzavretosti
Definícia
Každý softvérový artefakt (trieda, modul, funkcia) by mal byť otvorený pre rozširovanie, ale uzatvorený pre úpravy.
💡Pomôcka pre uvažovanie:
Píšme kód tak, aby sme vedeli modifikovať jeho správanie aj bez jeho prepisu.
📝 Príklady:
- používajme parametre a konfiguráciu
- vyhýbajme sa detailom a konkrétnostiam v kóde (adresy, texty, hodnoty...)
K tomu potrebujeme oddeliť konkrétne časti od všeobecných.
OCP
OCP
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks() {
const data = await loadData("/api/books.json");
return data.items.map(
(bookData) => createBook(bookData)
);
}🔍
Vidíte konkrétnosti, ktoré vieme oddeliť?
OCP
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks(sourceUrl: string = "/api/books.json") {
const data = await loadData(sourceUrl);
return data.items.map(
(bookData) => createBook(bookData)
);
}🎉
Oddeľme túto konkrétnosť od všeobecného kódu
OCP
// src/book/repo/loadTopTen.ts
export async function loadTopTenBooks(sourceUrl: string = "/api/books.json") {
const data = await loadData(sourceUrl);
return data.items.map(
(bookData) => createBook(bookData)
);
}Čo sme týmto získali?
👍
- teraz už dokážeme načítať zoznam kníh aj z inej URL
- takže už vieme rozšíriť správanie funkcie bez dodatočnej zmeny jej kódu
3. Liskov Substitution Principle
Liskovej princíp nahraditeľnosti
Definícia
💡Pomôcka pre uvažovanie:
Potomok musí byť dobrou náhradou svojho rodiča.
Ak z nejakého kódu odoberiem rodiča a na to isté miesto dosadím jeho potomka, kód musí fungovať bez chyby.
Nemôžeme sprísňovať požiadavky na argumenty a nemôžeme rozširovať typy návratovej hodnoty.
LSP
LSP
interface Encoder {
encode: (value: string | number) => string;
}
export async function saveEncrypted(
encoder: Encoder
) {
const secrets = readRawSecrets();
const encoded: string[] = secrets.map(
(secret) => {
return encoder.encode(secret);
}
);
// ...
}🔍
Ktoré objekty sú dobrým potomkom typu Encoder?
const encoderA: Encoder {
encode(value: string | number): string {
// ...
}
}
const encoderB: Encoder {
encode(value: string): string {
// ...
}
}
const encoderC: Encoder {
encode(
value: string | number | boolean
): string {
// ...
}
}✅
✅
❌
LSP
🔍
Ktoré funkcie sú dobrým potomkom typu Encode?
type Encode = (value: string | number) => string;
function encode(value: string | number): string {
// ...
}
function encode(value: string): string {
// ...
}
function encode(
value: string | number | boolean
): string {
// ...
}
function encode(value: string | number): string | number {
// ...
}
function encode(value: string | number): "foo" | "bar" {
// ...
}✅
✅
❌
✅
❌
Totožný podpis
Sprísnený vstupný kontrakt
Zvoľnený vstupný kontrakt
Zvoľnený výstupný kontrakt
Sprísnený výstupný kontrakt
LSP
Čo sme týmto získali?
👍
- časti kódu sú navzájom ľahko vymeniteľné
- vieme jednoducho upraviť správanie výmenou modulu (triedy, objektu, funkcie)
4. Interface Segregation Principle
Princíp oddeľovania rozhraní
Definícia
Žiaden kód by nemal závisieť na niečom, čo nepoužíva.
ISP
ISP
function isUnitEngaged(unit: Unit, gameState: GameState) {
return (
gameState.engagedUnitIds.find(id => id === unit.id) !== undefined
);
}interface Unit {
id: number;
typeId: UnitTypeId;
label: string;
sideId: SideId;
position: Position;
occupiedHexes: Position[];
isInColumn: boolean;
rotation: CounterRotation;
// ...
}interface GameState {
stateName: PossibleStates;
round: number;
phase: PhaseName;
activeSide: SideId;
units: Scenario['units'];
leaders: Scenario['leaders'];
engagedUnitIds: Unit['id'][];
// ...
}Čo v skutočnosti potrebuje funkcia isUnitEngaged?
🔍
ISP
function isUnitEngaged(unit: Pick<Unit, 'id'>, gameState: Pick<GameState, 'engagedUnitIds'>) {
return (
gameState.engagedUnitIds.find(id => id === unit.id) !== undefined
);
}Čo v skutočnosti potrebuje funkcia isUnitEngaged?
🔍
Vypýtajme si iba tie property a metódy, s ktorými pracujeme
LSP
Čo sme týmto získali?
👍
- kód závisí iba na tom, čo potrebuje
- jednoduchšie testovanie
- moduly vieme ľahšie vymieňať, lebo je medzi nimi menej závislostí a tieto sú viditeľnejšie
5. Dependency Inversion Principle
Princíp obrátenia závislostí
Definícia
Vysokoúrovňové moduly by nemali importovať nič z nízkoúrovňových. Oboje by mali závisieť na abstrakciách.
Abstrakcie by nemali závisieť na detailoch. Detaily by mali závisieť na abstrakciách.
DIP
Sme zvyknutí závisieť na detailoch
import prismaClient from '@local/infrastructure/prisma/client';
async function BookDetailPage({ bookId }: { bookId: string}) {
// ...
const book = await prismaClient.book.findUnique({
where: {
id: bookId,
}
});
// ...
}DIP
- UI vrstva má priamu vedomosť o databáze
- vie s ňou priamo komunikovať
- stávajú sa tak pevne prepojené
- a nič z toho nie je dobré
Odstráňme implementačný detail
import { findBook } from '@local/infrastructure/book/repository';
async function BookDetailPage({ bookId }: { bookId: string}) {
// ...
const book = await findBook(bookId);
// ...
}DIP
- zrušili sme závislosť priamo na databáze
- repozitárová funkcia findBook skrýva svoju implementáciu
- medzi UI a databázovým konektorom tak neexistuje priame prepojenie
Skúsme ešte lepšie
type FindBookFunc = (bookId: string) => Promise<Book | undefined>;
async function BookDetailPage(
{ bookId, findBook }: { bookId: string, findBook: FindBookFunc }
) {
// ...
const book = await findBook(bookId);
// ...
}DIP
- Funkcia teraz závisí iba na type
- Typ je abstraktný
- Zmizlo aj nepriame prepojenie na databázu
Skúsme ešte lepšie
type FindBookFunc = (bookId: string) => Promise<Book | undefined>;
async function BookDetailPage(
{ bookId, findBook }: { bookId: string, findBook: FindBookFunc }
) {
// ...
const book = await findBook(bookId);
// ...
}DIP
Kde ale teraz naimportujeme repozitár a pošleme ho ako argument do BookDetail Page?
- To je starosť volajúceho kódu
- controller, funkcia "main" a pod.
- Dependency Injection Container
🤔
DIP
Čo sme týmto získali?
👍
- jednotlivé časti kódu sú izolované
- jednoduchšie testovanie
- moduly vieme ľahšie vymieňať, lebo je jednoduchšie vyrobiť implementáciu niečoho abstraktného, ako kopírovať detailné API
Záver
Čo je SOLID?
- Single Responsibility Principle
- modul sa zodpovedá iba jednému aktérovi a má iba jeden dôvod pre zmenu
- Open-Closed Principle
- modul je otvorený pre rozširovanie bez potreby zmeny svojho kódu
- Liskov Substitution Principle
- modul je dobrým potomkom svojich rodičov
- Interface Segregation Principle
- modul závisí iba na tom, čo používa
- Dependency Inversion Principle
- modul nezávisí na konkrétnostiach
Päť základných princípov dobrej softvérovej architektúry:
Výhody
- v kóde sa nám zrazu samé od seba objavujú konštrukcie známe ako Návrhové vzory
- v architektúre aplikácie vznikajú viditeľné a nezávislé vrstvy
- kód sa ľahko testuje
- moduly sa ľahko nahradzujú
- aplikácia sa ľahko udržiava a rozširuje
Pri dôslednej aplikácii všetkých princípov:
Ďakujem za pozornosť
Milan Herda
- programátor
- Alma Career
- Lead Engineer
- Vedúci "Expertného tímu pre frontend a JavaScript"
- školím a vzdelávam
- programujem webové hry
- mám rád spoločenské hry
- https://perunhq.org/
Copy of SOLID princípy a JavaScript,2026-09-13
By Milan Herda
Copy of SOLID princípy a JavaScript,2026-09-13
- 2