Wzorzec fabryki - kiedy naprawdę upraszcza tworzenie obiektów?

Diagram klas pokazujący implementację wzorca **factory pattern** dla tworzenia posiłków wegetariańskich i wegańskich.

Napisano przez

Jacek Zając

Opublikowano

2 kwi 2026

Spis treści

Wzorzec factory pattern pomaga odciąć tworzenie obiektów od reszty aplikacji, gdy liczba wariantów zaczyna rosnąć i kod przestaje być czytelny. W praktyce chodzi o to, żeby klient nie musiał wiedzieć, czy dostaje obiekt z `new`, z klasy pomocniczej, czy z funkcji zwracającej gotową instancję. W tym tekście pokazuję, jak ten wzorzec działa w projektach webowych, kiedy naprawdę upraszcza architekturę i gdzie łatwo przesadzić z abstrakcją.

Najważniejsze fakty o wzorcu fabryki

  • Fabryka przenosi decyzję o tym, jak powstaje obiekt, z kodu używającego go do jednego miejsca.
  • W JavaScript i TypeScript bardzo często jest to zwykła funkcja zwracająca obiekt, bez klas i bez `new`.
  • W klasycznej nomenklaturze najbliżej temu do factory method, a abstract factory tworzy całe rodziny spójnych obiektów.
  • Największy zysk to łatwiejsze testy, mniejsza liczba rozgałęzień w kodzie i prostsza podmiana implementacji.
  • Jeśli tworzenie obiektu jest proste i niezmienne, fabryka bywa zbędna.

Na czym polega wzorzec fabryki

Najkrócej: fabryka przejmuje decyzję jak powstaje obiekt, a klient pyta tylko co ma dostać. Dzięki temu reszta kodu zależy od interfejsu lub kontraktu, a nie od konkretnej klasy. To drobna zmiana w strukturze, ale bardzo ważna dla utrzymania większych aplikacji.

Ja traktuję ten wzorzec jako sposób na uporządkowanie miejsc, w których tworzenie obiektu przestało być trywialne. Czasem trzeba dobrać konfigurację, wybrać implementację na podstawie środowiska, podmienić zależność w testach albo zwrócić obiekt z cache. Fabryka pozwala schować ten wybór w jednym miejscu i nie rozlewać go po całym projekcie.

Warto też pamiętać, że taka fabryka nie musi za każdym razem tworzyć nowej instancji. Może zwrócić obiekt już istniejący, pobrany z puli albo przygotowany wcześniej wariant. To nadal pasuje do idei wzorca, bo klient dostaje gotowy produkt, a nie musi wiedzieć, skąd dokładnie się wziął.

Jeśli ten podział jest jasny, łatwiej zobaczyć, kiedy fabryka naprawdę upraszcza projekt, a kiedy tylko przenosi decyzję w inne miejsce.

Kiedy ten wzorzec naprawdę upraszcza kod

Po fabrykę sięgam wtedy, gdy widzę co najmniej dwa sygnały: kod zaczyna wybierać typ obiektu przez rozgałęzienia, a sama decyzja o tworzeniu nie jest stała. W praktyce najczęściej chodzi o takie sytuacje:

  • aplikacja obsługuje kilka wariantów tego samego obiektu, ale klient używa ich przez jeden wspólny interfejs;
  • środowisko wpływa na wybór implementacji, na przykład przeglądarka, testy, SSR albo różne tryby działania;
  • tworzenie obiektu wymaga kilku kroków inicjalizacji, a nie tylko jednego `new`;
  • chcesz podmieniać implementacje bez ruszania kodu, który z nich korzysta;
  • chcesz ograniczyć liczbę `if/else` i `switch` rozrzuconych po aplikacji.

Najprostsza zasada jest taka: jeśli obiekt ma jedną klasę, tworzenie jest banalne i nie ma szans, żeby to się zmieniło, fabryka zwykle nie wnosi wartości. Wtedy prosty konstruktor albo zwykła funkcja są po prostu czytelniejsze. Jeśli jednak zaczynasz dokładać kolejne warianty, fabryka pomaga utrzymać porządek zanim kod zamieni się w zbiór wyjątków i warunków specjalnych.

Żeby nie mylić praktycznej fabryki z jej bardziej formalnymi odmianami, trzeba rozdzielić trzy nazwy, które developerzy często wrzucają do jednego worka.

Diagram klas pokazujący implementację wzorca **factory pattern** do tworzenia posiłków: MealFactory, VegetarianMealFactory, VeganMealFactory i konkretne posiłki.

Jak odróżnić prostą fabrykę od factory method i abstract factory

