Refaktoryzacja RPG w C#: Oddzielenie logiki domenowej od UI

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! 🚀

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *