Что вы ищете в зависимости, чтобы определить, является ли она зависимой зависимостью? - PullRequest
6 голосов
/ 10 сентября 2010

Мне трудно понять, когда нужно вводить зависимость. Давайте просто поработаем на простом примере из моего проекта:

class CompanyDetailProvider : ICompanyDetailProvider {
    private readonly FilePathProvider provider;
    public CompanyDetailProvider(FilePathProvider provider) {
        this.provider = provider;
    }

    public IEnumerable<CompanyDetail> GetCompanyDetailsForDate(DateTime date) {
        string path = this.provider.GetCompanyDetailFilePathForDate(date);
        var factory = new DataReaderFactory();
        Func<IDataReader> sourceProvider = () => factory.CreateReader(
            DataFileType.FlatFile, 
            path
        );
        var hydrator = new Hydrator<CompanyDetail>(sourceProvider);
        return hydrator;
    }
}

(Не качество продукции!)

ICompanyDetailProvider отвечает за предоставление экземпляров CompanyDetail s для потребителей. Конкретная реализация CompanyDetailProvider делает это путем гидратации экземпляров CompanyDetail из файла с использованием Hydrator<T>, который использует отражение для заполнения экземпляров T, полученных из IDataReader. Ясно, что CompanyDetailProvider зависит от DataReaderFactory (который возвращает экземпляры OleDbDataReader с указанием пути к файлу) и Hydrator. Нужно ли вводить эти зависимости? Правильно ли вводить FilePathProvider? Какие качества я проверяю, чтобы решить, следует ли их вводить?

Ответы [ 3 ]

2 голосов
/ 12 сентября 2010

Я оцениваю точки использования зависимостей через линзу намерения / механизма: этот код четко сообщает свое намерение, или мне нужно извлечь это из кучи деталей реализации?

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

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

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

public interface IEntityReader
{
    IEnumerable<T> ReadEntities<T>(string path);
}

Затем перепишите исходный класс, используя этот интерфейс для выявления намерений:

public sealed class CompanyDetailProvider : ICompanyDetailProvider
{
    private readonly IFilePathProvider _filePathProvider;
    private readonly IEntityReader _entityReader;

    public CompanyDetailProvider(IFilePathProvider filePathProvider, IEntityReader entityReader)
    {
        _filePathProvider = filePathProvider;
        _entityReader = entityReader;
    }

    public IEnumerable<CompanyDetail> GetCompanyDetailsForDate(DateTime date)
    {
        var path = _filePathProvider.GetCompanyDetailsFilePathForDate(date);

        return _entityReader.ReadEntities<CompanyDetail>(path);
    }
}

Теперь вы можете помещать в песочницу кровавые детали, которые становятся достаточно сплоченными в отдельности:

public sealed class EntityReader : IEntityReader
{
    private readonly IDataReaderFactory _dataReaderFactory;

    public EntityReader(IDataReaderFactory dataReaderFactory)
    {
        _dataReaderFactory = dataReaderFactory;
    }

    public IEnumerable<T> ReadEntities<T>(string path)
    {
        Func<IDataReader> sourceProvider =
            () => _dataReaderFactory.CreateReader(DataFileType.FlatFile, path);

        return new Hydrator<T>(sourceProvider);
    }
}

Как показано в этом примере, я думаю, что вы должны абстрагироваться от фабрики устройства чтения данных и напрямую создать экземпляр гидратора. Различие состоит в том, что EntityReader использует фабрику чтения данных, в то время как только создает гидратор. На самом деле это вообще не зависит от экземпляра; вместо этого он служит гидраторным заводом.

1 голос
/ 10 сентября 2010

Я склонен придерживаться более либеральной стороны внедрения зависимостей, поэтому я определенно хотел бы внедрить оба IDataReader, чтобы избавиться от нового DataFactoryReader и Hydrator. Он поддерживает более слабую связь, что, конечно, облегчает обслуживание.

Другим преимуществом, которое легко получить сразу же, является лучшая тестируемость. Вы можете создавать макеты ваших IDataReader и Hydrator, чтобы изолировать ваши модульные тесты только от метода GetCompanyDetailsForDate и не беспокоиться о том, что происходит внутри устройства чтения данных и гидратора.

1 голос
/ 10 сентября 2010

Как определить, должен ли класс использовать внедрение зависимостей


Требуется ли для этого класса внешняя зависимость?

Если да, введите.

Если нет, не зависит.

Чтобы ответить "Правильно ли вводить FilePathProvider?" да, это правильно.

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

Как правило, об этом следует думать каждый раз, когда вы звоните новому оператору. В большинстве случаев требуется реорганизовать все операции использования нового оператора, когда ему приходится иметь дело с любым классом, кроме объекта передачи данных. Когда класс является внутренним по отношению к местоположению использования, тогда новый оператор может подойти, если он уменьшает сложность, например, новый DataReaderFactory (), однако это, по-видимому, очень хороший кандидат для внедрения в конструктор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...