W codziennej rozmowie często mówi się po prostu „fabryka”, ale to skrót myślowy. W klasycznych wzorcach GoF najczęściej chodzi o factory method albo abstract factory, a w JavaScript dochodzi jeszcze popularna factory function, czyli zwykła funkcja zwracająca obiekt. To ważne rozróżnienie, bo każde z tych podejść rozwiązuje trochę inny problem.

Odmiana Co robi Kiedy pasuje Na co uważać
Prosta fabryka / factory function Zwraca obiekt na podstawie danych wejściowych, konfiguracji albo środowiska. JS, TS, front-end, testy, adaptery, prostsze projekty. Łatwo zamienić ją w duży `switch`, który tylko ukrywa złożoność.
Factory method Pozwala podklasie zdecydować, jaki konkretny produkt zostanie utworzony. Klasyczne OOP, frameworki, hierarchie klas, gdy creator ma własną logikę biznesową. Wymaga dziedziczenia i może zwiększyć liczbę klas.
Abstract factory Tworzy spójne rodziny powiązanych obiektów. Gdy różne elementy muszą pasować do siebie, np. zestawy komponentów UI albo warianty platform. Jest cięższa w implementacji, ale pilnuje spójności całej rodziny.

W praktyce webowej najczęściej spotykam prostą fabrykę albo factory function, bo w JavaScript i TypeScript nie zawsze potrzebujesz rozbudowanej hierarchii klas. Jeśli jednak budujesz framework, warstwę infrastrukturalną albo system z wieloma wariantami zależnych od siebie obiektów, klasyczne factory method i abstract factory zaczynają mieć większy sens. Kiedy różnice są już czytelne, dużo łatwiej przełożyć je na prosty kod, który nie wprowadza zbędnej warstwy abstrakcji.

Jak wygląda to w praktyce w TypeScript

Poniżej pokazuję prostą fabrykę w aplikacji webowej, która zwraca adapter do przechowywania danych. Dzięki temu reszta kodu nie musi wiedzieć, czy pracuje z `localStorage`, `sessionStorage`, czy z pamięcią używaną w testach. To dobry przykład, bo sam mechanizm jest prosty, a zysk z centralizacji decyzji widać od razu.

type StorageKind = "local" | "session" | "memory";

interface StorageAdapter {
  get(key: string): string | null;
  set(key: string, value: string): void;
  remove(key: string): void;
}

class LocalStorageAdapter implements StorageAdapter {
  get(key: string) {
    return window.localStorage.getItem(key);
  }
  set(key: string, value: string) {
    window.localStorage.setItem(key, value);
  }
  remove(key: string) {
    window.localStorage.removeItem(key);
  }
}

class SessionStorageAdapter implements StorageAdapter {
  get(key: string) {
    return window.sessionStorage.getItem(key);
  }
  set(key: string, value: string) {
    window.sessionStorage.setItem(key, value);
  }
  remove(key: string) {
    window.sessionStorage.removeItem(key);
  }
}

class MemoryStorageAdapter implements StorageAdapter {
  private store = new Map();

  get(key: string) {
    return this.store.has(key) ? this.store.get(key)! : null;
  }
  set(key: string, value: string) {
    this.store.set(key, value);
  }
  remove(key: string) {
    this.store.delete(key);
  }
}

export function createStorage(kind: StorageKind): StorageAdapter {
  switch (kind) {
    case "local":
      return new LocalStorageAdapter();
    case "session":
      return new SessionStorageAdapter();
    default:
      return new MemoryStorageAdapter();
  }
}

const storage = createStorage(import.meta.env.MODE === "test" ? "memory" : "local");
storage.set("theme", "dark");

W tym układzie klient korzysta tylko z jednego kontraktu. Jeśli później dojdzie nowy wariant, na przykład adapter dla IndexedDB albo dla zdalnego cache, dopisujesz nową implementację i rozszerzasz jedną fabrykę, zamiast szukać miejsc, w których ktoś ręcznie wywołał konkretną klasę. W aplikacjach działających również po stronie serwera taka izolacja jest szczególnie cenna, bo pozwala odciąć kod od bezpośredniego użycia `window` tam, gdzie nie jest ono dostępne.

Sam przykład pokazuje mechanikę, ale dopiero błędy ujawniają, gdzie taki wzorzec pomaga, a gdzie zaczyna przeszkadzać.

Najczęstsze błędy i ograniczenia

