Jak tworzyć instancję repozytorium?

W poprzednim poście opisałem wzorzec repozytorium. Jak już wspomniałem, repozytorium może różnić się  implementacją w zależności od encji. W skrajnych przypadkach może być jedno repozytorium na jedną encję (Order – OrderRepository, Product – ProductRepository itp). Tworzenie każdego repozytorium ręcznie w zależności od przetwarzanej encji jest dość niewygodne. Przydałby się jakiś globalny mechanizm na tworzenie obiektów na podstawie typu encji.

Dobrym rozwiązaniem problemu jest implementacja wzorca service locator, wywodzącego się  z wzorca factory. Ogólnie mówiąc, factory jest to klasa z metodą Create odpowiedzialną za dynamiczną inicjalizację. Spójrzmy na diagram klas wzorca:

image

Jeśli diagram nie jeszcze jasny, przedstawiam dodatkowo szkic kodu:

public interface IWindowFactory
{
   IWindow Create();
}
public class WindowsXpWindowFactory : IWindowFactory
{
   public IWindow Create()
   {
       return new WindowXp();
   }
}

IWindow to interfejs okna. WindowXp, Window98 to okna stylizowane na konkretny system operacyjny. W rozważanym przykładzie, celem jest stworzenie klas, które umożliwiają nam podpinanie różnych skórek czy też kontrolek stylizowanych np. na system operacyjny. W standardowym podejściu, użytkownik w celu stworzenia okna, wykonuje po prostu:

WindowXp window = new WindowXp();

Podejście ma zasadniczą wadę – co w przypadku gdy chcemy zmieniać skórki w czasie działania programu? Za pomocą wzorca factory, programista tworzyłby okna w następujący sposób:

IWindow window = windowFactory.Create();

W zależności od wstrzykniętego factory, okno stylizowane na jakiś system zostanie utworzone.

Można oczywiście stworzyć więcej klas factory. Przykładowo,  jeśli piszemy testy jednostkowe można stworzyć klasę WindowStubFactory, która będzie zwracać stuby\mocki zamiast rzeczywistych obiektów. Ponadto obiekty tworzone (tutaj okna) mogą posiadać wyłącznie konstruktor z modyfikatorem internal – dzięki temu użytkownik biblioteki będzie zmuszony do wykorzystania factory aby stworzyć dany obiekt. Jeśli ktoś jest zainteresowany dokładniejszym opisem wzorca, polecam książkę “Design Patterns, Elements of Reusable Object-Oriented Software”.

Service locator tak naprawdę nie różni się znacząco od factory. Jak sama nazwa wskazuje, zadaniem wzorca jest lokalizacja jakiś usług. W naszym przypadku będą lokalizowane repozytoria na podstawie typu encji:

public class RepositoryLocator
{
   private Dictionary<Type, IRepository> m_Repositories = null;
   public RepositoryLocator()
   {
       m_Repositories = new Dictionary<Type, IRepository>();
       m_Repositories.Add(typeof(Order),new OrderRepository());
       m_Repositories.Add(typeof(Product),new ProductRepository());
   }
   public IRepository GetForBusinessObject<T>(): where T : BusinessObject
   {
       return GetForBusinessObject(typeof(T));
   }
   public IRepository GetForBusinessObject(Type type)
   {
       if (m_Repositories.ContainsKey(type))
           return m_Repositories[type];
       else
           return null;
   }
}

Użytkownik chcąc stworzyć repozytorium dla danego obiektu biznesowego wywołuje:

RepositoryLocator locator = new RepositoryLocator();
IRepository orderRepository=locator.GetForBusinessObject<Order>();
IRepository productRepository=locator.GetForBusinessObject<Product>();

