Принцип единственной ответственности Роберт Мартин (Дядюшка Боб)


Вот один из наиболее фундаментальных принципов качественного проектирования:

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

Этот принцип иначе известен как принцип единственной ответственности, или SRP (single responsibility principle). Если коротко, он гласит, что причина изменения любой подсистемы, класса и даже функции должна быть ровно одна. Классический пример — класс с методами для работы с бизнес-правилами, отчетами и базами данных:

public class Employee {

public Money calculatePay()…

public String reportHours()… 

public void save()…

}

Кое-кто из программистов считает, что сочетание этих трех функций в одном классе вполне уместно. В конце концов, классы и должны собирать вместе функции, работающие с одними и теми же переменными. Однако проблема в том, что все три функции изменяются по совершенно разным причинам. Функция calculatePay (рассчитать зарплату) изменяется вместе с бизнес-правилами расчета заплаты. Функция reportHours (отчитаться о часах) изменяется, когда требуется другой формат отчета. Функция save (сохранить) изменяется, когда администратор базы данных меняет схему базы данных. В совокупности эти три причины делают Employee очень неустойчивым классом. Он будет меняться по каждой из этих причин. И что еще важнее, эти изменения затронут любые классы, которые зависят от Employee.

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

public class Employee {

  public Money calculatePay()…

}

public class EmployeeReporter {

  public String reportHours(Employee e)…

}

public class EmployeeRepository {

  public void save(Employee e)…

}

Каждый класс можно поместить в отдельный компонент. Или, выражаясь точнее, все классы, создающие отчеты, поместить в компонент отчетов. Все классы, связанные с базами данных, можно поместить в компонент хранилища. А все бизнес-правила поместить в компонент бизнес-правил.

Внимательный читатель заметит, что в приведенном решении остаются зависимости. Другие классы по-прежнему зависят от Employee. Поэтому если Employee изменится, вполне возможно, что эти классы придется заново скомпилировать и развернуть. Таким образом, Employee невозможно модифицировать, а потом развернуть независимо. Однако другие классы можно модифицировать и независимо развернуть. Никакая их модификация не требует перекомпиляции и повторного развертывания остальных классов. Даже Employee можно независимо развернуть, если тщательно применить принцип инверсии зависимости (DIP, dependency inversion principle), но это уже тема для другой книги.[27]

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

Загрузка...