2026-10
Milan Herda
2026-10
Milan Herda
2026-10
Skratka, pod ktorou sa skrýva
päť základných princípov
dobrej softvérovej architektúry
Päť základných princípov = mali by sme všetci poznať
Poznáme? 😅
Koľkí z vás by vedeli kolegovi vysvetliť:
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.
Nemusíme poznať definíciu na to, aby sme niečo úspešne používali.
Avšak, keď tú definíciu poznáme, tak:
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.
Princíp jedinej zodpovednosti
Modul by mal mať jeden (a iba jeden) dôvod pre svoju zmenu.
SRP
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:
SRP
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:
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>,
);🔍
SRP
🤔
// 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>
),
};SRP
// src/main.ts
import loadTopTenBooks from './books/repo/loadTopTen';
import mountApp from './infra/fw/mountApp';
const books = loadTopTenBooks();
mountApp(books);👍
Princíp otvorenej uzavretosti
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:
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)
);
}🔍
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)
);
}🎉
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)
);
}👍
Liskovej princíp nahraditeľnosti
💡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);
}
);
// ...
}🔍
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
🔍
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
👍
Princíp oddeľovania rozhraní
Ž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'][];
// ...
}🔍
ISP
function isUnitEngaged(unit: Pick<Unit, 'id'>, gameState: Pick<GameState, 'engagedUnitIds'>) {
return (
gameState.engagedUnitIds.find(id => id === unit.id) !== undefined
);
}🔍
Vypýtajme si iba tie property a metódy, s ktorými pracujeme
LSP
👍
Princíp obrátenia závislostí
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
import prismaClient from '@local/infrastructure/prisma/client';
async function BookDetailPage({ bookId }: { bookId: string}) {
// ...
const book = await prismaClient.book.findUnique({
where: {
id: bookId,
}
});
// ...
}DIP
import { findBook } from '@local/infrastructure/book/repository';
async function BookDetailPage({ bookId }: { bookId: string}) {
// ...
const book = await findBook(bookId);
// ...
}DIP
type FindBookFunc = (bookId: string) => Promise<Book | undefined>;
async function BookDetailPage(
{ bookId, findBook }: { bookId: string, findBook: FindBookFunc }
) {
// ...
const book = await findBook(bookId);
// ...
}DIP
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?
🤔
DIP
👍
Päť základných princípov dobrej softvérovej architektúry:
Pri dôslednej aplikácii všetkých princípov: