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:
- Tworzyła obiekty postaci,
- Wczytywała dane z plików TXT,
- 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]