kontekst otoczenia a wstrzykiwanie konstruktora


9

Mam wiele podstawowych klas, które wymagają ISessionContext bazy danych, ILogManager dla dziennika i IService używane do komunikacji z innymi usługami. Chcę użyć wstrzykiwania zależności dla tej klasy używanej przez wszystkie podstawowe klasy.

Mam dwie możliwe implementacje. Klasa podstawowa, która akceptuje IAmbientContext ze wszystkimi trzema klasami lub wstrzykuje dla wszystkich klas trzy klasy.

public interface ISessionContext 
{
    ...
}

public class MySessionContext: ISessionContext 
{
    ...
}

public interface ILogManager 
{

}

public class MyLogManager: ILogManager 
{
    ...
}

public interface IService 
{
    ...
}

public class MyService: IService
{
    ...
}

Pierwsze rozwiązanie:

public class AmbientContext
{
    private ISessionContext sessionContext;
    private ILogManager logManager;
    private IService service;

    public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
    {
        this.sessionContext = sessionContext;
        this.logManager = logManager;
        this.service = service;
    }
}


public class MyCoreClass(AmbientContext ambientContext)
{
    ...
}

drugie rozwiązanie (bez kontekstu otoczenia)

public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
    ...
}

Jakie jest najlepsze rozwiązanie w tym przypadku?


Co jest IServiceużywane do komunikowania się z innymi usługami? Jeśli IServicereprezentuje niejasną zależność od innych usług, brzmi to jak lokalizator usług i nie powinien istnieć. Twoja klasa powinna zależeć od interfejsów, które wyraźnie opisują, co zrobi z nimi ich konsument. Żadna klasa nigdy nie potrzebuje usługi, aby zapewnić dostęp do usługi. Klasa potrzebuje zależności, która robi coś konkretnego, czego potrzebuje klasa.
Scott Hannen,

Odpowiedzi:


4

„Najlepsze” jest tutaj zbyt subiektywne. Jak to często bywa z takimi decyzjami, jest to kompromis między dwoma równie ważnymi sposobami osiągnięcia czegoś.

Jeśli utworzysz AmbientContexti wstrzyknąć że w wielu klasach, jesteś potencjalnie zapewniając więcej informacji na każdy z nich, niż potrzebują (np klasa Foomoże używać ISessionContext, ale jest opowiadana o ILogManageri ISessionzbyt).

Jeśli przekażesz je za pomocą parametru, powiesz każdej klasie tylko te rzeczy, o których powinien wiedzieć. Ale liczba parametrów może szybko wzrosnąć i może się okazać, że masz o wiele za dużo konstruktorów i metod z wieloma wysoce powtarzanymi parametrami, które można uprościć za pomocą klasy kontekstu.

Jest to więc przypadek zrównoważenia dwóch i wybrania odpowiedniego dla twoich okoliczności. Jeśli masz tylko jedną klasę i trzy parametry, osobiście nie zawracałbym sobie głowy AmbientContext. Dla mnie punktem krytycznym byłyby prawdopodobnie cztery parametry. Ale to czysta opinia. Twój punkt zwrotny prawdopodobnie będzie inny niż mój, więc idź z tym, co wydaje ci się odpowiednie.


4

Terminologia zawarta w pytaniu tak naprawdę nie pasuje do przykładowego kodu. Ambient ContextJest wzór używany do przechwycenia zależność od jakiejkolwiek klasy w dowolnym module tak proste, jak to możliwe, bez zanieczyszczających każda klasa przyjąć interfejs zależność, ale zachowując ideę odwrócenie sterowania. Takie zależności są zwykle poświęcone logowaniu, bezpieczeństwu, zarządzaniu sesjami, transakcjom, buforowaniu, audytowi, tak jak w przypadku wszelkich przekrojowych problemów w tej aplikacji. To jakoś irytujące dodaj ILogging, ISecurity, ITimeProviderdo konstruktorów i przez większość czasu nie wszystkie zajęcia muszą w tym samym czasie, więc rozumiem Twoje potrzeby.

