Application Service

Refaktoryzacja do Clean Architecture – Application Service [4/6]  [Część 9]

Refaktoryzacja do Clean Architecture – Application Service [4/6]  [Część 9]

Wprowadzenie – po co nam Application Service?

Twój Program.cs robi wszystko naraz? Wyświetla komunikaty, tworzy obiekty, ładuje dane, zarządza eventami i jeszcze uruchamia logikę walki? To klasyczny sygnał, że czas na refaktoryzację do Clean Architecture w C#.

W tym wpisie pokażę Ci, jak krok po kroku wydzielić Application Service (GameService) i przygotować kod pod Dependency Injection. Dzięki temu kod stanie się czystszy, testowalny i zgodny z zasadami SOLID.


Czym jest Application Service w Clean Architecture?

Application Service to warstwa aplikacji, której zadania to:

  • orkiestracja współpracy między serwisami,
  • przechowywanie logiki biznesowej wysokiego poziomu,
  • obsługa scenariuszy użycia (use case’ów),
  • niezależność od UI i infrastruktury.

Zobacz też: Tworzenie klas w C#


Krok 1: Tworzenie klasy GameService

Zaczynamy od nowej klasy GameService w warstwie Application.

public class GameService
{
    private readonly IPostacRepository _postacRepository;
    private readonly IBattleService _battleService;
    private List<Postac> _postacie = new();

    public GameService(IPostacRepository postacRepository, IBattleService battleService)
    {
        _postacRepository = postacRepository;
        _battleService = battleService;
    }
}

👉 Dzięki Dependency Injection serwis nie tworzy zależności samodzielnie – przyjmuje je w konstruktorze.


Krok 2: Wczytywanie postaci z pliku

Przenosimy logikę wczytywania danych z pliku.

public void PobierzPostacie(string sciezkaPliku)
{
    _postacie = _postacRepository.PobierzPostacie(sciezkaPliku).ToList();
}

public IReadOnlyList<Postac> Postacie => _postacie.AsReadOnly();

✔️ Dane są enkapsulowane – udostępniamy tylko kolekcję IReadOnlyList.

  • Metoda PobierzPostacie przejmuje odpowiedzialność za wczytywanie danych z pliku.
  • Korzystamy z już istniejącego repository pattern.
  • Dodajemy właściwość Postacie zwracającą IReadOnlyList to zapewnia enkapsulację

Krok 3: Walidacja danych

Dodajemy walidacje parametrów i logiki biznesowej.

if (string.IsNullOrWhiteSpace(sciezkaPliku))
    throw new ArgumentException("Ścieżka pliku nie może być pusta", nameof(sciezkaPliku));

if (!File.Exists(sciezkaPliku))
    throw new FileNotFoundException($"Nie znaleziono pliku: {sciezkaPliku}");

if (_postacie.Count < 2)
    throw new InvalidOperationException("Za mało postaci do gry.");

✔️ Dzięki temu błędy są obsługiwane jasno i precyzyjnie.

  • Walidacja parametrów: Sprawdzamy czy ścieżka nie jest pusta.
  • Walidacja systemu plików: Upewniamy się, że plik istnieje.
  • Walidacja biznesowa: Sprawdzamy czy mamy wystarczająco postaci do gry.
  • Używamy odpowiednich typów wyjątków dla różnych rodzajów błędów.

Krok 4: Symulacja walki

Dodajemy metodę Start(), która uruchamia logikę walki.

public Postac Start()
{
    return _battleService.Symuluj(_postacie);
}

👉 Program.cs staje się czystszy – zamiast całej logiki wywołujemy jedną metodę.


Krok 5: Wyświetlanie statusów

Logikę wyświetlania przenosimy do GameService.

public void WyswietlStatusy()
{
     Postacie.ForEach(p => p.PokazStatus());
}

✔️ Możemy teraz używać tego wielokrotnie, bez duplikacji kodu.


Krok 6: Tworzenie interfejsu IGameService

Tworzymy interfejs, który jasno definiuje możliwości naszej aplikacji.

public interface IGameService
{
    IReadOnlyList<Postac> Postacie { get; }
    void PobierzPostacie(string sciezkaPliku);
    Postac Start();
    void WyswietlStatusy();
}

👉 Dzięki temu zyskujemy testowalność, łatwe mockowanie i luźne powiązania (Loose Coupling).


Krok 7: Refaktoryzacja Program.cs

Ostatecznie Program.cs staje się czystym punktem wejścia.

try
{
     // Tworzenie bohaterów
     var factory = new PostacFactory();
     var repository = new JsonPostacRepository(factory);
     var battleService = new BattleService();
     battleService.KomunikatWygenerowany += Console.WriteLine;

     var gameService = new GameService(repository, battleService);

     // Ładowanie postaci
     gameService.PobierzPostacie("postacie.json");

     // Podłączanie event handlerów
     foreach (var postac in gameService.Postacie)
        postac.KomunikatWygenerowany += Console.WriteLine;

     // Wyświetlanie statusów przed walką
     gameService.WyswietlStatusy();

     Console.WriteLine("Nacisnij Enter aby rozpocząć Grę");
     Console.ReadLine();
     Console.WriteLine("\n--- WALKA ROZPOCZĘTA! ---");

     // Start walki
     var zwyciezca = gameService.Start();

     // Podsumowanie
     Console.WriteLine("\n=== WALKA ZAKOŃCZONA ===");
     gameService.WyswietlStatusy();

     if (zwyciezca != null)
        Console.WriteLine($"\n🏆 {zwyciezca.Imie} WYGRYWA!");
     else
        Console.WriteLine("\n 💥 Wszyscy polegli w walce!");
}    
catch (Exception ex)
{
    Console.WriteLine($"❌ Błąd: {ex.Message}");
}

Teraz kod jest przejrzysty, a logika wyciągnięta do Application Service.


Co zyskaliśmy?

Separacja odpowiedzialności – każda klasa ma jasno określone zadanie.
Łatwiejsze testowanie – możemy testować GameService niezależnie.
Lepsza obsługa błędów – walidacje i wyjątki w odpowiednich miejscach.
Czystszy kod – Program.cs odpowiada tylko za uruchomienie aplikacji.
Przygotowanie pod Dependency Injection – kolejne kroki będą jeszcze prostsze.


Zobacz też

Podsumowanie

Refaktoryzacja do Application Service w Clean Architecture pozwala odchudzić Program.cs i wprowadzić klarowny podział odpowiedzialności.
Dzięki temu kod jest czystszy, bardziej testowalny i rozszerzalny.

Pamiętajcie
Refaktoring to nie przepisywanie od zera,
Refaktoryzacja to proces iteracyjny.
Nie musicie robić wszystkiego na raz.
To stopniowe ulepszanie przy zachowaniu funkcjonalności.
Najważniejsze to robić małe kroki i testować po każdej zmianie,
aby upewnić się, że funkcjonalność pozostaje nietknięta.

👉 W następnym kroku pokażę Ci, jak wprowadzić Dependency Injection Container i wyeliminować ręczne tworzenie obiektów.


📌 Co myślisz o takim podejściu do refaktoryzacji?
Zostaw komentarz pod postem lub na YouTube, a jeśli chcesz więcej praktycznych materiałów
subskrybuj kanał DevHobby i nie przegap kolejnych odcinków 🚀

Dodaj komentarz

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