11 thoughts on “Jak tworzyć instancję repozytorium?”

  1. Mala sugestia: przydałaby się tu dodatkowo choćby wzmianka o Dependency Injection. Service Locator nie jest najlepszym sposobem na uzyskiwanie instancji obiektów – a pisanie wlasnego service locatora to pomysl jeszcze mniej udany.

  2. W sumie post miał nie być o DI bo to inna bajka. Wspomniałem tylko, że to jest jedna z możliwości w przypadku factory.
    Co do Service Locator, to co masz na myśli?Użycie IUnityContainer?

  3. Użycie czegokolwiek, co jest już zrobione/gotowe/przetestowane/utrzymywane/nie wymaga pracy… Czy to bedzie Unity, czy Autofac czy Windsor czy Ninject czy StructureMap czy cos jeszcze innego to kwestia drugorzedna.
    No i lepiej jest jednak stosowac DI niż SL, co w przypadku wykorzystania wspomnianych narzedzi jest proste – a napisac to samemu nie jest juz tak latwo.

  4. Ale SL to nie jest DI – tutaj mapujemy Order na OrderRepository(brak wspólnego, bazowego interfejsu).
    Ponadto nawet jak da się jakoś wykorzystać Unity w SL to nadal upierałbym się za wrapperem dla tego. Użytkownik widząc RepositoryLocator od razu domyślna się przeznaczenia klasy. W przypadku UnityContainer, musi to być już specjalnie opisane, że w kontenerze znajduje się dana implementacja.

  5. Moim zdaniem nic nie pobije czytelnosci takiego zapisu:

    public class XXX
    {
    public XXX(IRepository ordersRepository)
    {
    //….
    }
    }

    A czy dostaniemy odpowiedni obiekt z Unity czy z innego kontenera to zadna roznica.
    Nie trzeba nic rejestrowac recznie, mozna zalatwic to odpowiednimi konwencjami.
    A “masową” rejestracje wszystkich IRepository uzyskamy dzieki otwartym typom generycznym i wsparciu dla nich w kazdym porzadnym DI Container.

    Niedlugo bede pisal o tym u siebie na blogu na przykladzie Autofac to moze lepiej wyrażę o co mi chodzi:).

  6. Drobna uwaga.
    W obecnej wersji wydaje mi się, że SL ma złe mapowania.

    m_Repositories.Add(typeof(OrderRepository),new OrderRepository());
    m_Repositories.Add(typeof(ProductRepository),new ProductRepository());
    }

    Czy nie miałeś na myśli?:

    m_Repositories.Add(typeof(Order),new OrderRepository());
    m_Repositories.Add(typeof(Product),new ProductRepository());
    }

  7. @Szymon, tak, dokładnie. Dzięki za uwagę.
    Mapujemy obiekt biznesowy ma repozytorium a nie repozytorium na repozytorium:)

  8. Dodatkowo można dodać ograniczenie where T : BusinessObject. Właśnie zaktualizowałem post 🙂

  9. Hej, w temacie o repozytoriach podałem niewielką zmianę, a teraz chciałbym pokazać, jak to wykorzystać w przypadku service locatora.

    Nie musimy dzięki temu tworzyć ręcznie repozytoriów.

    IRepository to pusty interfejs, dziedziczy po nim (o raz po tympowanym IRepository[[T]]) RepositoryFactory.
    Wszystkie encje dziedziczą po pustym interfejsie IModel.

    public RepositoryLocator(IDataContext context)
    {
    m_Repositories = new Dictionary[[Type, IRepository]]();

    Assembly asm = Assembly.GetCallingAssembly();

    IEnumerable[[Type]] models = (from t in asm.GetTypes()
    where t.GetInterface(“IModel”) != null
    select t
    );

    foreach (Type m in models)
    {
    Type repoFacType = typeof(RepositoryFactory[[]]);
    Type finalType = repoFacType.MakeGenericType(new Type[1]{m});
    object rep = Activator.CreateInstance(finalType, new object[] { context });

    m_Repositories.Add(m, (IRepository)rep);
    }
    }

    Użycie:
    IList[[Employee]] empl =
    rloc.GetRepository().GetByExample(e);

  10. Witam,
    mam mały problemik. Nawiązując do poprzedniego postu,
    tam interfejs IRepository jest generyczny. Tutaj biorąc pod uwagę kod ServiceLocatora, IRepository nie jest generyczny.

    Próbowałem to przeskoczyć i w sumie nie mam już pomysłów.

    Jakieś sugestie ? Bardzo prosiłbym o odpowiedź

Leave a Reply

Your email address will not be published.