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

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

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

Чем многократно используемый программный компонент отличается от одноразового? Повторно используемый программный компонент следует принципу единой ответственности и слабо связан со своими зависимостями, в то время как одноразовый компонент нарушает один или оба.

Принцип единой ответственности

Первая причина невозможности повторного использования программного компонента - нарушение принципа единой ответственности. Каждый компонент, независимо от его уровня (метод, класс и т. Д.), Должен иметь единственную причину для изменения. Поведение компонента следует описывать одним предложением, не содержащим союзов «и» или «или».

Давайте посмотрим, почему нарушение принципа единой ответственности убивает повторное использование кода, на этом простом примере:

public class FileParser
{
   public bool Parse(FileInfo file)
   {
      if(file.Extension != ".txt")
      {
         return false;
      }
      var parseResult = Parse(file);    
      
      return true;
   }
   
   //TODO: parsing logic here
}

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

Разработчик не сможет повторно использовать только логику проверки в новом контексте, потому что все обязанности класса нарушителя SRP должны повторно использоваться вместе.

Какие возможности есть у разработчика, когда необходимо повторно использовать только одну ответственность из нескольких, принадлежащих к одному классу? На самом деле их три:

  1. Разработчик может скопировать и вставить логику проверки в новое место.
  2. Разработчик может реализовать логический флаг и установить его извне, чтобы включить или отключить логику проверки.
  3. Ответственность за логику проверки следует выделить в отдельный класс.

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

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

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

Слабая связь

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

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

public class ReportGenerator
{
   public Report Generate()
   {
      var data = HttpClient.GetData("http://projectapi.com/data/");
      
      return GenerateReport(data);
   }
   //TODO: report generation logic here
}

Класс ReportGenerator тесно связан со своей зависимостью, классом HttpClient.

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

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

ReportGenerator должен быть абстрагирован от источника данных. Один из способов сделать это - реорганизовать класс ReportGenerator до чего-то похожего на это:

public class ReportGenerator
{
    private readonly IReportDataProvider _reportDataProvider;
    public ReportGenerator(IReportDataProvider reportDataProvider)
    {
        _reportDataProvider = reportDataProvider;
    }
    public Report Generate()
    {
        var data = _reportDataProvider.GetData();
        return GenerateReport(data);
    }
    
    //TODO: report generation logic here
}

Класс HttpClient должен быть инкапсулирован в реализацию интерфейса IReportDataProvider. Клиенты, которым необходимо повторно использовать класс ReportGenerator, просто должны предоставить свою собственную реализацию интерфейса IReportDataProvider, который может инкапсулировать выборку данных из базы данных или любого другого источника.

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

Заключение

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

Подробнее о ремонтопригодности