Wzorzec projektowy repozytorium. DSP16 – część 10

Postanowiłem użyć w moim projekcie wzorca projektorowego: repozytorium (ang. repository). Myślę, że stworzenie samemu repozytorium nie jest takie trudne. Krok po kroku przedstawię jak można samemu do tego dojść.

Posiadam tabelę w bazie danych o nazwie Player, która jest odzwierciedlona w kodzie tak:

public class Player
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string Surname { get; set; }
}

Posiadam również klasę dzięki której mam dostęp do bazy danych: DbContext. Aby pobrać wszystkie rekordy z bazy danych z informacjami o graczach wystarczy, że napiszę:

var dbContext = new DbContext();
var players = dbContext.Set<Player>();

Powiedzmy, że chcę napisać klasę, która będzie wykonywała potrzebne operacje pobierania rekordów z bazy danych i zwracała to co potrzebuję: czyli wszystkich graczy oraz wszystkich graczy w kolejności alfabetycznej.

public class Repository
{
	private DbContext _dbContext;
	public Repository(DbContext dbContext)
	{
		_dbContext = dbContext;
	}

	public IEnumerable<Player> GetAllPlayers()
	{
		return _dbContext.Set<Player>();           
	}

	public IEnumerable<Player> GetAllPLayersOrderByName()
	{
		var players = GetAllPlayers();
		var orderedPlayers = players.OrderBy(p => p.Name);
		return orderedPlayers;
	}
}

Proste. Na potrzeby testowania stworzę do tej klasy interfejs.

public interface IRepository
{
	IEnumerable<Player> GetAllOrderByName();
	IEnumerable<Player> GetAllPlayers();
}
public class Repository : IRepository
{
	private DbContext _dbContext;
	...
}

Po chwili jednak przyszło mi do głowy, że potrzebowałbym też wyciągnąć z bazy danych dane z tabeli Venue, której odzwierciedlenie w kodzie wygląda tak:

public class Venue
{
	public int ID { get; set; }
	public string Name { get; set; }
	public string Address { get; set; }
	public DayOfWeek Day { get; set; }
}

I również w tym przypadku chciałbym używać klasy, która zwracałaby to co potrzebuję i jest związane z Venue: Wszystkie dostępne miejsca oraz miejsca posortowane po dacie tygodnia w której grany jest turniej.Nie przejmując się wiele postanowiłem nową funkcjonalność wrzucić do istniejącej już klasy:

public class Repository
{
	...

	public IEnumerable<Venue> GetAllVenues()
	{
		return _dbContext.Set<Venue>();
	}

	public IEnumerable<Venue> GetVenuesOrderByDay()
	{
		var venues = GetAllVenues();
		var orderedVenues = venues.OrderBy(v => v.Day);
		return orderedVenues;
	}
}

Rozszerzę interfejs:

public interface IRepository
{
	IEnumerable<Player> GetAllOrderByName();
	IEnumerable<Player> GetAllPlayers();
	IEnumerable<Venue> GetAllVenues();
	IEnumerable<Venue> GetVenuesOrderByDay();
}

Żyć nie umierać! Ale niestety łamię zasadę SOLID: Zasadę Jednej Odpowiedzialności – klasa Repository ma za dużo obowiązków: zajmuje się jednocześnie Graczami oraz Miejscami. No to naprawmy to. Stworzę osobną klasę, która będzie zwracać potrzebne rzeczy związane z klasą Venue oraz osobną klasę która będzie obsługiwać klasę Player.

public class PlayerRepository : IPlayerRepository
{
	private DbContext _dbContext;
	public PlayerRepository(DbContext dbContext)
	{
		_dbContext = dbContext;
	}

	public IEnumerable<Player> GetAllPlayers()
	{
		return _dbContext.Set<Player>();
	}

	public IEnumerable<Player> GetAllOrderByName()
	{
		var players = GetAllPlayers();
		var orderedPlayers = players.OrderBy(p => p.Name);
		return orderedPlayers;
	}
}


public class VenueRepository : IVenueRepository
{
	private DbContext _dbContext;
	public VenueRepository(DbContext dbContext)
	{
		_dbContext = dbContext;
	}


	public IEnumerable<Venue> GetAllVenues()
	{
		return _dbContext.Set<Venue>();
	}

	public IEnumerable<Venue> GetVenuesOrderByDay()
	{
		var venues = GetAllVenues();
		var orderedVenues = venues.OrderBy(v => v.Day);
		return orderedVenues;
	}
}

