Oddzielenie logiki domenowej od UI
Pisząc gry czy aplikacje w C#, często zaczynamy od prostych rozwiązań – np. wyświetlania komunikatów przez Console.WriteLine
. To działa na początku, ale szybko prowadzi do problemu: logika biznesowa staje się przyspawana do jednego interfejsu użytkownika.
W tym artykule pokażę Ci, jak w projekcie RPG w C# zrobić refaktoryzację i odseparować logikę domenową od warstwy UI, wykorzystując podejście event-driven. To fundament pod Clean Architecture i elastyczne rozwiązania w .NET.
Dlaczego nie warto mieszać logiki z UI?
Na początku w klasie Postac
każdy komunikat wyglądał tak:
Console.WriteLine($"{Imie} nie może atakować - jest pokonany!");
Niby działa, ale problem pojawia się wtedy, gdy chcemy grę przenieść np. do:
- WPF albo WinForms,
- ASP.NET Core (wersja webowa),
- aplikacji mobilnej.
W takim układzie – klops. Domena nie nadaje się do ponownego użycia, bo jest ściśle związana z konsolą. To łamie zasadę Single Responsibility i zamyka drogę do rozwoju.
Jak oddzielić logikę domenową od UI?
1. Usuń Console.WriteLine
z domeny
Zamiast pisać komunikaty bezpośrednio do konsoli – emitujemy zdarzenia.
2. Dodaj zdarzenie domenowe
// Zdarzenie do komunikatów
public event Action<string>? KomunikatWygenerowany;
Action<string>
→ informuje: „hej, wygenerowałem komunikat”.?
→ event jest opcjonalny, subskrybenci nie są wymagani.
3. Stwórz metodę pomocniczą
protected void GenerujKomunikat(string tresc)
{
KomunikatWygenerowany?.Invoke(tresc);
}
To jedyny punkt, gdzie domena „wysyła” informacje na zewnątrz. Zamiast Console.WriteLine, robimy GenerujKomunikat.
To genialne, bo:
- UI może wyświetlić komunikat.
- Logger może zapisać do pliku.
- API może wysłać do klienta.
- Albo… nic nie musi zrobić (brak subskrybentów = brak komunikatów).
Refaktoryzacja metod
Przed:
Console.WriteLine($"{Imie} nie może atakować - jest pokonany!");
Po:
GenerujKomunikat($"{Imie} nie może atakować - jest pokonany!");
Różnica?
- Domena mówi: „wydarzyło się coś”.
- UI decyduje, co z tym zrobić.
Integracja w aplikacji konsolowej
W fabryce podpinamy eventy do obsługi w konsoli:
public Postac StworzBohatera(string typ, string imie)
{
Postac postac;
switch (typ.ToLower())
{
case "wojownik":
postac = new Wojownik(imie);
postac.KomunikatWygenerowany += Console.WriteLine;
break;
case "lucznik":
postac = new Lucznik(imie);
postac.KomunikatWygenerowany += Console.WriteLine;
break;
case "mag":
postac = new Mag(imie);
postac.KomunikatWygenerowany += Console.WriteLine;
break;
default:
throw new ArgumentException($"Nieznany typ bohatera: {typ}");
}
return postac;
}
public Postac StworzPotwora(string typ)
{
Postac postac;
switch (typ.ToLower())
{
case "ork":
postac = new Ork();
postac.KomunikatWygenerowany += Console.WriteLine;
break;
case "goblin":
postac = new Goblin();
postac.KomunikatWygenerowany += Console.WriteLine;
break;
case "smok":
postac = new Smok();
postac.KomunikatWygenerowany += Console.WriteLine;
break;
default:
throw new ArgumentException($"Nieznany typ potwora: {typ}");
}
return postac;
}
A w innych UI możemy zrobić coś innego:
W WPF:
wojownik.KomunikatWygenerowany += msg => MessageBox.Show(msg);
W ASP.NET Core:
wojownik.KomunikatWygenerowany += msg => logger.LogInformation(msg);
Podsumowanie
W tym artykule:
✅ Usunęliśmy Console.WriteLine
z domeny – bo domena nie powinna wiedzieć o UI.
✅ Wprowadziliśmy zdarzenie KomunikatWygenerowany
.
✅ Pokazaliśmy jak UI subskrybuje eventy i decyduje o ich obsłudze.
✅ Zrobiliśmy krok w stronę event-driven architecture i Clean Architecture.
To fundament do kolejnych kroków: podziału projektu na warstwy, eliminacji spaghetti kodu i wprowadzenia nowoczesnej architektury w .NET.
👉 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] | C# Factory Pattern w praktyce [Część 4]
Podobał Ci się ten artykuł?
📩 Zostaw komentarz, podziel się swoim doświadczeniem i zasubskrybuj kanał na YT, aby nie przegapić kolejnych części serii! 🚀