Stopniowo przenieś bazę kodu do kontenera wstrzykiwania zależności

9

Mam dużą bazę kodową z wieloma singletonami „anty-wzorcowymi”, klasami użyteczności z metodami statycznymi i klasami tworzącymi własne zależności za pomocą newsłowa kluczowego. To bardzo utrudnia testowanie kodu.

Chcę stopniowo migrować kod do kontenera wstrzykiwania zależności (w moim przypadku jest to Guice, ponieważ jest to GWTprojekt). Z mojego zrozumienia zastrzyku uzależnienia, to wszystko albo nic. Wszystkimi klasami zarządza Spring / Guice lub żadnymi. Ponieważ baza kodów jest duża, nie mogę przekształcić kodu w ciągu nocy. Potrzebuję więc sposobu, aby robić to stopniowo.

Problem polega na tym, że kiedy zaczynam od klasy, która wymaga wstrzyknięcia do innych klas, nie mogę użyć prostej @Injectw tych klasach, ponieważ te klasy nie są jeszcze zarządzane przez kontener. To tworzy długi łańcuch aż do „najlepszych” klas, które nigdzie nie są wstrzykiwane.

Jedyny sposób, jaki widzę, to na razie udostępnienie Injectorglobalnego kontekstu / aplikacji za pośrednictwem singletona, aby inne klasy mogły uzyskać z niego zarządzaną fasolę. Jest to jednak sprzeczne z ważną ideą nieujawniania composition rootaplikacji.

Innym podejściem byłoby podejście oddolne: aby rozpocząć od klas „wysokiego poziomu”, należy uwzględnić je w pojemniku wstrzykiwania zależności i powoli przechodzić do klas „mniejszych”. Ale potem muszę długo czekać, ponieważ mogę przetestować te mniejsze klasy, które wciąż zależą od globałów / statyki.

W jaki sposób można osiągnąć taką stopniową migrację?

PS Pytanie Stopniowe podejście do wstrzykiwania zależności jest podobne w tytule, ale nie odpowiada na moje pytanie.

damluar
źródło
1
Czy chcesz przejść od posiadania klas sprzężonych do korzystania z kontenera do wstrzykiwania zależności? Najpierw musisz dokonać refaktoryzacji, aby rozdzielić klasy, używając interfejsów, a nawet pomyśleć o połączeniu się z jakimkolwiek szkieletem lub narzędziem wstrzykiwania zależności.
Tulains Córdova
Słusznie. Mam więc pewną klasę użyteczności A, która jest używana w 30 innych klasach (jedna z nich to klasa B), co czyni je niestabilnymi. Pomyślałem, aby uczynić klasę A zależnością konstruktora klasy B. Muszę zmienić wszystkie wywołania konstruktorów i przekazać A do środka. Ale skąd mam to wziąć?
damluar
Jeśli B jest najmniejszą jednostką, do której możesz zastosować DI, aby rozpocząć, to nie widzę żadnej szkody w konstruowaniu A, gdziekolwiek na razie budujesz B - pod warunkiem, że A nie ma zależności. Gdy piłka zacznie się toczyć, możesz wrócić i zmienić ją.
Andy Hunt
@damluar Fabryka na początek?
Tulains Córdova
1
@damluar klasa nie staje się niestabilna tylko dlatego, że opiera się na klasie użyteczności. Oznacza to po prostu, że twoja jednostka jest nieco większa niż chcesz. Jeśli możesz samodzielnie przetestować swoją klasę użyteczności, możesz bez obaw używać jej w testach dla wszystkich pozostałych 30 klas. Przeczytaj blog Martina Fowlersa na temat testów jednostkowych.
gbjbaanb

Odpowiedzi:

2

Niestety C#to mój język wybór, mogę czytać Javaale prawdopodobnie rżnąć składnię próbuje napisać ... Te same koncepcje zastosowania pomiędzy C#i Javachoć, więc mam nadzieję, że to pokaże kroki w jaki sposób można stopniowo przenieść swoją bazę kodu, aby być bardziej testowalny.

Dany:

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    public void DoStuff()
    {
        Bar bar = new Bar();
        bar.DoSomethingElse();
    }

}

public class Bar
{
    public void DoSomethingElse();
}

można łatwo refaktoryzować do korzystania z DI, bez użycia kontenera MKOl - i potencjalnie możesz nawet rozbić go na kilka etapów:

(potencjalnie) Krok pierwszy - weź zależności, ale bez zmiany kodu wywołującego (UI):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = new Foo();
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // Leaving this constructor for step one, 
    // so that calling code can stay as is without impact
    public Foo()
    {
        _iBar = new Bar();
    }

    // simply because we now have a constructor that take's in the implementation of the IBar dependency, 
    // Foo can be much more easily tested.
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

public interface IBar
{
    void DoSomethingElse();
}

public class Bar
{
    public void DoSomethingElse();
}

Refaktor 2 (lub pierwszy refaktor w przypadku natychmiastowego wdrożenia kontenera IOC i zmiany kodu wywoławczego):

public class MyUI
{
    public void SomeMethod()
    {
        Foo foo = null // use your IOC container to resolve the dependency
        foo.DoStuff();
    }
}

public class Foo
{

    private IBar _iBar;

    // note we have now dropped the "default constructor" - this is now a breaking change as far as the UI is concerned.
    // You can either do this all at once (do only step 2) or in a gradual manner (step 1, then step 2)

    // Only entry into class - requires passing in of class dependencies (IBar)
    public Foo(IBar iBar)
    {
        _iBar = iBar;
    }

    public void DoStuff()
    {
        _iBar.DoSomethingElse();
    }

}

Krok 2 mógłby technicznie być wykonany sam - ale (potencjalnie) byłby o wiele większy - zależny od tego, ile klas obecnie „odnawia” funkcjonalność, którą chcesz DI.

Zastanów się nad przejściem z kroku 1 -> kroku 2 - Fooniezależnie od tego możesz tworzyć testy jednostkowe Bar. Natomiast przed refaktorem kroku 1 nie było łatwo tego dokonać bez faktycznej implementacji obu klas. Wykonanie kroku 1 -> krok 2 (zamiast kroku 2 natychmiast) pozwoliłoby na mniejsze zmiany w miarę upływu czasu, a Ty już masz zestaw testowy, aby lepiej upewnić się, że Twój reaktor pracował bez konsekwencji.

Kritner
źródło
0

Koncepcja jest taka sama, niezależnie od tego, czy używasz Java, PHP, czy nawet C #. W tym filmie na YouTube Gemma Anible dość dobrze poradziła sobie z tym pytaniem:

https://www.youtube.com/watch?v=Jccq_Ti8Lck (PHP, przepraszam!)

Zamieniasz niestabilny kod na „fasada” (z powodu braku lepszego terminu), który wywołuje nowy, testowalny kod. Następnie stopniowo możesz zastąpić starsze połączenia wprowadzonymi usługami. Zrobiłem to w przeszłości i działa całkiem dobrze.

Kevin G.
źródło