C# Factory Pattern w praktyce
Chcesz tworzyć bohaterów i potwory w grze RPG jedną linijką kodu?
W tej części kursu pokażę Ci, jak zastosować wzorzec projektowy Factory w C#, aby centralnie zarządzać procesem generowania postaci. Nauczysz się wczytywać dane z plików TXT i JSON, oddzielać logikę biznesową od logiki tworzenia obiektów oraz pisać kod, który łatwo rozbudujesz bez kodowego spaghetti.
📌 Zobacz też: C# Klasy i Obiekty – Gra RPG cz.1 | C# System Walki – Gra RPG cz.2 | Dziedziczenie i Polimorfizm w Grze RPG [Część 3]
🧠 Dlaczego warto używać wzorca Factory w C#?
Główne zalety:
- Czysty kod – logika tworzenia obiektów jest w jednym miejscu.
- Elastyczność – łatwo dodajesz nowe typy postaci bez ruszania istniejącego kodu.
- Polimorfizm – kod gry operuje na interfejsach lub klasach bazowych, nie na szczegółach implementacji.
🏗 Tworzymy interfejs fabryki
Najpierw definiujemy interfejs, który określa kontrakt tworzenia postaci:
namespace DevHobby.Code.RPG
{
public interface IPostacFactory
{
Postac StworzBohatera(string typ, string imie);
Postac StworzPotwora(string typ);
}
}
💡 Dzięki temu możemy mieć różne implementacje fabryk i w przyszłości je wymieniać bez zmiany reszty kodu.
⚙ Implementacja klasy PostacFactory
Wzorzec Factory centralizuje logikę tworzenia:
using System.Text.Json;
namespace DevHobby.Code.RPG
{
public class PostacFactory : IPostacFactory
{
public Postac StworzBohatera(string typ, string imie)
{
return typ.ToLower() switch
{
"wojownik" => new Wojownik(imie),
"lucznik" => new Lucznik(imie),
"mag" => new Mag(imie),
_ => throw new ArgumentException($"Nieznany typ bohatera: {typ}")
};
}
public Postac StworzPotwora(string typ)
{
return typ.ToLower() switch
{
"ork" => new Ork(),
"goblin" => new Goblin(),
"smok" => new Smok(),
_ => throw new ArgumentException($"Nieznany typ potwora: {typ}")
};
}
}
}
✅ Switch expression w C# 8+ pozwala na krótszy, czytelniejszy kod.
✅ Wyjątek w przypadku błędnego typu zabezpiecza nas przed nieprawidłowymi danymi.
✅ => oznacza zwróć to co jest po prawej stronie
✅ _ to jest “default case”, oznacza “wszystko inne”
Użycie w Program.cs
Teraz, zamiast tworzyć postacie bezpośrednio za pomocą operatora new
w pliku Program.cs, użyjemy naszej nowej fabryki.
Dzięki temu główna pętla gry nie musi “wiedzieć”,
jak tworzone są konkretne obiekty.
Zamiast: (bezpośrednie tworzenie):
var rycerz = new Wojownik("Sir Galahad"); // Bezpośrednio wywołujemy konstruktor
var czarnoksieznik = new Mag("Mroczny Mag"); // Musimy "wiedzieć" jakie klasy istnieją
var lucznik = new Lucznik("Legolas"); // Kod jest "sztywny"
var ork = new Ork();
var smok = new Smok();
var goblin = new Goblin();
Problemy tego podejścia:
- Jeśli zmieni się konstruktor Wojownika,
musimy zmieniać kod w wielu miejscach - Trudno testować – nie możemy podpiąć “fake” obiektów
- Kod gry jest bezpośrednio powiązany z konkretnymi klasami
Robimy
var factory = new PostacFactory();
var rycerz = factory.StworzBohatera("wojownik", "Sir Galahad");
var czarnoksieznik = factory.StworzBohatera("mag", "Mroczny Mag");
var lucznik = factory.StworzBohatera("lucznik", "Legolas");
var ork = factory.StworzPotwora("ork");
var smok = factory.StworzPotwora("smok");
var goblin = factory.StworzPotwora("goblin");
Zalety nowego podejścia:
- Jedna zmiana w fabryce = zmiana w całej aplikacji
- Możliwość tworzenia postaci na podstawie danych z pliku/bazy
- Łatwiejsze dodawanie nowych typów postaci
📂 Tworzenie postaci z pliku TXT
Plik postacie.txt:
# typ,imie
bohater:wojownik,Sir Galahad
bohater:mag,Mroczny Mag
bohater:lucznik,Legolas
potwor:ork,Ork
potwor:goblin,Goblin
potwor:smok,Smok
Implementacja wczytywania:
public List<Postac> WczytajPostacieZPliku(string sciezka)
{
var lista = new List<Postac>();
foreach (var linia in File.ReadAllLines(sciezka))
{
if (string.IsNullOrWhiteSpace(linia) || linia.StartsWith("#"))
continue;
var czesci = linia.Split(',');
if (czesci.Length != 2) continue;
var kategoria = czesci[0].Split(':')[0].Trim().ToLower();
var typ = czesci[0].Split(':')[1].Trim();
var imie = czesci[1].Trim();
if (kategoria == "bohater")
lista.Add(StworzBohatera(typ, imie));
else if (kategoria == "potwor")
lista.Add(StworzPotwora(typ));
}
return lista;
}
Użycie w Program
.
cs
// Tworzenie bohaterów
var factory = new PostacFactory();
// Wczytanie z pliku konfiguracyjnego
var wszystkiePostacie = factory.WczytajPostacieZPliku("postacie.txt");
// Pokazujemy status przed walką
wszystkiePostacie.ForEach(p => p.PokazStatus());
Korzyści
- Dodanie nowej postaci = dopisana linijka w pliku
np.potwor:troll,Brzydki Troll
- Gra sama odczyta i stworzy obiekt bez zmian w kodzie
- Fabryka dalej ukrywa wszystkie
new
💡 Prosta struktura TXT = szybkie testy i edycja danych bez rekompilacji.
📦 Tworzenie postaci z pliku JSON
Struktura postacie.json
:
[
{ "Kategoria": "bohater", "Typ": "wojownik", "Imie": "Sir Galahad" },
{ "Kategoria": "bohater", "Typ": "mag", "Imie": "Mroczny Mag" },
{ "Kategoria": "bohater", "Typ": "lucznik", "Imie": "Legolas" },
{ "Kategoria": "potwor", "Typ": "ork", "Imie": "Ork" },
{ "Kategoria": "potwor", "Typ": "goblin", "Imie": "Goblin" },
{ "Kategoria": "potwor", "Typ": "smok", "Imie": "Smok" },
]
Zalety JSON vs TXT:
- Struktura – JSON ma wbudowaną strukturę, łatwiej parsować
- Walidacja – JsonSerializer automatycznie sprawdzi poprawność
- Rozszerzalność – łatwo dodać nowe pola (np. HP, siła) bez zmiany parsera
Standardowość – JSON jest uniwersalnym formatem
Model konfiguracyjny:
namespace DevHobby.Code.RPG
{
public class PostacConfig
{
public string Kategoria { get; set; } // "bohater" lub "potwor"
public string Typ { get; set; } // np. "wojownik"
public string Imie { get; set; } // np. "Sir Galahad"
}
}
Metoda wczytująca JSON:
public List<Postac> WczytajPostacieZJson(string sciezka)
{
var json = File.ReadAllText(sciezka);
var configList = JsonSerializer.Deserialize<List<PostacConfig>>(json);
var lista = new List<Postac>();
foreach (var cfg in configList)
{
if (cfg.Kategoria.ToLower() == "bohater")
lista.Add(StworzBohatera(cfg.Typ, cfg.Imie));
else if (cfg.Kategoria.ToLower() == "potwor")
lista.Add(StworzPotwora(cfg.Typ));
}
return lista;
}
📌 Checklist – co już mamy dzięki Factory?
- Jedno miejsce tworzenia wszystkich postaci
- Możliwość łatwej zmiany źródła danych (TXT, JSON, baza)
- Elastyczne dodawanie nowych typów postaci
- Czysty i skalowalny kod
🚀 Korzyści z Factory Pattern w grach RPG
- Centralizacja logiki – łatwiejsze debugowanie.
- Mniejsze powiązania – klasy gry nie muszą znać szczegółów konstrukcji obiektów.
- Skalowalność – dodanie
Boss
lubNPC
to dosłownie jedna linijka w fabryce.
📚 Kolejne kroki
- Dodaj fabrykę przedmiotów (
ItemFactory
) - Wczytuj dane z bazy SQLite
- Połącz system Factory z systemem walki z części 2
📌 Zobacz też: C# Klasy i Obiekty – Gra RPG cz.1 | C# System Walki – Gra RPG cz.2 | Dziedziczenie i Polimorfizm w Grze RPG [Część 3]