Wzorzec Factory w C# - kiedy warto go stosować?

Bruno Krawczyk • 6 sierpnia 2026
Diagram klas C# ilustrujący wzorzec **factory pattern** do tworzenia posiłków wegetariańskich i wegańskich.

Spis treści

Gdy aplikacja obsługuje kilka kanałów płatności, formatów raportów albo dostawców AI, szybko pojawia się pytanie, kto ma zdecydować, jaki obiekt utworzyć. Angielskie określenie c# factory pattern opisuje właśnie sposób przeniesienia tej decyzji poza kod biznesowy. Pokażę, jak zaimplementować wzorzec Factory w C#, czym różni się Simple Factory od Factory Method i Abstract Factory oraz jak połączyć fabrykę z dependency injection w .NET.

Fabryka porządkuje wybór implementacji i upraszcza rozwój aplikacji

  • Oddziel decyzję o wyborze klasy od kodu, który korzysta z obiektu.
  • Simple Factory sprawdza się przy prostym wyborze jednej implementacji.
  • Factory Method przenosi tworzenie obiektu do klas pochodnych.
  • Abstract Factory tworzy całe rodziny powiązanych obiektów.
  • Dependency injection ogranicza ręczne używanie new i ułatwia testowanie.

Schemat c# factory pattern: abstrakcyjna fabryka tworzy obiekty w kształcie kul i piramid.

Po co wydzielać tworzenie obiektów

Najprostszy kod często zaczyna się od instrukcji new umieszczonej dokładnie tam, gdzie obiekt jest potrzebny. Problem pojawia się wtedy, gdy jedna część aplikacji musi obsługiwać kilka implementacji tego samego interfejsu, a wybór zależy od konfiguracji, typu żądania lub danych użytkownika.

Bez fabryki warunki wyboru szybko rozlewają się po kontrolerach, serwisach i handlerach. Każda zmiana, na przykład dodanie eksportu XML, wymaga znalezienia wielu miejsc, w których występuje podobny if albo switch. Fabryka zbiera tę wiedzę w jednym miejscu, a reszta aplikacji pracuje na abstrakcji.

W praktyce zyskujemy trzy rzeczy. Po pierwsze, klient nie zna klas konkretnych. Po drugie, konstrukcja obiektu może obejmować walidację i dodatkowe zależności. Po trzecie, test może otrzymać prostą atrapę zamiast prawdziwego klienta API, generatora plików czy dostawcy płatności.

Nie traktuję jednak fabryki jako obowiązkowej warstwy przed każdym konstruktorem. Jeśli klasa ma prosty konstruktor, jedną implementację i nie ma żadnej decyzji do ukrycia, dodatkowa fabryka będzie tylko dekoracją.

Trzy odmiany fabryki, które łatwo pomylić

W projektach C# słowo „fabryka” bywa używane dla kilku różnych rozwiązań. To nie jest wyłącznie kwestia nazewnictwa, ponieważ każda odmiana rozwiązuje inny problem z tworzeniem obiektów.

Wariant Co tworzy Kiedy ma sens
Simple Factory Jeden obiekt wybrany na podstawie parametru Mała liczba wariantów i prosta reguła wyboru
Factory Method Obiekt tworzony przez klasę pochodną Gdy algorytm jest wspólny, ale produkt zależy od podklasy
Abstract Factory Rodzinę kompatybilnych obiektów Gdy kilka produktów musi pochodzić z tego samego wariantu

Simple Factory

Simple Factory to zwykle zwykła klasa z metodą Create. Nie jest osobnym wzorcem z katalogu GoF, ale w codziennym kodzie .NET jest bardzo praktycznym sposobem na schowanie prostego wyboru implementacji.

public enum ReportFormat
{
    Csv,
    Json
}

public interface IReportExporter
{
    string Export(Report report);
}

public sealed class ReportExporterFactory
{
    public IReportExporter Create(ReportFormat format) => format switch
    {
        ReportFormat.Csv => new CsvReportExporter(),
        ReportFormat.Json => new JsonReportExporter(),
        _ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
    };
}

Ten wariant jest czytelny, dopóki liczba formatów pozostaje niewielka. Jeśli fabryka zaczyna zawierać kilkadziesiąt warunków, a każda implementacja wymaga wielu zależności, przeniesienie instrukcji switch do osobnej klasy nie rozwiązuje problemu. Wtedy lepiej użyć rejestru obiektów albo konfiguracji DI.

Factory Method

W Factory Method klasa bazowa definiuje ogólny przebieg działania, ale pozostawia klasom pochodnym decyzję, jaki produkt utworzyć. To dobre rozwiązanie, gdy algorytm jest stały, a tylko jeden etap zależy od konkretnego typu.

