Factory Method (metoda wytwórcza) to kreacyjny wzorzec projektowy, który pozwala tworzyć obiekty o wspólnych polach i metodach bez jawnego używania słowa kluczowego new: klasa-Twórca zwraca odpowiednią implementację wspólnego interfejsu. W JavaScripcie jego prostszą odmianą jest funkcja wytwórcza, czyli zwykła funkcja zwracająca obiekt. Wersja oparta na klasach jest wydajniejsza przy dużej liczbie obiektów, bo metody trafiają do prototypu, a funkcja wytwórcza tworzy nowe kopie metod dla każdego obiektu.
Metoda wytwórcza (ang. Factory Method), nazywana też wzorcem fabryka (ang. factory), to kreacyjny wzorzec projektowy. Dzięki niej możemy w wygodny sposób tworzyć obiekty o wspólnych cechach z pominięciem jawnego używania słowa kluczowego new. Za pomocą interfejsu określamy wspólne pola i metody. Następnie implementujemy go i dzięki klasie bazowej tworzymy „potomków”, czyli obiekty o wspólnych polach i metodach. Brzmi skomplikowanie? Wcale nie musi takie być!
JavaScript nie powstał jako język obiektowy, co oznacza, że możemy tworzyć nowe obiekty jako rezultat działania funkcji. Dlatego w JS metoda wytwórcza jest dużo prostsza do zastosowania niż w innych językach. Nosi nawet zmienioną nazwę: funkcja wytwórcza. Poznamy ją, a gdy poczujemy się pewnie w jej użytkowaniu, przejdziemy do metody wytwórczej, porównamy obie wersje i sprawdzimy, czym Factory Method różni się od fabryki abstrakcyjnej.
Kiedy stosować wzorzec projektowy Factory Method?
Dzięki metodzie wytwórczej możemy wygodnie tworzyć obiekty o wspólnych cechach. Na myśl przychodzi szereg zastosowań, m.in.:
- Budowanie kolekcji elementów, na których planujemy podobne działania:
- frameworki UI, m.in. Material UI, Bootstrap,
- biblioteki programistyczne, np. Redux, React-dnd.
- Tworzenie obiektów, które przynależą do różnych grup, ale mają wspólne pola lub metody:
- e-commerce, gdzie możemy sprzedawać i dostarczać np. usługę, subskrypcję lub konkretną rzecz,
- gry, w których postacie czy przedmioty mają podobną logikę działania (np. szachy).
Po wzorzec fabryka warto sięgnąć, gdy w Twoim kodzie pojawia się kilka z tych sygnałów:
| Sygnał w kodzie | Dlaczego fabryka pomaga |
|---|---|
| Nie wiesz z góry, jakiego typu obiekt będzie potrzebny (decyduje o tym np. dana z API lub wybór użytkownika) | Decyzję o typie podejmuje jedno miejsce, a reszta kodu dostaje gotowy obiekt |
| Wiele typów obiektów ma te same metody | Kod, który z nich korzysta, nie musi znać konkretnej klasy |
Te same wywołania new z warunkami powtarzają się w wielu plikach | Logika tworzenia trafia w jedno miejsce i łatwiej ją zmienić |
| Spodziewasz się, że dojdą nowe typy obiektów | Nowy typ dodajesz w fabryce, a nie w każdym miejscu użycia |
Jeśli tworzysz tylko jeden rodzaj obiektu i nic nie wskazuje, że to się zmieni, zwykły konstruktor lub klasa w zupełności wystarczą. Wzorzec dodany „na zapas” tylko utrudnia czytanie kodu.
Jak wygląda wzorzec fabryka w praktyce? Przykład z gry RPG
Wyobraź sobie, że tworzymy webową grę RPG. Występują w niej różne postacie, w tym kontrolowane przez gracza i przez komputer. Mimo że będą miały odmienną logikę działania, to jednak niektóre z ich cech będą wspólne. Co więcej, nie jesteśmy jeszcze pewni, o jakie funkcje rozszerzymy konkretne klasy postaci. Dobrym pomysłem będzie zastosowanie funkcji wytwórczej, by stworzyć dwie pierwsze postacie w naszej grze. Spójrz na poniższy przykład.
// CharacterFactory to funkcja wytwórcza
import { characterFactory } from '../factory-function/characterFactory.js';
const humanCharacter = characterFactory({
type: 'human',
level: 0,
money: 0,
health: 100
});
const shopNPC = characterFactory({
type: 'shop',
money: 100,
health: null
});
// dzięki wspólnym cechom obiektów możemy je grupować
const characters = [humanCharacter, shopNPC];
// możemy użyć tej samej metody
humanCharacter.renderOnMap({
x: 0,
y: 0
});
// możemy użyć tej samej metody
shopNPC.renderOnMap({
x: 10,
y: 10
});
// możemy wykorzystać fakt, że metody są współdzielone
for (const character of characters) {
character.renderOnMap({
x: Math.random() * 10,
y: Math.random() * 10
});
}
Powyższy przykład pokazuje największe korzyści wynikające z użycia funkcji wytwórczej. Na początku importujemy naszą funkcję wytwórczą. Ta funkcja zwraca dwie nowe postacie (humanCharacter oraz shopNPC). Dzięki zastosowaniu wzorca otrzymaliśmy obiekty postaci o wspólnej metodzie nazwanej renderOnMap. Nie musimy zagłębiać się w szczegóły implementacji postaci, by pokazać je na mapie. Wiemy, że dzielą tę samą metodę, i możemy bezpiecznie jej użyć. Bardzo wygodne! Przejdźmy do szczegółów implementacji funkcji wytwórczej.
Czym jest funkcja wytwórcza (factory function) w JavaScript?
Aby stworzyć funkcję wytwórczą, wystarczy nam… funkcja zwracająca obiekt. Stwórzmy ją.
export const characterFactory = characterObject => ({
...characterObject,
location: { x: 0, y: 0 },
renderOnMap: function (location) {
this.location = location;
console.log(this);
}
});
Funkcja characterFactory przyjmuje tylko jeden parametr: obiekt, na podstawie którego stworzy postać w grze. Dzięki użyciu funkcji strzałkowej mogę pominąć słowo kluczowe return (tzw. implicit return) i od razu zwrócić obiekt postaci. Za pomocą operatora rozproszenia (...characterObject) do nowo tworzonego obiektu przypiszę właściwości i metody obiektu, który przekazuję w parametrze funkcji. Dodam nowe pole location oraz metodę renderOnMap. Od tego momentu wszystkie obiekty stworzone na podstawie funkcji wytwórczej będą dzielić nazwy elementów (funkcji i zmiennych).
Zauważ, że tworząc kolejne obiekty, tworzysz również kolejne funkcje. Może mieć to wpływ na wydajność programu, jeśli obiektów są setki czy tysiące. Wydajność tworzenia nowych obiektów znacznie różni się między funkcją wytwórczą a metodą wytwórczą (o wydajności piszę trochę niżej). Działający kod z przykładu możesz podejrzeć w tym repozytorium. Przejdźmy do metody!
Jak zaimplementować metodę wytwórczą w JavaScript krok po kroku?
JavaScript to język, który daje dużą swobodę programiście. Napisaliśmy działający kod funkcyjny, który tworzy postać w grze. Teraz spróbujmy zrobić to samo, ale korzystając z programowania obiektowego (OOP). Do dzieła!
Krok 1: interfejs wspólny dla wszystkich postaci
Na samym początku napiszmy interfejs Character, który będzie definiował wspólne pola (location i renderOnMap) dla wszystkich obiektów stworzonych za pomocą metody wytwórczej. O tym, że w JS interfejs to nazwa umowna, przeczytasz we fragmencie o hermetyzacji w artykule „4 filary programowania obiektowego”.
export class CharacterInterface {
constructor() {
this.location = { x: 0, y: 0 };
}
renderOnMap(location) {
throw new Error('renderOnMap not implemented');
}
}
Krok 2: klasy implementujące interfejs
Teraz możemy stworzyć dwie klasy: Player oraz NPC, które będą implementować ten sam interfejs.
// Player.js
import { CharacterInterface } from '../interfaces/CharacterInterface.js';
export class Player extends CharacterInterface {
renderOnMap(location) {
this.location = location;
console.log(this);
}
// inne metody charakterystyczne dla gracza
move(direction) {
// ...
}
}
// NPC.js
import { CharacterInterface } from '../interfaces/CharacterInterface.js';
export class NPC extends CharacterInterface {
renderOnMap(location) {
this.location = location;
console.log(this);
}
// inne metody charakterystyczne dla NPC
sell(item) {
// ...
}
}
Krok 3: klasa-Twórca z metodą wytwórczą
Teraz napiszemy kod Twórcy, czyli klasy, która posiada metodę wytwórczą. Zobacz przykład poniżej.
import { NPC } from './NPC.js';
import { Player } from './Player.js';
export class CharacterCreator {
create(characterType) {
switch (characterType) {
case 'player':
return new Player();
case 'npc':
return new NPC();
default:
throw new Error('Unknown character type');
}
}
}
Krok 4: tworzenie postaci przez metodę wytwórczą
Poskładajmy wszystko w spójną całość i stwórzmy dwie nowe postacie.
import { CharacterCreator } from './classes/CharacterCreator.js';
// inicjuję klasę-Twórcę
const creator = new CharacterCreator();
// korzystam z metody wytwórczej
const humanCharacter = creator.create('player');
// korzystam z metody wytwórczej
const shopNPC = creator.create('npc');
// używam wspólnej metody
shopNPC.renderOnMap({
x: 10,
y: 10
});
// używam wspólnej metody
humanCharacter.renderOnMap({
x: 0,
y: 0
});
Na samym początku importuję i inicjuję klasę-Twórcę, czyli CharacterCreator. Następnie korzystam z metody wytwórczej create, dzięki której otrzymuję dwie nowe postacie: humanCharacter i shopNPC. Korzystają one z tego samego interfejsu Character, co pozwala mi bez żadnych obaw używać wspólnej metody renderOnMap. Kod z przykładu znajdziesz w tym repozytorium.
Funkcja wytwórcza czy metoda wytwórcza: co jest wydajniejsze?
Z artykułu na temat OOP wiesz, że klasy w JS to nic innego jak lukier składniowy na funkcje-konstruktory. Nowo utworzone metody w klasie przenoszone są niejawnie do prototypu danego obiektu. Zatem metoda w klasie zajmuje tylko „jedno” miejsce w pamięci. Dzięki temu możemy mieć setki obiektów korzystających z tej samej metody. Takie zachowanie obserwujemy w metodzie wytwórczej.
Sytuacja ma się odwrotnie w funkcji wytwórczej. Metody zadeklarowane są bezpośrednio w obiekcie (a nie w prototypie). Oznacza to, że za każdym razem, gdy tworzymy nowy obiekt za pomocą funkcji wytwórczej, tworzymy kolejne instancje metod, które zajmują miejsce w pamięci. Nie jest to rozwiązanie optymalne. Warto mieć to na uwadze podczas tworzenia aplikacji, które wykorzystują dużo obiektów i muszą działać szybko (np. gry webowe).
Wydajność to nie jedyna różnica. Tak wypadają obie wersje w bezpośrednim porównaniu:
| Cecha | Funkcja wytwórcza | Klasa / metoda wytwórcza |
|---|---|---|
| Tworzenie obiektu | Zwykłe wywołanie funkcji, bez new | new (ukryte wewnątrz Twórcy) |
| Metody w pamięci | Osobna kopia dla każdego obiektu | Jedna kopia w prototypie |
instanceof | Nie działa (obiekt nie ma własnej klasy) | Działa, np. humanCharacter instanceof Player |
| Prywatne dane | Przez domknięcia (zmienne w funkcji) | Przez pola prywatne #pole |
this | Łatwo je zgubić, np. przekazując metodę jako callback | To samo ryzyko, ten sam sposób naprawy (bind lub funkcja strzałkowa) |
| Kiedy wybrać | Mało obiektów, prosty kod, programowanie funkcyjne | Dużo obiektów, rozbudowana hierarchia typów |
Jak zmniejszyć zużycie pamięci w funkcji wytwórczej?
Funkcja wytwórcza nie musi tworzyć nowej kopii metod dla każdego obiektu. Wystarczy przenieść metody do jednego wspólnego obiektu i użyć go jako prototypu przez Object.create. Dzięki temu zachowujesz prostotę funkcji wytwórczej i oszczędzasz pamięć tak samo jak przy klasach:
const characterMethods = {
renderOnMap(location) {
this.location = location;
console.log(this);
}
};
export const characterFactory = characterObject =>
Object.assign(Object.create(characterMethods), {
...characterObject,
location: { x: 0, y: 0 }
});
const humanCharacter = characterFactory({ type: 'human' });
const shopNPC = characterFactory({ type: 'shop' });
// obie postacie korzystają z tej samej funkcji w pamięci
console.log(humanCharacter.renderOnMap === shopNPC.renderOnMap); // true
W pierwszej wersji funkcji wytwórczej to samo porównanie zwróciłoby false, bo każda postać miała własną kopię renderOnMap. Ta technika przydaje się, gdy lubisz styl funkcyjny, ale tworzysz setki lub tysiące obiektów, np. pocisków czy przeciwników w grze.
Czym różni się Factory Method od Simple Factory i fabryki abstrakcyjnej?
Nazwa „fabryka” pojawia się w trzech różnych rozwiązaniach i łatwo je pomylić:
| Rozwiązanie | Co robi | Przykład |
|---|---|---|
| Simple Factory (prosta fabryka) | Jedna funkcja lub klasa z warunkiem (if / switch) tworzy obiekty różnych typów. To raczej popularny idiom niż wzorzec z katalogu GoF. | Funkcja createCharacter(type) |
| Factory Method (metoda wytwórcza) | Tworzenie obiektu odbywa się w metodzie Twórcy, którą podklasy mogą nadpisać, żeby zwracać inne produkty. | CharacterCreator.create() i jego podklasy |
| Abstract Factory (fabryka abstrakcyjna) | Obiekt tworzący całą rodzinę powiązanych obiektów, które muszą do siebie pasować. | Fabryka motywu, która tworzy przycisk, pole tekstowe i okno w jednym stylu (np. jasnym albo ciemnym) |
Klasa CharacterCreator z tego artykułu to tzw. parametryzowana metoda wytwórcza: jedna metoda create wybiera klasę na podstawie parametru. W klasycznym ujęciu z książki „Gang of Four” każdy rodzaj Twórcy jest osobną podklasą, która nadpisuje metodę wytwórczą (np. PlayerCreator i NPCCreator). Obie wersje rozwiązują ten sam problem: kod korzystający z postaci nie musi wiedzieć, jaka klasa stoi za obiektem.
Fabrykę abstrakcyjną wybierz dopiero wtedy, gdy tworzysz kilka obiektów, które muszą być ze sobą spójne. Dla jednego rodzaju produktu wystarczy metoda wytwórcza. Jeśli piszesz w Pythonie, zobacz też wzorzec Factory w Pythonie, a jeśli interesują Cię inne wzorce projektowe w JavaScript, przeczytaj o wzorcu Proxy (pełnomocnik).
Jak wygląda diagram UML wzorca Factory Method?
Gdy poznałeś już funkcję oraz metodę wytwórczą, zaprezentuję Ci ich diagram UML. Stosując go, możesz w prosty sposób zaimplementować własną wersję wzorca!

Podsumowanie
Właśnie poznałeś prosty i bezpieczny sposób na tworzenie kolekcji obiektów, które dzielą pola i metody. Poznanie funkcji wytwórczej pozwoli Ci tworzyć kod jeszcze sprawniej i czytelniej, a metoda wytwórcza sprawdzi się tam, gdzie obiektów jest dużo i liczy się wydajność. Warto stosować ten wzorzec szczególnie w początkowej fazie projektu, gdy tworzysz dużo nowych funkcjonalności o wspólnych cechach. Pomyśl tylko, jak łatwo rozbudujesz swoje obiekty o dodatkową logikę!
Źródła
- Aleksander Shvets, Wzorce projektowe. Nowoczesny podręcznik, Refactoring.Guru, 2022
- Dokumentacja UML w wersji 2.4
- Factory Functions - Programowanie funkcyjne w JavaScript
- https://github.com/mateuszjansowa/article-patterns-js-factory-method


