Подтвердить что ты не робот

Принцип единой ответственности: все ли общедоступные методы в классе должны использовать все зависимости класса?

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

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 для зависимости, которую я надел "Мне нужно определенное количество функциональных возможностей, которые я ищу для тестирования.

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

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

4b9b3361

Ответ 1

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

internal class SomeClass
{
    ...


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

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

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

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

Что касается шаблона Facade, я видел, что он слишком много раз становился одичалым, чтобы понравиться, вы знаете, когда вы закончите с классом 50+ методов... Вопрос в том, зачем вам это нужно? По соображениям эффективности à la old-timer EJB?

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

НТН

Ответ 6

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

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

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

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