Знущання вводить обробку у виробничий код


15

Припускаючи інтерфейс IReader, реалізацію інтерфейсу ReaderImplementation IReader та клас ReaderConsumer, який споживає та обробляє дані з читача.

public interface IReader
{
     object Read()
}

Впровадження

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

Споживач:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

Для тестування ReaderConsumer та обробки я використовую макет IReader. Тож ReaderConsumer стає:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

У цьому рішенні глузування вводить фразу if для виробничого коду, оскільки лише макетний конструктор постачає екземпляри інтерфейсу.

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

В цілому він смердючий, як з цим можна впоратися краще?


16
Зазвичай це не проблема, оскільки конструктор із введеною залежністю був би єдиним конструктором. Чи не може бути ReaderConsumerнезалежним питання ReaderImplementation?
— Кріс Уолерт

Наразі залежність важко буде зняти. Дивлячись на це трохи більше, у мене виникає глибша проблема, ніж просто залежність від ReaderImplemenatation. Оскільки ReaderConsumer - створений під час запуску з фабрики, зберігається впродовж життя програми та приймає зміни від користувачів, це потребує додаткового масажу. Можливо, конфігурація / введення користувача може існувати як об'єкт, і тоді ReaderConsumer і ReaderImplementation можуть бути створені на льоту. Обидві наведені відповіді досить добре вирішують загальний випадок.
— Крістіан пн

3
Так . Це суть TDD: необхідність складати тести спочатку передбачає більш відокремлений дизайн (інакше ви не зможете написати одиничні тести ...). Це допомагає зробити код більш рентабельним, а також розширеним.
— Бакуріу

Хороший спосіб виявити запахи, які можна вирішити за допомогою введення залежності, шукає ключове слово "new". Не випробовуйте своїх залежностей. Введіть їх замість цього.
— Вічний21

Відповіді:


67

Замість ініціалізації читача зі свого методу перемістіть цей рядок

{
    this.reader = new ReaderImplementation(this.location)
}

У конструктор без параметрів за замовчуванням.

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

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


3
оцінка ++ ... окрім точки зору DI, цей конструктор за замовчуванням безумовно запах коду.
— Матьє Гіндон

3
@ Mat'sMug нічого поганого з реалізацією за замовчуванням. Тут не вистачає запаху ланцюга ctor. =;) -
— RubberDuck

О, так, є - якщо у вас немає книги, ця стаття, здається, досить добре описує антидіаграма з ін'єкцією сволота (тільки що прокинулася).
— Матьє Гіндон

3
@ Mat'sMug: Ви інтерпретуєте DI догматично. Якщо вам не потрібно вводити конструктор за замовчуванням, тоді ви цього не робите.
— Роберт Харві

3
У книзі @ Mat'sMug також йдеться про "прийнятні значення за замовчуванням" для залежностей, які є просто чудовими (хоча йдеться про введення властивостей). Скажемо так: двоконструкторний підхід коштує дорожче з точки зору простоти та ремонтопридатності, ніж одноконструкторний підхід, і якщо питання надто спрощене для наочності, вартість не є виправданою, але це може бути в деяких випадках .
— Карл Лет

54

Вам потрібен лише один конструктор:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

у виробничому коді:

var rc = new ReaderConsumer(new ReaderImplementation(0));

у вашому тесті:

var rc = new ReaderConsumer(new MockImplementation(0));

13

Подивіться на введення залежностей та інверсію управління

І Еван, і RubberDuck мають відмінні відповіді. Але я хотів би згадати ще одну область, яку слід вивчити, яка є вприскуванням залежності (DI) та інверсією управління (IoC). Обидва ці підходи переміщують проблему, яку ви відчуваєте, у рамку / бібліотеку, щоб вам не довелося хвилюватися з цього приводу.

Ваш приклад простий і швидко не обходиться, але, неминуче, ви збираєтеся будувати на ньому, і ви закінчитеся з тоннами конструкторів або підпрограм ініціалізації, схожими на:

var foo = новий Foo (новий Bar (новий Baz (), новий Quz ()), новий Foo2 ());

За допомогою DI / IoC ви використовуєте бібліотеку, яка дозволяє вам прописати правила відповідності інтерфейсів реалізаціям, а потім просто скажіть "Дайте мені Foo", і він розробив, як прокласти все це.

Там є дуже багато дружніх контейнерів IoC (як їх називають), і я рекомендую поглянути на них, але, будь ласка, вивчіть, оскільки дуже багато варіантів.

Простим для початку є:

http://www.ninject.org/

Ось список для вивчення:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx


7
І відповіді Евана, і RubberDuck демонструють Д.І. DI / IoC не стосується інструменту чи основи, це архітектура та структурування коду таким чином, щоб управління було перевернуто та ввели залежності. Книга Марка Семана виконує чудову (дивовижну!) Роботу з пояснення того, як DI / IoC є повністю досяжним без фреймворку IoC, а чому і коли IoC-рамка стає хорошою ідеєю (підказка: коли у вас залежність залежностей залежностей .. .) =)
— Матьє Гіндон

Також ... +1, тому що Ninject є приголомшливим, і після повторного читання ваша відповідь здається прекрасною. Перший абзац дещо схожий на відповіді Юана та RubberDuck - це не те, що відповіли на ІП, саме це підштовхнуло мій перший коментар.
— Матьє Гіндон

1
@ Mat'sMug Однозначно зрозумійте свою думку. Я намагався закликати ОП вивчити DI / IoC, які використовували інші афіші (Еван - це Бідний чоловік), але не згадував. Перевага, звичайно, у використанні рамки полягає в тому, що вона занурить користувача у світ DI з великою кількістю конкретних прикладів, які, сподіваємось, допоможуть їм зрозуміти, що вони роблять ... хоча ... не завжди. :-) І, звичайно, мені сподобалася книга Марка Семана. Це один із моїх улюблених.
— Регінальд Блю

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