Wzorce projektowe to sprawdzone, adaptowalne schematy, które ułatwiają projektowanie i utrzymanie kodu OOP. Ten przewodnik wyjaśnia wzorce kreacyjne, strukturalne i behawioralne oraz pokazuje praktyczne przykłady w TypeScript, które poprawią czytelność i testowalność twojego kodu2.
December 20, 2025 (8mo ago) — last updated June 10, 2026 (2mo ago)
Wzorce projektowe OOP w TypeScript — praktyczny przewodnik
Poznaj wzorce kreacyjne, strukturalne i behawioralne z praktycznymi przykładami TypeScript — lepsza architektura, łatwiejsza refaktoryzacja i większa testowalność.
← Back to blog
Wzorce projektowe OOP w TypeScript — praktyczny przewodnik
Opanuj wzorce projektowe OOP dzięki temu praktycznemu przewodnikowi. Poznaj wzorce kreacyjne, strukturalne i behawioralne z czytelnymi przykładami w TypeScript, które poprawią architekturę, ułatwią refaktoryzację i zwiększą testowalność kodu.
Wprowadzenie
Wzorce projektowe to sprawdzone, wielokrotnego użytku sposoby rozwiązywania powtarzających się problemów w programowaniu zorientowanym obiektowo. To nie jest kod do kopiowania i wklejania, lecz adaptowalne szablony pomagające strukturyzować klasy i obiekty, aby kod był łatwiejszy w utrzymaniu, rozbudowie i testowaniu. W tym przewodniku omówimy wzorce kreacyjne, strukturalne i behawioralne oraz pokażemy praktyczne przykłady w TypeScript, które szybko zastosujesz w projektach produkcyjnych2.
Czym są wzorce projektowe w OOP
Kodowanie bez wzorców może prowadzić do poplątanej bazy kodu, trudnej do skalowania i utrzymania. Wzorce projektowe działają jak przepisy mistrza kuchni: sprawdzone rozwiązania, które adaptujesz do własnego kontekstu. Znajomość podstawowych wzorców — kreacyjnych, strukturalnych i behawioralnych — przyspiesza komunikację w zespole i ułatwia bezpieczną refaktoryzację kodu legacy1.

Wspólny język programistów
Jedną z największych korzyści wzorców jest wspólne słownictwo. Wypowiedz „Factory” lub „Singleton”, a doświadczeni programiści od razu zrozumieją intencję i ogólną strukturę rozwiązania. Książka „Design Patterns: Elements of Reusable Object-Oriented Software” skatalogowała 23 kluczowe wzorce, które wciąż stanowią podstawę nauki wzorców projektowych2.
Wzorzec projektowy nie jest gotowym kodem, który można wkleić. To opis sposobu rozwiązania problemu, który można zastosować w wielu sytuacjach.
Poczucie komfortu z podstawami OOP, takimi jak polimorfizm i dziedziczenie, pomaga skutecznie stosować wzorce. Dla szybkiego odświeżenia zobacz nasz przewodnik porównujący polimorfizm vs dziedziczenie.
Trzy główne kategorie wzorców projektowych
Gang of Four podzielił wzorce na trzy kategorie: kreacyjne, strukturalne i behawioralne. Zrozumienie tych kategorii pomaga dobrać właściwe rozwiązanie do konkretnego problemu.

Przegląd kategorii
| Kategoria | Cel | Przykłady |
|---|---|---|
| Kreacyjne | Kontrolowanie tworzenia obiektów | Factory, Builder, Singleton, Prototype |
| Strukturalne | Komponowanie klas i obiektów | Adapter, Decorator, Facade, Composite |
| Behawioralne | Zarządzanie komunikacją między obiektami | Observer, Strategy, Command, Iterator |
Wzorce kreacyjne — kontrola tworzenia obiektów
Wzorce kreacyjne wprowadzają warstwę abstrakcji wokół tworzenia obiektów, dzięki czemu kod klienta nie jest ściśle związany z klasami implementującymi. Poniżej dwa praktyczne przykłady w TypeScript: Singleton i Factory Method.

