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


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.