public abstract class DocumentProcessor
{
    public void Process(string content)
    {
        var parser = CreateParser();
        var document = parser.Parse(content);

        Save(document);
    }

    protected abstract IDocumentParser CreateParser();

    private static void Save(Document document)
    {
        Console.WriteLine($"Zapisano dokument: {document.Title}");
    }
}

public sealed class CsvDocumentProcessor : DocumentProcessor
{
    protected override IDocumentParser CreateParser()
    {
        return new CsvDocumentParser();
    }
}

Klasa bazowa nie musi znać szczegółów parsera CSV, XML czy JSON. Wywołuje metodę fabrykującą, a podklasa dostarcza właściwą implementację. Ten wariant bywa jednak cięższy niż zwykła fabryka, dlatego sięgam po niego głównie wtedy, gdy rzeczywiście istnieje wspólny szablon procesu.

Przeczytaj również: Mikroserwisy w .NET bez chaosu - granice, wzorce i wdrożenie

Abstract Factory

Abstract Factory przydaje się wtedy, gdy nie tworzysz jednego obiektu, tylko zestaw obiektów, które powinny do siebie pasować. Dobrym przykładem może być interfejs użytkownika dla różnych platform albo zestaw usług płatniczych jednego dostawcy.

public interface IPaymentFactory
{
    IPaymentProcessor CreateProcessor();
    IRefundProcessor CreateRefundProcessor();
}

public sealed class StripePaymentFactory : IPaymentFactory
{
    public IPaymentProcessor CreateProcessor()
    {
        return new StripePaymentProcessor();
    }

    public IRefundProcessor CreateRefundProcessor()
    {
        return new StripeRefundProcessor();
    }
}

public sealed class CheckoutService
{
    private readonly IPaymentFactory _factory;

    public CheckoutService(IPaymentFactory factory)
    {
        _factory = factory;
    }

    public void Process(decimal amount)
    {
        var processor = _factory.CreateProcessor();
        processor.Charge(amount);
    }
}

Największą wartością nie jest samo ukrycie operatora new. Chodzi o to, aby kod nie połączył przypadkiem procesora Stripe z mechanizmem zwrotów PayPal. Fabryka abstrakcyjna pilnuje spójności całej rodziny produktów.

Praktyczna implementacja fabryki w C#

Załóżmy, że aplikacja generuje raporty w formacie CSV i JSON. Kod biznesowy powinien powiedzieć „potrzebuję eksportera JSON”, ale nie powinien wiedzieć, jak ten eksporter jest zbudowany ani jakie biblioteki wykorzystuje.

using System.Text.Json;

public sealed record Report(string Title, IReadOnlyList Rows);

public enum ExportFormat
{
    Csv,
    Json
}

public interface IReportExporter
{
    ExportFormat Format { get; }
    string Export(Report report);
}

public sealed class CsvReportExporter : IReportExporter
{
    public ExportFormat Format => ExportFormat.Csv;

    public string Export(Report report)
    {
        return string.Join(Environment.NewLine, report.Rows);
    }
}

public sealed class JsonReportExporter : IReportExporter
{
    public ExportFormat Format => ExportFormat.Json;

    public string Export(Report report)
    {
        return JsonSerializer.Serialize(report);
    }
}

public sealed class ReportExporterFactory
{
    private readonly IReadOnlyDictionary _exporters;

    public ReportExporterFactory(IEnumerable exporters)
    {
        _exporters = exporters.ToDictionary(exporter => exporter.Format);
    }

    public IReportExporter Create(ExportFormat format)
    {
        if (_exporters.TryGetValue(format, out var exporter))
        {
            return exporter;
        }

        throw new ArgumentOutOfRangeException(nameof(format), format, "Nieobsługiwany format.");
    }
}

W tym przykładzie fabryka nie zna klas konkretnych w swoim kodzie. Otrzymuje kolekcję implementacji i buduje mapę na podstawie właściwości Format. Dzięki temu dodanie nowego eksportera zwykle polega na dopisaniu klasy i rejestracji, a nie na rozbudowywaniu wielkiego switch.

Użycie jest proste:

var factory = new ReportExporterFactory(
    new IReportExporter[]
    {
        new CsvReportExporter(),
        new JsonReportExporter()
    });

var report = new Report("Sprzedaż", new[] { "styczeń", "luty" });
var exporter = factory.Create(ExportFormat.Json);

var output = exporter.Export(report);

Metoda Create zwraca interfejs, więc kod wywołujący nie musi znać typu JsonReportExporter. To drobna różnica w kodzie, ale duża różnica w architekturze, gdy implementacje zaczynają mieć własne zależności, konfigurację i zasady walidacji.