Singleton — jedna instancja globalnego zasobu
Singleton gwarantuje, że klasa ma tylko jedną instancję i udostępnia globalny punkt dostępu. To przydatne dla współdzielonych zasobów, takich jak połączenie z bazą danych czy logger, ale wprowadza stan globalny, który utrudnia testowanie i zarządzanie zależnościami. Używaj Singletona tylko dla naprawdę globalnych zasobów i rozważ wstrzykiwanie zależności zamiast globalnego stanu3.
Przykład: Singleton w TypeScript dla połączenia z bazą danych.
class DatabaseConnection {
private static instance: DatabaseConnection;
private constructor() {
// Prywatny konstruktor blokuje bezpośrednie tworzenie instancji
console.log("Connecting to the database...");
}
public static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
public query(sql: string): void {
console.log(`Executing query: ${sql}`);
}
}
// Użycie
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
db1.query("SELECT * FROM users");
console.log(db1 === db2); // true
Factory Method — delegowanie decyzji tworzenia
Factory Method definiuje interfejs tworzenia obiektu, podczas gdy podklasy decydują, który konkretny produkt zainstancjonować. Oddziela to kod klienta od klas konkretnego produktu, co ułatwia rozszerzanie systemu.
Przykład: renderowanie przycisków specyficznych dla systemu operacyjnego.
interface Button {
render(): void;
onClick(f: () => void): void;
}
class WindowsButton implements Button {
render() { console.log("Rendering a button in Windows style."); }
onClick(f: () => void) { console.log("Windows button click event."); f(); }
}
class MacButton implements Button {
render() { console.log("Rendering a button in macOS style."); }
onClick(f: () => void) { console.log("Mac button click event."); f(); }
}
abstract class Dialog {
abstract createButton(): Button;
render() {
const okButton = this.createButton();
okButton.render();
}
}
class WindowsDialog extends Dialog {
createButton(): Button { return new WindowsButton(); }
}
class MacDialog extends Dialog {
createButton(): Button { return new MacButton(); }
}
// Klient
const os: string = "windows";
let dialog: Dialog;
if (os === "windows") dialog = new WindowsDialog(); else dialog = new MacDialog();
dialog.render();
Factory Method pomaga utrzymać kod twórcy niezależnym od konkretnych produktów, co ułatwia dodawanie nowych wariantów.
Wzorce strukturalne — komponowanie i adaptacja
Wzorce strukturalne ułatwiają składanie obiektów w większe, elastyczne systemy. Poniżej Adapter i Decorator jako praktyczne przykłady.

