Принцип единой ответственности: все ли открытые методы в классе должны использовать все зависимости класса? - PullRequest
12 голосов
/ 13 июля 2009

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

internal class SomeClass
{
    IDependency _someDependency;

    ...


    internal string SomeFunctionality_MakesUseofIDependency()
    {
    ...
    }
}

И затем я хочу добавить связанную функциональность, но использует другую зависимость для достижения своей цели . Возможно что-то вроде следующего:

internal class SomeClass
{
    IDependency _someDependency;

    IDependency2 _someDependency2;

    ...


    internal string SomeFunctionality_MakesUseofIDependency()
    {
    ...
    }

    internal string OtherFunctionality_MakesUseOfIDependency2()
    {
    ...
    }
}

Когда я пишу модульные тесты для этой новой функциональности (или обновляю модульные тесты, которые у меня есть для существующей функциональности), я обнаруживаю, что создаю новый экземпляр SomeClass (SUT), одновременно передавая null для зависимости, которую я получаю Мне не нужна особая функциональность, которую я хочу проверить.

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

Мой вопрос: должны ли все зависимости класса использоваться всеми его функциональными возможностями, т. Е. Если разные части функциональности используют разные зависимости, это признак того, что они, вероятно, должны жить в отдельных классах?

Ответы [ 6 ]

11 голосов
/ 14 июля 2009

Когда каждый метод экземпляра касается каждой переменной экземпляра, класс максимально связен. Когда ни один метод экземпляра не разделяет переменную экземпляра с любым другим, класс является минимально связным. Хотя это правда, что мы хотим, чтобы сплоченность была высокой, также верно и правило 80-20. Чтобы получить последнее небольшое увеличение сплоченности, может потребоваться много усилий.

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

3 голосов
/ 14 июля 2009

Поддерживает ли SomeClass внутреннее состояние или это просто "сборка" различных частей функциональности? Можете ли вы переписать это так:

internal class SomeClass
{
    ...


    internal string SomeFunctionality(IDependency _someDependency)
    {
    ...
    }

    internal string OtherFunctionality(IDependency2 _someDependency2)
    {
    ...
    }
}

В этом случае вы не можете нарушать SRP, если SomeFunctionality и OtherFunctionality каким-то образом (функционально) связаны, что не очевидно при использовании заполнителей.

Кроме того, у вас есть возможность выбирать зависимость для использования на клиенте, а не во время создания / DI. Возможно, некоторые тесты, определяющие варианты использования этих методов, помогут прояснить ситуацию: если вы можете написать содержательный тестовый пример, в котором оба метода вызываются для одного и того же объекта, вы не нарушаете SRP.

Что касается паттерна «Фасад», я видел, как он слишком часто сходил с ума, чтобы полюбить его, знаете ли, когда вы заканчиваете классом более 50 методов ... Вопрос: зачем он вам нужен? По соображениям эффективности а-ля EJB со старым таймером?

0 голосов
/ 14 июля 2009

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

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

Проблема будет в том, будет ли этот класс продолжать расти и в каком направлении. Два метода не будут иметь значения, но если это повторяется с более чем 3, вам нужно решить, хотите ли вы объявить его как фасад / адаптер или вам нужно создать дочерние классы для вариантов.

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

0 голосов
/ 13 июля 2009

Похоже, перегрузка для меня. Вы пытаетесь что-то сделать, и есть два способа сделать это, так или иначе. На уровне SomeClass у меня была бы одна зависимость для выполнения работы, а затем этот единственный зависимый класс поддерживал бы два (или более) способа сделать одно и то же, скорее всего с взаимоисключающими входными параметрами. Другими словами, у меня был бы тот же код, что и у SomeClass, но вместо этого я определил бы его как SomeWork и не включал бы никакого другого не связанного кода.

НТН

0 голосов
/ 13 июля 2009

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

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

Вы должны найти хороший баланс, который работает для вас (и всей вашей команды).

0 голосов
/ 13 июля 2009

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

...