Repository Pattern

Repository Pattern w C# – Refaktoryzacja kodu do Clean Architecture [2/6]  [Część 7]

Repository Pattern w C# – Refaktoryzacja kodu do Clean Architecture

Masz wrażenie, że Twoja klasa robi zbyt wiele? Wczytuje dane z pliku, deserializuje JSON i jeszcze tworzy obiekty? To klasyczny problem, który prowadzi do chaosu i łamania zasad SOLID. W tym wpisie pokażę Ci, jak w praktyce wykorzystać Repository Pattern w C# w ramach Clean Architecture, aby oddzielić domenę od warstwy danych i stworzyć elastyczny, testowalny kod.

Czym jest Repository Pattern?

Repository Pattern to wzorzec projektowy, który enkapsuluje logikę dostępu do danych i udostępnia ją aplikacji za pomocą interfejsu. Dzięki temu:

  • warstwa domenowa nie zależy od technologii zapisu/odczytu,
  • możesz w prosty sposób wymieniać źródła danych (pliki TXT, JSON, baza danych, API),
  • kod staje się zgodny z zasadami Single Responsibility Principle i Open/Closed Principle.

👉 Zobacz też: Tworzenie klas w C# – poradnik krok po kroku


Problem w projekcie RPG

W naszym projekcie RPG mieliśmy klasę PostacFactory, która:

  1. Tworzyła obiekty postaci,
  2. Wczytywała dane z plików TXT,
  3. Wczytywała dane z JSON.

Efekt? Jedna klasa – trzy odpowiedzialności. Każda zmiana formatu danych wymagała edycji fabryki. To łamało zasady Single Responsibility i Open/Closed.


Rozwiązanie: Repository Pattern

Repository Pattern to wzorzec, który:

  • Enkapsuluje logikę dostępu do danych
  • Oddziela domenę od szczegółów technicznych
  • Umożliwia łatwą zamianę źródeł danych

Struktura warstw w Clean Architecture

Core (Domain)
 ├── Entities
 ├── Interfaces
 │    └── IPostacRepository
 |    └── IPostacFactory

Infrastructure
 ├── Data
 │    ├── TxtPostacRepository
 │    ├── JsonPostacRepository
 │    └── PostacDto
 ├── PostacFactory 
  • Core – zawiera interfejsy i logikę domenową,
  • Infrastructure – implementuje dostęp do danych (TXT, JSON, baza, API),
  • Presentation – korzysta z Core przez interfejsy.

💻 Kod źródłowy
Pełny kod z tego tutoriala znajdziesz na GitHub:
👉 [Link do repozytorium GitHub]

Implementacja krok po kroku

1. Tworzymy interfejs IPostacRepository

using DevHobby.Code.RPG.Core.Entities;

namespace DevHobby.Code.RPG.Core.Interfaces;

public interface IPostacRepository
{
    List<Postac> PobierzPostacie(string sciezka);
}

Interfejs jest bardzo prosty:

  • Ma jedną metodę PobierzPostacie
  • Przyjmuje ścieżkę do pliku
  • Zwraca listę postaci
  • Nie wie skąd pochodzą dane – to szczegół implementacji!

👉 Dzięki interfejsowi warstwa domenowa nie wie, skąd pochodzą dane – może to być plik, baza albo API.

2. Implementacja dla plików TXT

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

namespace DevHobby.Code.RPG.Infrastructure.Data;

public class TxtPostacRepository : IPostacRepository
{
    private readonly IPostacFactory _factory;

    public TxtPostacRepository(IPostacFactory factory)
    {
        _factory = factory;
    }

    public List<Postac> PobierzPostacie(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(_factory.StworzBohatera(typ, imie));
            else if (kategoria == "potwor")
                lista.Add(_factory.StworzPotwora(typ));
        }

        return lista;
    }
}

Dependency Injection:

  • Repository otrzymuje IPostacFactory przez konstruktor
  • To pozwala na łatwiejsze testowanie

Logika parsowania:

  • Dokładnie taka sama jak wcześniej w PostacFactory
  • Parsujemy linie formatu: bohater:wojownik,Sir Galahad

Separation of Concerns:

  • Repository tylko wczytuje i parsuje dane
  • Factory tylko tworzy obiekty postaci

3. Implementacja dla plików JSON

using DevHobby.Code.RPG.Core.Entities;
using DevHobby.Code.RPG.Core.Interfaces;
using System.Text.Json;

namespace DevHobby.Code.RPG.Infrastructure.Data;

public class JsonPostacRepository : IPostacRepository 
{
    private readonly IPostacFactory _factory;

    public JsonPostacRepository(IPostacFactory factory)
    {
        _factory = factory;
    }

    public List<Postac> PobierzPostacie(string sciezka)
    {
        var json = File.ReadAllText(sciezka);
        var configList = JsonSerializer.Deserialize<List<PostacDto>>(json);
        var lista = new List<Postac>();

        foreach (var cfg in configList)
        {
            if (cfg.Kategoria.ToLower() == "bohater")
                lista.Add(_factory.StworzBohatera(cfg.Typ, cfg.Imie));
            else if (cfg.Kategoria.ToLower() == "potwor") 
                lista.Add(_factory.StworzPotwora(cfg.Typ));
        }

        return lista;
    }
}

PostacDto jest prostym DTO, które odzwierciedla strukturę JSON.

namespace DevHobby.Code.RPG.Infrastructure.Data;
public class PostacDto
{
    public string Kategoria { get; set; }
    public string Typ { get; set; } 
    public string Imie { get; set; }
}

Korzyści z Repository Pattern

Single Responsibility Principle – fabryka zajmuje się tylko tworzeniem obiektów.
Open/Closed Principle – nowe źródło danych dodajesz bez modyfikacji istniejącego kodu.
Testowalność – możesz mockować IPostacRepository w testach jednostkowych.
Elastyczność – zmieniasz TXT na JSON lub bazę danych w jednej linijce kodu.


Podsumowanie

Dzięki Repository Pattern udało się:

  • uprościć PostacFactory,
  • oddzielić domenę od logiki dostępu do danych,
  • przygotować kod na przyszłe rozszerzenia (baza, API, XML).

To właśnie kwintesencja Clean Architecture w C# – jasne granice, elastyczny kod i pełna kontrola nad projektem.

Nasze warstwy są teraz prawdziwie oddzielone:

  • Core – zależy tylko od interfejsów
  • Infrastructure – implementuje interfejsy Core
  • Presentation – używa Core przez interfejsy

👉 W kolejnym kroku wprowadzimy Service Layer i wyniesiemy logikę walki do dedykowanej warstwy!

Masz pytania albo własne doświadczenia z Repository Pattern? Zostaw komentarz pod tym wpisem!

💻 Kod źródłowy
Pełny kod z tego tutoriala znajdziesz na GitHub:
👉 [Link do repozytorium GitHub]

Dodaj komentarz

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