Jak pewnie zauważyłeś od razu zaimplementowałem odpowiednie interfejsy. No bo po co klasa ma implementować coś z czego nie będzie korzystać (Zasada Segregacji Interfejsów)?

public interface IPlayerRepository
{
	IEnumerable<Player> GetAllOrderByName();
	IEnumerable<Player> GetAllPlayers();
}

public interface IVenueRepository
{
	IEnumerable<Venue> GetAllVenues();
	IEnumerable<Venue> GetVenuesOrderByDay();
}

Rzucają mi się tu w oczy jeszcze dwie rzeczy: obydwie klasy mają jedno pole: DbContext oraz z obydwie klasy mają metodę której zadaniem jest wyciągniecie wszystkich elementów danego typu z bazy danych. Rozwiązaniem na to jest dziedziczenie klasy generycznej. Rozwiązanie podaję na talerzu:

public class Repository<T> 
{
	protected readonly DbContext DbContext;

	public Repository(DbContext dbContext)
	{
		DbContext = dbContext;
	}

	public IEnumerable<T> GetAll()
	{
		return DbContext.Set<T>();
	}
}

To teraz poprawmy klasy: VenueRepository oraz PlayerRepository, aby dziedziczyły po klasie Repository.

public class PlayerRepository : Repository<Player>
{
	public PlayerRepository(DbContext dbContext) : base(dbContext)
	{

	}

	public IEnumerable<Player> GetAllOrderByName()
	{
		var players = GetAll();
		var orderedPlayers = players.OrderBy(p => p.Name);
		return orderedPlayers;
	}
}


public class VenueRepository : Repository<Venue>
{
	public VenueRepository(DbContext dbContext) : base(dbContext)
	{
	   
	}

	public IEnumerable<Venue> GetVenuesOrderByDay()
	{
		var venues = GetAll();
		var orderedVenues = venues.OrderBy(v => v.Day);
		return orderedVenues;
	}
}

Jak widać teraz używam tylko metody GetAll(). Dzięki zastosowaniu klasy generycznej, metoda GetAll zwraca albo graczy albo miejsca. To zależy w której klasie wywołam tą metodę. Idźmy dalej… Aby móc mockować klasę Repository wypadałoby dodać interfejs do tej klasy:

public interface IRepository<T> where T : class
{
	IEnumerable<T> GetAll();
}

public class Repository<T> : IRepository<T> where T : class
{
	protected readonly DbContext DbContext;

	...
}

Wróćmy do IPlayerRepository oraz IVenueRepository. Poprawmy te interfejsy, pozbądźmy się niepotrzebnych metod.

public interface IPlayerRepository
{
	IEnumerable<Player> GetAllOrderByName();
}

public interface IVenueRepository
{
	IEnumerable<Venue> GetVenuesOrderByDay();
}

public class PlayerRepository : Repository<Player>, IPlayerRepository
{
	public PlayerRepository(DbContext dbContext) : base(dbContext)
	{

	}

	public IEnumerable<Player> GetAllOrderByName()
	{
		var players = GetAll();
		var orderedPlayers = players.OrderBy(p => p.Name);
		return orderedPlayers;
	}
}


public class VenueRepository : Repository<Venue>, IVenueRepository
{
	public VenueRepository(DbContext dbContext) : base(dbContext)
	{
	   
	}

	public IEnumerable<Venue> GetVenuesOrderByDay()
	{
		var venues = GetAll();
		var orderedVenues = venues.OrderBy(v => v.Day);
		return orderedVenues;
	}
}

Z interfejsów usunąłem metody GetAllPlayers oraz GetAllVenues. One są zastąpione przez jedną generyczną: GetAll.

 

Tak widzę właśnie budowę repozytorium od początku. Jeżeli chcesz zobaczyć jak można stworzyć dobre repozytorium zapraszam do obejrzenia filmiku na youtubie (patrz niżej), który moim zdaniem wyjaśnia całe zagadnienie w bardzo przystępny i fajny sposób.

Repozytorium, Linki:

Film na YouTubie, który świetnie wyjaśnia Repository Pattern

Marcin Dembowski o repozytorium

CodingTV – czy warto stosować repozytorium

 

 

3 thoughts on “Wzorzec projektowy repozytorium. DSP16 – część 10”

      1. Kod kliencki powinien korzystać wyłącznie z konkretnych repozytoriów, i te faktycznie powinny mieć interfejsy i być publiczne. Zaś generyczne repozytorium bazowe powinno być internal, no i raczej nie potrzebuje interfejsu. (Zwłaszcza do rozszerzalności, ile nowych metod można jeszcze dopisać do generycznego repozytoruim? :))

Leave a Comment

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