Refaktoryzacja Clean Architecture

Refaktoryzacja kodu do Clean Architecture – BattleService w Core [3/6]  [Część 8]

Refaktoryzacja kodu do Clean Architecture – BattleService w Core

Masz wrażenie, że Twój Program.cs robi wszystko naraz – od wyświetlania UI po symulację logiki biznesowej? 🚨
To klasyczny objaw spaghetti code.
W tym artykule pokażę Ci, jak przenieść logikę do Service Layer, zyskać czystość architektury i przygotować kod pod testy jednostkowe. Wszystko w praktycznym przykładzie refaktoryzacji RPG w C# i .NET.

Dlaczego Program.cs robi za dużo?

W trakcie pracy nad aplikacjami w C# często spotykamy się z problemem:

  • UI, orkiestracja i logika biznesowa znajdują się w jednym pliku,
  • kod jest trudny do testowania i rozszerzania,
  • każda zmiana w logice wymusza modyfikację warstwy prezentacji.

Typowe objawy złej architektury

  • 80+ linii kodu w Program.cs
  • mieszanie Console.WriteLine z logiką walki
  • brak możliwości uruchomienia testów jednostkowych
  • chaos i trudność w rozwoju aplikacji

👉 Rozwiązanie? Wydzielenie logiki do Service Layer.


Czym jest Service Layer?

Service Layer to dodatkowa warstwa w Clean Architecture, która:

  • enkapsuluje logikę biznesową,
  • oddziela Core (domena) od Presentation (UI),
  • umożliwia reużycie w różnych kontekstach (API, GUI, testy),
  • czyni aplikację łatwiejszą do testowania.

📌 Analogia:

  • UI (kelner) – przyjmuje zamówienia,
  • Service (kucharz) – przygotowuje danie,
  • Repository (magazyn) – dostarcza składniki.

Kelner nie gotuje, kucharz nie obsługuje klientów – tak samo powinno być w Twoim kodzie.


Krok 1: Definiujemy interfejs IBattleService

W Core/Interfaces tworzymy prosty kontrakt dla logiki walki:

using DevHobby.Code.RPG.Core.Entities;

namespace DevHobby.Code.RPG.Core.Interfaces;

public interface IBattleService
{
    Postac Sylumuj(IList<Postac> uczestnicy);
}

Dlaczego tak?

  • Prosty kontrakt – jeden use case = jedna metoda,
  • Elastyczność – w przyszłości możemy dodać różne tryby walki (tournament, battle royale, team battle).

Krok 2: Implementacja BattleService

W Core/Services umieszczamy pełną logikę walki:

using DevHobby.Code.RPG.Core.Entities;
using DevHobby.Code.RPG.Core.Interfaces;

namespace DevHobby.Code.RPG.Core.Services;

public class BattleService : IBattleService
{
    private readonly Random random = new Random();

    public event Action<string>? KomunikatWygenerowany;

    public Postac Sylumuj(IList<Postac> uczestnicy)
    {
        // Symulacja walki
        while (uczestnicy.Count(p => p.PunktyZycia > 0) > 1)
        {
            // Losowanie atakującego spośród żywych postaci
            var zywePostacie = uczestnicy.Where(p => p.PunktyZycia > 0).ToList();
            if (zywePostacie.Count <= 1) break;

            var atakujacy = zywePostacie[random.Next(zywePostacie.Count)];

            // Losowanie celu (różnego od atakującego)
            Postac cel;
            do
            {
                cel = zywePostacie[random.Next(zywePostacie.Count)];
            } while (cel == atakujacy);

            // Wykonanie ataku
            atakujacy.Atakuj(cel);

            // Krótka pauza, aby śledzić przebieg walki
            Thread.Sleep(1000);

            // Kontratak
            if (cel.PunktyZycia > 0)
            {
                cel.Atakuj(atakujacy);
            }

            // Czasem boharter się leczy
            if (atakujacy is Bohater bohater && atakujacy.PunktyZycia < 10 && atakujacy.PunktyZycia > 0)
            {
                GenerujKomunikat($"\n{atakujacy.Imie} używa mikstury!");
                bohater.Lecz(30);
            }

            Thread.Sleep(1000); // Pauza dla dramatyzmu          
        }

        return uczestnicy.First(p => p.PunktyZycia > 0);
    }

    protected void GenerujKomunikat(string tresc)
    {
        KomunikatWygenerowany?.Invoke(tresc);
    }
}

⚠️ Uwaga: Console.WriteLine i Thread.Sleep nie powinny być w Service Layer. To odpowiedzialność UI.


Krok 3: Czyszczenie Program.cs

Przed refaktoryzacją: 80 linii chaosu.
Po refaktoryzacji: czysta orkiestracja.

using DevHobby.Code.RPG.Core.Services;
using DevHobby.Code.RPG.Infrastructure;
using DevHobby.Code.RPG.Infrastructure.Data;

public class Program
{
    static void Main()
    {
        Console.WriteLine("Nacisnij Enter aby zobaczyć Bohaterów Gry");
        Console.ReadLine();
        Console.WriteLine("=== ARENA WALKI ===\n");
        
        // Tworzenie bohaterów
        var factory = new PostacFactory();
        var repostory = new JsonPostacRepository(factory);
        var battleService = new BattleService();
        battleService.KomunikatWygenerowany += Console.WriteLine;

        // Wczytanie z pliku konfiguracyjnego
        var wszystkiePostacie = repostory.PobierzPostacie("postacie.json");
        wszystkiePostacie.ForEach(p => p.KomunikatWygenerowany += Console.WriteLine);
         
        // Pokazujemy status przed walką
        wszystkiePostacie.ForEach(p => p.PokazStatus());
        
        Console.WriteLine("Nacisnij Enter aby rozpocząć Grę");
        Console.ReadLine();
        Console.WriteLine("\n--- WALKA ROZPOCZĘTA! ---");

        // CAŁA LOGIKA WALKI W JEDNEJ LINII!
        var zwyciezca = battleService.Sylumuj(wszystkiePostacie);

        // Podsumowanie
        Console.WriteLine("\n=== WALKA ZAKOŃCZONA ===");
        wszystkiePostacie.ForEach(p => p.PokazStatus());
             
        if (zwyciezca != null)
            Console.WriteLine($"\n🏆 {zwyciezca.Imie} WYGRYWA!");
        else
            Console.WriteLine("\n 💥 Wszyscy polegli w walce!");
    }
}

👉 Teraz Program.cs robi tylko orkiestrację – tak, jak powinien.


Efekty refaktoryzacji

  • Czytelność – Program.cs odpowiada tylko za start aplikacji.
  • Single Responsibility – każda klasa ma jeden cel.
  • Testowalność – logika walki może być sprawdzona w testach jednostkowych.
  • Rozszerzalność – nowe tryby walki = nowe serwisy, bez modyfikacji UI.

Zobacz też

To teraz Twoja kolej! 💪

  • Wydziel logikę biznesową do Service Layer w swoim projekcie.
  • Podziel się w komentarzu: gdzie w Twoim kodzie UI miesza się z logiką biznesową?
  • Subskrybuj YT , aby nie przegapić kolejnych materiałów o Clean Architecture w C#.

Dodaj komentarz

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