Легко ли понять этот дизайн для внедрения зависимостей? - PullRequest
3 голосов
/ 03 августа 2010

Я работаю над проектом с большой существующей кодовой базой, и мне было поручено выполнить некоторую реинжиниринг небольшой его части. Существующая кодовая база не имеет большого количества модульных тестов, но я хотел бы, чтобы по крайней мере часть, над которой я работаю, имела полный набор модульных тестов с хорошим охватом кода.

Поскольку существующая кодовая база не проектировалась с учетом модульного тестирования, мне необходимо внести некоторые структурные изменения, чтобы поддержать изоляцию зависимостей. Я пытаюсь сохранить как можно меньше влияния на существующий код, но у меня есть немного возможностей для внесения изменений в другие модули. Я хотел бы получить отзыв о том, имеют ли смысл внесенные мной изменения или есть ли лучший способ решения проблемы. Я сделал следующие изменения:

  1. Я извлек интерфейсы для всех нетривиальных классов, от которых зависит мой класс «координатор». Другие разработчики неохотно разрешили это, с небольшим ворчанием.

  2. Я изменил класс координатора для ссылки только на интерфейсы для его работы.

  3. Я не мог использовать структуру DI, но мне пришлось внедрить большое количество зависимостей (в том числе дополнительное создание экземпляров зависимостей как часть операции класса), поэтому я изменил класс координатора, чтобы он выглядел следующим образом (domain- конкретные имена упрощены):

.

public sealed class Coordinator
{
    private IFactory _factory;

    public Coordinator(int param)
        : this(param, new StandardFactory())
    {            
    }

    public Coordinator(int param, IFactory factory)
    {
        _factory = factory;
        //Factory used here and elsewhere...
    }

    public interface IFactory
    {
        IClassA BuildClassA();

        IClassB BuildClassB(string param);

        IClassC BuildClassC();
    }

    private sealed class StandardFactory : IFactory
    {
        public IClassA BuildClassA()
        {
            return new ClassA();   
        }

        public IClassB BuildClassB(string param)
        {
            return new ClassB(param);
        }

        public IClassC BuildClassC()
        {
            return new ClassC();
        }
    }
}

Это позволяет вводить заглушенную фабрику, чтобы возвращать проверенные зависимости для модульного тестирования. Я обеспокоен тем, что это также означает, что любой пользователь этого класса может предоставить собственную фабрику, чтобы изменить способ работы класса, что на самом деле не является намерением. Этот конструктор предназначен исключительно для модульного тестирования, но имеет побочный эффект, заключающийся в том, что была предусмотрена дополнительная расширяемость. Я обычно не вижу этот шаблон в других классах, которые я использую, что заставляет меня задуматься, не слишком ли я усложняю и есть ли лучший способ добиться требуемой изоляции.


ПРИМЕЧАНИЕ: что-то странное происходит с форматированием кода, поэтому я прошу прощения за плохое форматирование выше. Не уверен, почему он не позволяет мне правильно отформатировать его.

Ответы [ 2 ]

3 голосов
/ 03 августа 2010

Предлагаемый проект представляет собой вариант Конструктор Injeciton , иногда известный как Bastard Injection . В ситуации «с нуля» я бы назвал Bastard Injection анти-паттерном, но это хороший компромисс в такой ситуации, как та, которую вы описываете.

На самом деле вас не должно беспокоить подразумеваемая расширяемость класса Coordinator. Если все сделано правильно, тестируемость - это расширяемость , поэтому в идеале внедренный IFactory должен представлять собой надлежащую концепцию предметной области, которая обеспечивает изменчивость. Я понимаю, что это может быть трудно / невозможно достичь одним махом, когда вы выполняете рефакторинг из унаследованного кода, но это направление, в котором должна двигаться ваша база кода.

В любом случае, если ваши коллеги уже завладеют извлечением интерфейсов *1013*, они, скорее всего, не будут интерпретировать параметр конструктора IFactory как точку расширяемости.

Удачи.

2 голосов
/ 03 августа 2010

Возможно, вы захотите создать конструктор, который принимает внутренний код IFactory, что помешает другим пользователям вне сборки "расширить" ваш Coodinator. Затем используйте атрибут InternalsVisibleTo, чтобы открыть его только для ваших юнит-тестов.

...