2009-03-02 10 views
5

Mam nadzieję, że potrafię to wyjaśnić nieco cnotliwie, ponieważ dzisiaj wyrzuca mi bezpiecznik w moim mózgu. Uczę się TDD w języku C#, więc wciąż próbuję naprawić mój mózg, aby go dopasować.TDD: Metody statyczne, wtrysk zależności, buforowanie i ty!

Załóżmy, że mam klasę Użytkownik, która poprzednio miała statyczną metodę pobierania obiektu użytkownika (uproszczoną poniżej).

public static User GetUser(string username) 
{ 
    User user = GetUserFromCache(username); 
    if(user == null) 
    { 
     user = GetUserFromDatabase(username); 
     StoreObjectInCache(user); 
    } 
    return user; 
} 

Więc staram się przepisać ten używać iniekcji zależność więc mogę Fałszywe poza „GetUserFromDatabase” metoda, jeżeli musi tam pojechać. Oznacza to, że muszę sprawić, aby funkcja nie była statyczna. Podczas gdy warstwa dostępu do danych skonstruowałaby obiekt użytkownika z bazy danych, odwzorowując zwrócone kolumny na właściwości obiektu, pobieranie z pamięci podręcznej zwróci prawdziwy obiekt niebieski. Jednak w przypadku metody niestatycznej nie można po prostu powiedzieć, że jest to nie działa w ten sposób. Chociaż nie jestem światowym ekspertem w tańcu wokół tego z OO, wygląda na to, że prawie będę musiał pobrać obiekt User z pamięci podręcznej i napisać inną funkcję odwzorowania, która przechowałaby zwrócone właściwości obiektu użytkownika do nowego Instancja użytkownika.

Jakie jest rozwiązanie tutaj? Czy brakuje jakiejś magii OO? Czy jedynym rozwiązaniem jest refaktoryzowanie wszystkiego w celu użycia fabryk, zamiast posiadania logiki instancji w samym obiekcie? A może patrzyłem na to zbyt długo i tęskniłem za czymś zupełnie oczywistym?

Odpowiedz

7

Nie sądzę, że brakuje ci magii i myślę, że refaktoryzacja w celu usunięcia kodu trwałości z twoich obiektów biznesowych i warstwy uporczywości jest właściwym sposobem na przejście zarówno od testów jednostkowych, jak i od projektu. Możesz pomyśleć o umieszczeniu cache pomiędzy warstwą biznesową a warstwą trwałości, pośrednicząc w pobieraniu/aktualizacji twoich obiektów biznesowych, aby uprościć rzeczy. Powinieneś być w stanie udawać/fałszować pamięć podręczną i warstwę uporczywości, jeśli rozdzielisz te rzeczy w ten sposób.

+0

Dzięki za informację zwrotną! Jeśli chodzi o przeniesienie logiki pamięci podręcznej do trwałości, jestem ciekawy, co się stanie, jeśli obiekt, który stawiam, ma grupę odwołań do buforowanego obiektu ... a następnie buforowany obiekt wypada z pamięci podręcznej. Boli mnie głowa. – Chris

+0

+1: zdecydowanie trzeba oddzielić obawy więcej. posiadanie pamięci podręcznej jako oddzielnej warstwy między aplikacją a warstwą trwałości ma wiele sensu. –

4

Unit testing code that does date processing based on today's date

Przed

  • Jest jakiś kod, który korzysta z bazy danych, aby pobrać użytkownika i umieścić go w pamięci podręcznej.

Po

  • Jest jakiś kod, który korzysta z bazy danych, aby sprowadzić Użytkownika
  • Istnieje jakiś kod, który umieszcza użytkownika w pamięci podręcznej.

Te dwa zestawy kodów nie powinny być od siebie zależne.

public static Func<string, UserName> Loader {get;set;} 

public static Constructor() 
{ 
    Loader = GetFromDataBase; 
} 

public static User GetUser(string userName) 
{ 
    User user = GetUserFromCache() 
    if (user == null) 
    { 
    user = Loader(userName); 
    StoreUserInCache(user); 
    } 
    return user; 
}  

public void Test1() 
{ 
    UserGetter.Loader = Mock.GetUser; 
    UserGetter.GetUser("Bob"); 
} 

Klasycznie zamiast funkcji Func używany byłby interfejs. Jeśli dotyczy to więcej niż jednej metody, interfejs jest oczywistym wyborem w stosunku do Func. Jeśli same implementacje metod są statyczne, Func jest sposobem na ich abstrakcję.

+0

Świetna odpowiedź, dzięki David –

1

To czego mi brakuje w twoim przykładzie to kontekst twojego połączenia z "GetUser". Jest tak prawdopodobnie dlatego, że dzięki statycznej metodzie nie musisz się zastanawiać, ponieważ możesz ją wywołać zewsząd.W DI oznacza to, że repozytorium musi być w jakiś sposób przywoływane przez nadawcę, co jest najprawdopodobniej polem.

Gdy Twoja pamięć podręczna jest polem jakiegoś obiektu, prawdopodobnie prawdopodobnie będzie to fasada, dzięki której pamięć podręczna stanie się proxy Twojej bazy danych.

Więc trzeba:

class ApplicationFacade{ 
    private IUserRepository users = null; 

    public doStuff(){ 
    this.users.GetUser("my-name"); 
    } 
} 

gdzie IUserRepository jest wspólny interfejs dla pamięci podręcznej, fałszywe bazy danych i bazy danych. Coś prostego jak:

interface IUserRepository{ 
    User GetUser(string username); 
} 

Twoja skrzynka mogła teraz być prosty przedmiot wdrożenia tego interfejsu, a ponieważ pamięć podręczna jest wstrzykiwany kontener DI można również wstrzykiwać do niego.

class Cache : IUserRepository { 
    private IUserRepository users = null; 
    public User GetUser(string username){ 
    if (this.NotCached(username)){ 
     this.ToCache(this.users.GetUser(username)); 
    } 
    return this.FromCache(username); 
    } 
} 

Teraz w zależności od tego, co chcesz, możesz wstrzykiwać fałszywe, pamięć podręczną lub bazy danych do swojego obiektu elewacji i jeśli używasz obiektu cache można wstrzykiwać fałszywe bazy er do niego w razie potrzeby (lub nawet inny cache jeśli naprawdę chciałeś).

Przyczyna, w której rzeczywisty mechanizm wstrzykiwania zależy od pojemnika DI i może wymagać dodatkowego kodu jako właściwości publicznej lub pól konstruktora.