Jak połączyć fabrykę z dependency injection

W aplikacji ASP.NET Core ręczne tworzenie eksporterów nie jest już potrzebne. Wbudowany kontener DI może zarejestrować wiele implementacji tego samego interfejsu, a fabryka otrzyma je przez IEnumerable.

services.AddTransient();
services.AddTransient();
services.AddTransient();

Taki układ dobrze działa, gdy wybór zależy od danych przychodzących w żądaniu. Fabryka pozostaje miejscem, w którym opisujemy regułę wyboru, a kontener DI zajmuje się budowaniem zależności.

Jeżeli konstruktor implementacji wymaga konfiguracji, można użyć fabryki delegatowej podczas rejestracji:

services.AddTransient(serviceProvider =>
{
    var options = serviceProvider
        .GetRequiredService>();

    return new JsonReportExporter(options.Value);
});

Nie polecam wstrzykiwania IServiceProvider do każdej klasy tylko po to, aby pobierać z niego zależności w metodzie biznesowej. To szybko zamienia fabrykę w Service Locator, czyli ukryty mechanizm rozwiązywania zależności. Lepiej pobrać potrzebne implementacje w konstruktorze albo zamknąć logikę w jasno nazwanej fabryce.

W większym systemie fabryka dobrze współgra z architekturą heksagonalną w .NET. Interfejs eksportera może być portem aplikacji, a klasy CSV i JSON adapterami technicznymi. Dzięki temu logika biznesowa nie zależy bezpośrednio od biblioteki do serializacji ani od szczegółów zapisu pliku.

W .NET 8 i nowszych można także użyć usług kluczowanych, gdy wybór sprowadza się do prostego klucza, na przykład "csv" lub "json". Przy bardziej rozbudowanych regułach jawna fabryka zazwyczaj lepiej pokazuje intencję i łatwiej ją przetestować.

Kiedy fabryka pomaga, a kiedy komplikuje kod

Najlepszy moment na wprowadzenie fabryki pojawia się wtedy, gdy tworzenie obiektu zaczyna być decyzją architektoniczną, a nie zwykłym wywołaniem konstruktora. W moich projektach najczęściej ma to miejsce w czterech sytuacjach.

  • Istnieje kilka implementacji jednego kontraktu, a wybór następuje w czasie działania programu.
  • Konstrukcja obiektu wymaga konfiguracji, walidacji lub kilku zależności.
  • Wiele miejsc w aplikacji powtarza ten sam warunek wyboru.
  • Trzeba pilnować, aby kilka obiektów pochodziło z jednej kompatybilnej rodziny.

Fabryka nie jest dobrym wyborem, gdy tworzy jedną klasę i tylko przekazuje parametry do jej konstruktora. W takim przypadku bezpośrednie użycie new jest często czytelniejsze i tańsze w utrzymaniu.

Trzeba też uważać na fabrykę, która staje się centralnym magazynem wszystkich klas w systemie. Jeżeli jedna metoda zna kilkanaście niezwiązanych ze sobą typów, problemem nie jest brak kolejnego warunku, tylko zbyt szeroka odpowiedzialność. Lepiej podzielić ją według obszarów biznesowych albo wykorzystać rejestrację implementacji w DI.

Przydatne jest również rozróżnienie fabryki od pozostałych narzędzi architektonicznych. Szerszy przegląd znajdziesz w materiale o wzorcach projektowych w .NET, ale w praktyce najważniejsze pytanie brzmi prosto: czy ukrywam zmienną decyzję, czy tylko dokładam klasę bez realnej korzyści?

Fabryka powinna upraszczać decyzję, nie ją ukrywać

Najzdrowsza implementacja wzorca Factory ma jasno określoną odpowiedzialność. Wie, jak wybrać właściwą implementację, ale nie przejmuje logiki biznesowej produktu, obsługi całego procesu ani zarządzania cyklem życia wszystkich usług.

  • Zwracaj interfejs lub klasę abstrakcyjną, jeśli klient nie musi znać typu konkretnego.
  • Obsłuż nieznany wariant kontrolowanym wyjątkiem lub walidacją konfiguracji.
  • Testuj osobno regułę wyboru i każdą implementację produktu.
  • Przy rosnącej liczbie wariantów rozważ rejestr obiektów oraz dependency injection.

Jeśli wybór jest prosty, zacznij od małej fabryki. Jeśli tworzysz kompatybilne zestawy obiektów, wybierz Abstract Factory. Najważniejsze jest nie samo użycie nazwy wzorca, lecz usunięcie zmienności z miejsca, które powinno zajmować się pracą biznesową.

FAQ - Najczęstsze pytania

Fabryka pomaga, gdy aplikacja ma kilka implementacji jednego interfejsu, a wybór zależy od konfiguracji, typu żądania lub danych użytkownika. Warto jej użyć także wtedy, gdy tworzenie obiektu wymaga walidacji, konfiguracji albo wielu zależności.

Simple Factory wybiera jeden obiekt na podstawie parametru. Factory Method przenosi tworzenie produktu do klas pochodnych przy zachowaniu wspólnego algorytmu, a Abstract Factory tworzy rodziny kompatybilnych obiektów, takich jak procesor płatności i mechanizm zwrotów tego samego dostawcy.

Zarejestruj każdą implementację IReportExporter, na przykład CsvReportExporter i JsonReportExporter, oraz samą ReportExporterFactory. Fabryka może otrzymać wszystkie implementacje przez IEnumerable<IReportExporter>, zbudować mapę według właściwości Format i zwracać właściwy eksporter.

Dodatkowa fabryka jest zbędna, gdy klasa ma prosty konstruktor, istnieje tylko jedna implementacja i nie trzeba ukrywać żadnej decyzji. Problem pojawia się także wtedy, gdy jedna fabryka zna wiele niezwiązanych typów, ponieważ może stać się centralnym magazynem klas i mieć zbyt szeroką odpowiedzialność.

Oceń artykuł

Ocena: 4.63 Liczba głosów: 8

Tagi

fabryka
factory method
abstract factory
dependency injection
asp.net core
Autor Bruno Krawczyk
Bruno Krawczyk
Mam na imię Bruno i od 8 lat zgłębiam tajniki programowania w ekosystemie .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z technologią zaczęła się od ciekawości, jak złożone systemy mogą ułatwiać codzienne życie i rozwiązywać realne problemy. Dziś moją misją jest dzielenie się tą wiedzą, starając się przybliżyć nawet najbardziej skomplikowane zagadnienia w sposób zrozumiały i przystępny dla każdego. W moich artykułach na kursdotnet.pl skupiam się na praktycznych aspektach, analizuję najnowsze trendy i weryfikuję informacje, aby dostarczyć Wam treści, które są nie tylko dokładne i aktualne, ale przede wszystkim użyteczne w Waszej własnej ścieżce rozwoju technologicznego.

Udostępnij artykuł

Napisz komentarz

Komentarze

2
WI

Wieslaw_B

no w sumie fajnie ze ktos to zebral do kupy bo w sumie te factory to niby proste ale jak sie tak glebiej zastanowic to mozna sie pogubic w tych wszystkich simple factory method i abstract factory nwm czy kazdy to ogarnia tak od razu wiec taki artykul to spoko sprawa zeby to sobie poukladac w glowie zwlaszcza ze potem dochodzi jeszcze to dependency injection i wgl to juz sie robi troche wiecej do ogarniecia ale jak sie to dobrze zrobi to potem kod jest duzo czystszy i latwiejszy do testowania co jest mega wazne w sumie to ja sam czesto sie zastanawiam kiedy uzyc ktorej factory bo czasem to sie wydaje ze mozna by prosciej ale potem wychodzi ze jednak nie no ale wazne zeby myslec o tym zeby nie robic syfu w kodzie nie? 👍

Bruno Krawczyk
Bruno KrawczykAutor

Cieszę się, że pomogłem uporządkować temat! 👍

NA

NataszaWrites

Ojejku, to mi się przypomniało, jak kiedyś u mojej sąsiadki, co to miała taki stary telewizor, wiecie, ten, co to miał z tyłu taką wielką dupę, no to jej się zepsuł i ona do mnie przychodzi, cała w nerwach, że co teraz, bo przecież ona bez telewizora to jak bez ręki! No i ja jej mówię, spokojnie, pani Krystyno, zobaczymy, co tam się stało. A to mi się od razu skojarzyło z tymi wzorcami, co to pan o nich pisze, bo przecież w życiu też tak jest, że czasem trzeba coś naprawić, a nie wiadomo, od czego zacząć, prawda? I wtedy taki plan, taki wzorzec, to jest jak złoto! A tu proszę, Factory Method, Abstract Factory, wszystko ładnie rozpisane, kiedy co stosować. No bo przecież nie do wszystkiego pasuje to samo narzędzie, tak jak do tego telewizora sąsiadki nie pasował młotek, tylko trzeba było delikatnie, z wyczuciem. Bardzo mi się podoba, jak pan to wszystko wytłumaczył, bo ja to zawsze miałam problem z tymi abstrakcjami, a tu proszę, wszystko jasne. I to z tym Dependency Injection, to też bardzo mądre, żeby nie robić wszystkiego na siłę, tylko dać się temu życiu, czy tam programowi, samemu decydować, co mu lepiej pasuje. No, super! 🙂