Внедрение зависимостей против статических методов

21

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

Представьте себе что-то вроде следующего, полностью составленное для примера.

public string GetStringPart(string input)
{ 
   //Some input validation which is removed for clarity

   if(input.Length > 5)
        return input.Substring(0,1);

   if(input.Substring(0,1) == "B")
        return input.Substring(0,3);

   return string.empty;
}

Функция, которая имеет некоторую логику, основанную на строковом вводе, добавляется в проект с использованием DI и имеет на своем месте контейнер DI. Вы бы добавили этот новый класс с интерфейсом и добавили бы его в случае необходимости, или вы сделали бы его статическим классом? Каковы плюсы и минусы каждого? Почему вы (или нет) хотели бы использовать это с использованием инжектора конструктора, а не просто когда к нему обращались когда угодно.

Джеймс
источник
1
@Ewan Для вспомогательных методов, которые не используются для абстракции. Пример: Apache FileUtils.
Вальфрат
3
@Ewan: статические методы без побочных эффектов - лучшие методы, потому что они просты для понимания и просты в тестировании.
JacquesB
1
но они делают невозможным юнит-тестирование вещей, которые зависят от них
Ewan
3
@ Иван Эх, не совсем. Math.Max ​​() плох, потому что он статический? Если вы тестируете статический метод, и он работает, вы можете безопасно использовать его на других ваших методах без проблем. Если статический сбой, его тест поймает его.
Т. Сар - Восстановить Монику
1
если бы «статический» гарантировал отсутствие побочных эффектов, я мог бы увидеть аргумент. но это не так.
Эван

Ответы:

25

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

Нет необходимости абстрагировать функции без зависимостей, это излишне.

Если это становится более сложным, то, возможно, передача интерфейса в конструктор или метод оправдана. Но я бы не пошел по этому пути, если бы у меня не было сложной GetStringPartлогики, основанной на местоположении и т. Д.

Джон Рейнор
источник
2
Это правильный ответ! На плохой грузо-культовый ответ был принят.
JacquesB
3
что функция не имеет зависимостей, не в этом дело. проблема в том, что другие вещи зависят от функции и тесно связаны с ней
Эван
2
Что, если функция изменится через 6 месяцев и не будет такой чистой, но она уже используется во многих местах. Он не расширяемый как статический и не может быть изолирован от использования модульных тестов классов. Если есть хоть какой-то небольшой шанс измениться, я бы пошел на инъекцию. Тем не менее, я открыт для прослушивания противоположных аргументов, в этом суть этого поста. Каковы недостатки этого впрыскиваемого и положительного статического?
Джеймс
1
Вообще говоря, люди, обсуждающие внедрение зависимостей на этом веб-сайте, не нуждаются в внедрении зависимостей. Отсюда уклон к ненужной сложности.
Фрэнк Хилман
2
@James Если функция становится менее чистой, вы делаете что-то не так, и функция, вероятно, не была четко определена с самого начала. Вычисление площади квадрата не должно внезапно потребовать вызова базы данных или развернуть визуальное обновление для пользовательского интерфейса. Можете ли вы представить себе сценарий, в котором метод «подстроки» вдруг становится нечистым?
Т. Сар - Восстановить Монику
12

Вот почему

class DOSClient {
    OrderParser orderParser;
    string orderCode;

    DOSClient(OrderParser orderParser, string ordercode) { 
        this.orderParser = orderParser; 
        this.ordercode = ordercode;
    }
    void DisplayOrderCode() {
        Console.Write( "Prefix: " + orderParser.GetStringPart(ordercode) ); 
        ...
    }
}

class GUIClient {
    OrderParser orderParser;
    string orderCode;
    GUI gui;

    GUIClient(OrderParser orderParser, string ordercode, GUI gui) { 
        this.orderParser = orderParser; 
        this.ordercode = ordercode;
        this.gui = gui;
    }

    void DisplayOrderCode() {
        gui.Prefix( orderParser.GetStringPart(ordercode) ); 
        ...
    }
}

 

class OrderParserUS : IOrderParser {

    public string GetStringPart(string input)
    { 
        //Some input validation which is removed for clarity

        if(input.Length > 5)
            return input.Substring(0,1);

        if(input.Substring(0,1) == "B")
            return input.Substring(0,3);

        return string.empty;
    }
}

class OrderParserEU : IOrderParser {

    public string GetStringPart(string input)
    { 
        //Some input validation which is removed for clarity

        if(input.Length > 6)
            return input.Substring(0,1);

        if(input.Substring(0,1) == "#")
            return input.Substring(0,3);

        return string.empty;
    }
}

Если бы вы использовали статический метод, не было бы никакого способа изменить поведение, GetStringPartне разрушив старое поведение или не загрязнив его условной логикой. Это правда, что статики - это замаскированные злые глобалы, но тот факт, что они отключают полиморфизм, является моей главной жалобой на них. Статические методы не являются первоклассными в языках ООП. Предоставляя методу объект для жизни, даже если он не имеет состояния, мы делаем метод переносимым. Его поведение может передаваться как значение переменной.

Здесь я представил систему, которая должна вести себя немного иначе, когда она развернута в Европе, чем при развертывании в США. Вместо того, чтобы заставить любую систему содержать код, необходимый только другой, мы можем изменить поведение, контролируя, какой объект анализа заказов вводится в клиентах. Это позволяет нам сдерживать распространение деталей региона. Это также позволяет легко добавлять OrderParserCanada без необходимости трогать существующие парсеры.

Если это ничего не значит для вас, то для этого нет веских аргументов.

Кстати, GetStringPartэто ужасное имя.

candied_orange
источник
* Придерживайтесь скобок в стиле кода * Я предполагаю, что вы программист на Java, пытаясь написать код на C #? Полагаю, чтобы узнать один, я полагаю. :)
Нил
Вопрос не только в языке, а в дизайне. Моя проблема со статическими методами заключается в том, что они препятствуют изоляции кода для тестирования. Разработчик, с которым я разговаривал, считает, что я слишком сильно полагаюсь на DI, и иногда методы расширения / статические методы актуальны. Я думаю, возможно, в редких случаях, но не в качестве общего назначения. Текущие обсуждения, которые я чувствовал, были хорошими для дальнейшего обсуждения. Я также согласен с вашим аргументом в пользу полиморфизма.
Джеймс
Тестирование - это причина, но есть и другие. Я не люблю винить в этом много испытаний, потому что это только начинает критиковать людей. Простой факт заключается в том, что процедурным программистам не понравится, если вы не будете программировать процедурно.
candied_orange
Не могли бы вы просто сказать static getStringPartEU()? Ваш пример имеет смысл только в том случае, если в этом классе есть другие методы, которые также требуют специального подхода ЕС, и они должны рассматриваться как единое целое.
Роберт Харви
2
Вы определенно спорите в пользу ненужной сложности. Все может иметь все больше и больше кода, добавленного к нему. Вещи, которые должны измениться, должны легко модифицироваться, но вы не должны пытаться предсказать, что должно измениться в будущем. Статические поля могут быть идентичны глобальным переменным, но статические методы могут быть чистыми, наилучшим возможным типом метода.
Фрэнк Хилман