Поступово перемістіть кодову базу до контейнера для ін'єкцій залежностей


9

У мене є велика база коду з великою кількістю "антидіапазонних" синглтонів, утилітні класи зі статичними методами та класи, що створюють власні залежності за допомогою newключового слова. Це робить код дуже важким для перевірки.

Я хочу поступово перенести код на контейнер для ін'єкцій залежності (в моєму випадку це Guice, тому що це GWTпроект). З мого розуміння введення залежності, це все або нічого. Або всіма класами керує Spring / Guice, або жоден. Оскільки база коду велика, я не можу перетворити код протягом ночі. Тому мені потрібен спосіб зробити це поступово.

Проблема полягає в тому, що коли я починаю з класу, який потрібно вводити в інші класи, я не можу використовувати простий @Injectу цих класах, оскільки цими класами ще не керує контейнер. Таким чином, це створює довгий ланцюг аж до "верхніх" класів, які ніде не вводяться.

Єдиний спосіб, який я бачу, - це зробити Injectorконтекст / додаток глобально доступним на даний момент через сингл, щоб інші класи могли отримати керовані боби з нього. Але це суперечить важливій ідеї не розкривати composition rootдодаток.

Іншим підходом буде «знизу вгору»: почати з класів «високого рівня», включити їх у контейнер для ін'єкцій залежності та повільно перейти до «менших» класів. Але тоді мені доведеться довго чекати, оскільки я можу перевірити ті менші класи, які все ще залежать від глобальної / статичної.

Яким був би спосіб досягти такої поступової міграції?

PS Питання Поступові підходи до введення залежності залежать від назви, але це не відповідає на моє запитання.


1
Ви хочете перейти від складних зв'язаних класів до використання контейнера для ін'єкцій залежностей? Спочатку потрібно зробити деякий рефакторинг для роз'єднання класів, використовуючи інтерфейси, перш ніж навіть думати про одруження з будь-якою рамкою або інструментом введення залежності.
Тулен Кордова

Справедливо. Тож у мене є корисний клас A, який використовується в 30 інших класах (один з них - клас В), що робить їх нестабільними. Я думав зробити клас A конструктором залежним від класу B. Я повинен змінити всі виклики на конструктори і пройти A всередині. Але звідки мені це взяти?
damluar

Якщо B - найменша одиниця, до якої можна застосувати DI для того, щоб почати, то я не бачу ніякої шкоди в побудові A, де б ви не будували B на даний момент - за умови, що A не має залежностей. Після того, як кулька котиться, ви можете повернутися і повернути її назад.
Енді Хант

@damluar Фабрика для початку?
Тулен Кордова

1
@damluar клас не стає нестабільним лише тому, що він покладається на клас корисності. Це просто означає, що ваш пристрій трохи більше, ніж ви хочете. Якщо ви можете перевірити свій клас корисності самостійно, то ви можете без перешкод використовувати його в тестах для всіх інших 30 класів. Читайте блог Мартіна Фаулерса про тестування одиниць.
gbjbaanb

Відповіді:


2

До жаль C#моя мова за вибором, я можу читати , Javaале, ймовірно , обробляти синтаксис намагається писати ... Ті ж принципи застосовні між C#і , Javaхоча, так що сподіваюся , це буде показувати кроки , як ви могли б поступово перемістити базу коду , щоб бути більш перевіряється.

Подано:

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();
}

можна легко відновити використання DI, без використання контейнера МОК - і ви потенційно навіть можете розбити його на кілька етапів:

(потенційний) Крок перший - прийміть залежності, але не змінюйте код виклику (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();
}

Рефактор 2 (або перший рефактор, якщо реалізувати контейнер МОК та негайно змінити код виклику):

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();
    }

}

Крок 2 технічно можна зробити самостійно - але потенційно це буде набагато більше роботи - залежно від того, скільки класів наразі «оновлює» функціонал, який ви шукаєте для DI.

Подумайте про перехід до кроку 1 -> крок 2 - ви зможете створити одиничні тести для Foo, незалежно від Bar. Тоді як до етапу 1 рефактор не був легко виконаний без використання реальної реалізації обох класів. Виконання кроку 1 -> крок 2 (а не крок 2 негайно) дозволить з часом змінитись менше, і ви вже матимете початок тестового збруї, щоб краще забезпечити роботу рефактора без наслідків.


0

Концепція така ж, як ви використовуєте Java, PHP або навіть C #. Це питання досить добре вирішує Gemma Anible у цьому відео на YouTube:

https://www.youtube.com/watch?v=Jccq_Ti8Lck (PHP, вибачте!)

Ви замінюєте нестійкий код на "фасад" (за відсутності кращого терміна), який викликає новий, перевіряється код. Потім поступово ви можете замінити старі дзвінки на введені послуги. Я робив це раніше і це працює досить добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.