Co zrobić, jeśli czas życia ISessioninstancji jest inny niż ten ILogger? Może instancja ISession powinna zostać utworzona dla każdego żądania, a ILogger raz. Zatem posiadanie tych wszystkich zależności zarządzanych przez jeden obiekt, który nie jest samym kontenerem, nie wydaje się właściwym wyborem ze względu na te wszystkie problemy z zarządzaniem i lokalizacją przez całe życie i innymi opisanymi w tym wątku.

IAmbientContextW pytaniu nie rozwiązuje problemu nie zanieczyszczających każdy konstruktor. Nadal musisz go użyć w podpisie konstruktora, jasne, że tym razem tylko raz.

Zatem najłatwiejszym sposobem jest NIE używanie iniekcji konstruktora lub jakiegokolwiek innego mechanizmu iniekcji do radzenia sobie z zależnościami przekrojowymi, ale za pomocą wywołania statycznego . W rzeczywistości często widzimy ten wzorzec, wdrażany przez samą platformę. Sprawdź Thread.CurrentPrincipal, która jest statyczną właściwością zwracającą implementację IPrincipalinterfejsu. Można go również ustawić, więc możesz zmienić implementację, jeśli chcesz, więc nie jesteś z nią związany.

MyCore wygląda teraz jak

public class MyCoreClass
{
    public void BusinessFeature(string data)
    {
        LoggerContext.Current.Log(data);

        _repository.SaveProcessedData();

        SessionContext.Current.SetData(data);
        ...etc
    }
}

Ten wzorzec i możliwe implementacje zostały szczegółowo opisane przez Marka Seemanna w tym artykule . Mogą istnieć implementacje oparte na samym kontenerze IoC, z którego korzystasz.

Chcesz uniknąć AmbientContext.Current.Logger, AmbientContext.Current.Sessionz tych samych powodów, jak opisano powyżej.

Ale masz inne opcje rozwiązania tego problemu: użyj dekoratorów, dynamicznego przechwytywania, jeśli twój kontener ma taką możliwość lub AOP. Kontekst otoczenia powinien być ostatecznością, ponieważ klienci kryją w nim swoje zależności. Nadal używałbym Ambient Context, jeśli interfejs naprawdę naśladuje mój impuls do używania zależności statycznej, takiej jak DateTime.Nowlub, ConfigurationManager.AppSettingsa ta potrzeba rośnie dość często. Ale ostatecznie zastrzyk konstruktora może nie być tak złym pomysłem na uzyskanie tych wszechobecnych zależności.


3

Unikałbym AmbientContext.

Po pierwsze, jeśli klasa zależy od AmbientContexttego, to tak naprawdę nie wiesz, co ona robi. Musisz spojrzeć na wykorzystanie tej zależności, aby dowiedzieć się, z której zagnieżdżonej zależności korzysta. Nie można także spojrzeć na liczbę zależności i stwierdzić, czy klasa robi zbyt wiele, ponieważ jedna z tych zależności może w rzeczywistości reprezentować kilka zależności zagnieżdżonych.

Po drugie, jeśli używasz go, aby uniknąć wielu zależności od konstruktorów, takie podejście zachęci innych programistów (w tym ciebie) do dodania nowych członków do tej klasy kontekstu otoczenia. Wtedy pierwszy problem jest złożony.

Po trzecie, wykpiwanie zależności AmbientContextjest trudniejsze, ponieważ w każdym przypadku musisz dowiedzieć się, czy wyśmiewać wszystkich jej członków, czy tylko tych, których potrzebujesz, a następnie ustawić próbkę, która zwróci te symulacje (lub dubluje testy). To sprawia, że Twoja jednostka trudniej testuje, aby pisać, czytać i utrzymywać.

Po czwarte, brakuje mu spójności i narusza zasadę jednolitej odpowiedzialności. Dlatego ma nazwę „AmbientContext”, ponieważ robi wiele niepowiązanych rzeczy i nie ma sposobu, aby nazwać ją zgodnie z tym, co robi.

I prawdopodobnie narusza to zasadę segregacji interfejsu, wprowadzając członków interfejsu do klas, które ich nie potrzebują.


2

Drugi (bez opakowania interfejsu)

O ile nie zachodzi jakaś interakcja między różnymi usługami, która wymaga enkapsulacji w klasie pośredniej, komplikuje twój kod i ogranicza elastyczność, gdy wprowadzasz „interfejs interfejsów”

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.