Adapter — most między niekompatybilnymi interfejsami
Adapter owija niekompatybilny interfejs, aby pasował do oczekiwań systemu. To pozwala integrować biblioteki zewnętrzne bez inwazyjnych zmian w kodzie.
Przykład: dopasowanie ModernLogger do istniejącego interfejsu ILogger.
class ModernLogger {
public logInfo(message: string): void {
console.log(`[INFO]: ${message}`);
}
}
interface ILogger { log(message: string): void; }
class LoggerAdapter implements ILogger {
private modernLogger: ModernLogger;
constructor() { this.modernLogger = new ModernLogger(); }
public log(message: string): void { this.modernLogger.logInfo(message); }
}
const logger: ILogger = new LoggerAdapter();
logger.log("User logged in successfully.");
Decorator — dodawanie funkcji w czasie wykonywania
Decorator pozwala dodawać obowiązki obiektom przez owijanie ich innymi obiektami. To bardziej elastyczne rozwiązanie niż dziedziczenie i sprzyja zasadzie pojedynczej odpowiedzialności.
Przykład: komponowanie subskrypcji z dodatkami.
interface Subscription { getDescription(): string; getCost(): number; }
class BasicSubscription implements Subscription {
getDescription(): string { return "Basic Plan"; }
getCost(): number { return 10; }
}
abstract class SubscriptionDecorator implements Subscription {
protected subscription: Subscription;
constructor(subscription: Subscription) { this.subscription = subscription; }
abstract getDescription(): string;
abstract getCost(): number;
}
class PremiumSupportDecorator extends SubscriptionDecorator {
getDescription(): string { return `${this.subscription.getDescription()}, Premium Support`; }
getCost(): number { return this.subscription.getCost() + 5; }
}
class CloudStorageDecorator extends SubscriptionDecorator {
getDescription(): string { return `${this.subscription.getDescription()}, 1TB Cloud Storage`; }
getCost(): number { return this.subscription.getCost() + 7; }
}
let mySubscription: Subscription = new BasicSubscription();
mySubscription = new PremiumSupportDecorator(mySubscription);
mySubscription = new CloudStorageDecorator(mySubscription);
console.log(mySubscription.getDescription());
console.log(mySubscription.getCost());
Takie kompozycyjne podejście utrzymuje funkcje modularne i łatwe do przetestowania.
Wzorce behawioralne — komunikacja i odpowiedzialność
Wzorce behawioralne porządkują interakcje między obiektami, dzięki czemu systemy są elastyczniejsze i łatwiejsze w utrzymaniu. Poniżej Observer i Strategy.
Observer — powiadamianie zainteresowanych
Observer tworzy relację jeden-do-wielu: gdy Subject zmienia stan, Observers są powiadamiani automatycznie. To podstawowy wzorzec dla systemów opartych na zdarzeniach.
Przykład: prosty serwis powiadomień.
interface Subject { attach(observer: Observer): void; detach(observer: Observer): void; notify(): void; }
interface Observer { update(subject: Subject): void; }
class NotificationService implements Subject {
public state: string = '';
private observers: Observer[] = [];
attach(observer: Observer): void { this.observers.push(observer); }
detach(observer: Observer): void { const i = this.observers.indexOf(observer); if (i !== -1) this.observers.splice(i, 1); }
notify(): void { for (const o of this.observers) o.update(this); }
public createNewPost(title: string): void { this.state = `New Post: ${title}`; console.log(`\nNotificationService: A new post was created.`); this.notify(); }
}
class EmailNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`EmailNotifier: Sending email about "${subject.state}"`); } }
class PushNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`PushNotifier: Sending push notification for "${subject.state}"`); } }
const notificationService = new NotificationService();
const emailer = new EmailNotifier();
const pusher = new PushNotifier();
notificationService.attach(emailer);
notificationService.attach(pusher);
notificationService.createNewPost("Understanding Observer Pattern");
notificationService.detach(pusher);
notificationService.createNewPost("Why Strategy is Awesome");
Observer daje luźne powiązania: Subjects nie muszą znać konkretnych Observers, które powiadamiają.
Strategy — enkapsulacja algorytmów
Strategy pozwala wymieniać algorytmy w czasie wykonywania, unikając rozbudowanych bloków warunkowych. Wspiera zasadę otwarte/zamknięte (Open/Closed Principle) i ułatwia dodawanie nowych zachowań bez modyfikowania istniejącego kodu4.
Przykład: strategie płatności dla koszyka zakupowego.
interface PaymentStrategy { pay(amount: number): void; }
class CreditCardStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} with Credit Card.`); } }
class PayPalStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} via PayPal.`); } }
class ShoppingCart {
private paymentStrategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
public setPaymentStrategy(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
public checkout(amount: number): void { this.paymentStrategy.pay(amount); }
}
const cart = new ShoppingCart(new CreditCardStrategy());
cart.checkout(150);
cart.setPaymentStrategy(new PayPalStrategy());
cart.checkout(150);
Strategy redukuje złożoność warunkową i poprawia testowalność.
Typowe pułapki i refaktoryzacja
Wybór wzorca to tylko część zadania. Unikaj antywzorców, takich jak God Object, który gromadzi obowiązki i narusza zasadę pojedynczej odpowiedzialności. Szukaj zapachów kodu — długich metod, nadmiernych zależności czy monstrualnych klas — i refaktoryzuj je krok po kroku.
Refaktoryzacja polega na poprawie wewnętrznej struktury bez zmiany zachowania zewnętrznego. To sposób na opanowanie systemów legacy. Dla praktycznych wskazówek sprawdź nasze zasoby o refaktoryzacji na stronie Clean Code Guy.
Przykład: refaktoryzacja do Strategy
Duży switch lub długa seria if/else to klasyczny zapach. Zrefaktoryzuj, wyodrębniając zmienne zachowanie do interfejsu Strategy i konkretnych klas strategii. Ta zmiana poprawia rozszerzalność, testowalność i zgodność z zasadami SOLID4.
Najczęściej zadawane pytania
Ile wzorców projektowych powinienem znać?
Skoncentruj się najpierw na 5–7 podstawowych wzorcach: Singleton, Factory, Adapter, Decorator, Observer i Strategy. Zrozum, jaki problem każdy z nich rozwiązuje — to pomoże szybko stosować kolejne wzorce.
Kiedy nie stosować wzorca projektowego?
Unikaj wzorców, które dodają niepotrzebnej złożoności. Nie wprowadzaj wzorca „na zapas”. Stosuj zasadę YAGNI — dodawaj rozwiązania tylko wtedy, gdy istnieje rzeczywisty problem.
Czy wzorce OOP mają sens w kodzie funkcyjnym?
Tak. Wiele idei wzorców mapuje się na techniki funkcyjne. Strategy można przekazać jako funkcję, a Decorator zrealizować jako funkcję wyższego rzędu. Kluczowe jest zrozumienie celu wzorca, nie ścisłe trzymanie się formy OOP.
W Clean Code Guy pomagamy zespołom budować oprogramowanie, które przetrwa, osadzając podstawowe zasady w ich workflow. Dowiedz się, jak nasze audyty kodu i refaktoryzacja gotowa na AI mogą pomóc twojemu zespołowi na https://cleancodeguy.com.
AI pisze kod.Ty sprawiasz, że przetrwa.
W erze przyspieszenia AI czysty kod to nie tylko dobra praktyka — to różnica między systemami, które się skalują, a bazami kodu, które zapadają się pod własnym ciężarem.