December 20, 2025 (7mo 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
Cover Image for Wzorce projektowe OOP w TypeScript — praktyczny przewodnik

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.

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.

A sketch of a chef's hat dropping golden letter shapes into an open cookbook.

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.

Diagram illustrating creational, structural, and behavioural design patterns with simple box illustrations.

Przegląd kategorii

KategoriaCelPrzykłady
KreacyjneKontrolowanie tworzenia obiektówFactory, Builder, Singleton, Prototype
StrukturalneKomponowanie klas i obiektówAdapter, Decorator, Facade, Composite
BehawioralneZarządzanie komunikacją między obiektamiObserver, 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.

An illustration contrasting individual egg-like objects with a factory assembly line producing colorful items.

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.

Hand-drawn illustrations of an electrical adapter and a stack of rings representing OOP design patterns.

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.

1.
W. G. Griswold et al., “A Formal Foundation for Design Pattern Abstraction and Reuse,” Proceedings of ECOOP ’93, University of California, San Diego. https://cseweb.ucsd.edu/~wgg/CSE210/ecoop93-patterns.pdf
2.
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). https://en.wikipedia.org/wiki/Design_Patterns
3.
Martin Fowler, “Singleton,” Bliki, omówienie kompromisów Singletona i wpływu na testowalność. https://martinfowler.com/bliki/Singleton.html
4.
Robert C. Martin (Uncle Bob), “The SOLID Principles,” omówienie zasady Open/Closed i innych celów projektowych. https://8thlight.com/blog/uncle-bob/2012/08/13/the-s-o-l-i-d-principles.html
5.
Stack Overflow, Developer Survey 2023, pokazuje szerokie zainteresowanie i adopcję TypeScript wśród programistów zawodowych. https://survey.stackoverflow.co/2023
← Back to blog
🙋🏻‍♂️

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.