Największy problem nie leży w samej idei, tylko w sposobie wdrożenia. Fabryka potrafi bardzo pomóc, ale równie łatwo można z niej zrobić dodatkową warstwę komplikacji. Najczęściej widzę pięć błędów:

  • Fabryka staje się wielkim `switch`em. Jeśli każdy nowy wariant dokłada kolejne gałęzie, a logika robi się długa i krucha, wzorzec tylko przestawia problem, zamiast go rozwiązać.
  • Fabryka miesza się z logiką biznesową. Jeżeli obiekt tworzący zaczyna liczyć ceny, walidować formularze albo podejmować decyzje domenowe, to znak, że zakres odpowiedzialności jest zbyt szeroki.
  • Dodajesz ją za wcześnie. Przy jednym prostym obiekcie fabryka jest zbędna. Zyskujesz strukturę dopiero wtedy, gdy realnie masz kilka wariantów albo zależności środowiskowych.
  • Brakuje wspólnego kontraktu. Jeśli produkty nie mają spójnego interfejsu, klient i tak będzie musiał sprawdzać typy i rozgałęzienia, a to psuje sens całego rozwiązania.
  • Mylenie z dependency injection. Fabryka wybiera i tworzy obiekt, DI zarządza dostarczaniem zależności. Te techniki mogą współpracować, ale nie są tym samym.

Warto też uczciwie powiedzieć, że koszt wykonania samej fabryki zwykle nie jest problemem. Problemem bywa złożoność poznawcza: więcej klas, więcej plików i więcej miejsc, które trzeba zrozumieć przed zmianą. Jeśli przejdziesz przez ten filtr, wybór jest zwykle prosty: albo fabryka porządkuje decyzje o tworzeniu, albo lepiej zostawić prostsze rozwiązanie.

Co zostaje z tego wzorca w codziennej pracy

Jeśli mam sprowadzić temat do jednej praktycznej reguły, to brzmi ona tak: fabryka ma sens wtedy, gdy decyzja o tworzeniu obiektu może się zmieniać, ale kod korzystający z obiektu nie powinien o tym wiedzieć. To właśnie ta separacja daje największą wartość w projektach, które rosną, szczególnie w aplikacjach webowych z wieloma środowiskami i wariantami konfiguracji.

Ja używam tego wzorca jako narzędzia porządkującego, nie jako obowiązkowego elementu architektury. Jeśli obiekt jest prosty, konstruktor wystarczy. Jeśli wariantów przybywa, a tworzenie zaczyna być „specjalnym przypadkiem”, fabryka pozwala utrzymać kod w ryzach bez przepisywania połowy aplikacji. Właśnie za to ten wzorzec jest nadal aktualny i nadal bardzo praktyczny.

FAQ - Najczęstsze pytania

Wzorzec fabryki to sposób na odseparowanie procesu tworzenia obiektów od kodu, który ich używa. Klient prosi o obiekt, nie wiedząc, jak dokładnie został on utworzony (czy z `new`, z funkcji, czy z cache). To ułatwia zarządzanie wariantami i testowanie.

Warto go użyć, gdy tworzenie obiektu przestaje być trywialne – np. wymaga wielu kroków inicjalizacji, wyboru implementacji na podstawie środowiska, obsługi wielu wariantów tego samego obiektu lub łatwej podmiany zależności w testach. Pomaga to uniknąć rozgałęzień `if/else` w kodzie.

Factory function (prosta fabryka) to zwykła funkcja zwracająca obiekt (częsta w JS/TS). Factory method pozwala podklasie decydować o tworzonym produkcie (klasyczne OOP). Abstract factory tworzy całe rodziny spójnych, powiązanych obiektów, zapewniając ich kompatybilność.

Częste błędy to: fabryka stająca się zbyt dużym `switch`em, mieszanie jej z logiką biznesową, dodawanie jej zbyt wcześnie (gdy obiekt jest prosty), brak wspólnego kontraktu dla produktów oraz mylenie jej z dependency injection. Zbyt wczesne użycie może zwiększyć złożoność projektu.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi:

factory pattern wzorzec fabryki w javascript factory pattern w typescript factory function factory method vs abstract factory kiedy używać wzorca fabryki

Udostępnij artykuł

Jacek Zając

Jacek Zając

Nazywam się Jacek Zając i od dziewięciu lat zajmuję się programowaniem webowym. Moja przygoda z tą dziedziną zaczęła się od fascynacji tworzeniem stron internetowych, co szybko przerodziło się w pasję do nauczania innych. Lubię dzielić się wiedzą i pomagać osobom, które stawiają pierwsze kroki w programowaniu. Skupiam się na wyjaśnianiu złożonych zagadnień w przystępny sposób, aby każdy mógł zrozumieć podstawy i rozwijać swoje umiejętności. W moich artykułach poruszam różnorodne tematy związane z programowaniem webowym, od HTML i CSS po JavaScript i frameworki. Dokładam wszelkich starań, aby informacje, które prezentuję, były rzetelne, aktualne i łatwe do przyswojenia. Regularnie śledzę nowinki w branży, co pozwala mi na dostarczanie czytelnikom treści zgodnych z najnowszymi trendami. Wierzę, że dobrze zorganizowana wiedza to klucz do sukcesu w karierze programisty.

Napisz komentarz