🔗 SOLID Principles


SRP — Single Responsibility Principle

Принцип единственной ответственности гласит:

  • Класс должен иметь одну и только одну причину для изменения.

При первом знакомстве с этим принципом может показаться странным, что сначала говорится об ответственности, а потом — о причине для изменения. Но если задуматься, это не так странно. Рассмотрим, как принцип мог бы выглядеть в реальной жизни. 

Представьте, что у вас есть мультиварка. Она умеет готовить рис, тушить мясо, варить суп и так далее. Если она сломается, вам придётся отнести её в ремонт или купить новую. Если бы у вас были отдельные приборы для каждой функции, вы могли бы заменить только тот, который сломался, а остальные продолжили бы работать. Так же и в программировании: класс должен иметь только одну причину для изменения.

Рассмотрим конкретный пример класса, который используется для отправки подтверждения регистрации нового пользователя на адрес его электронной почты. У класса есть некоторые зависимости, такие как шаблонизатор для рендеринга тела сообщения, сервис для перевода темы сообщения и почтовый клиент для отправки сообщения, — все они внедряются в класс по интерфейсам, но об этом позже. Также есть некие сущности User и Message, описывающие характеристики пользователя и логику формирования сообщения.

class ConfirmationMailMailer:  
   def __init__(self, templating: Any, translator: Any, mailer: Any):  
       self.templating = templating  
       self. Translator = translator  
       self. Mailer = mailer  
  
   def send_to(self, user: 'User') -> None:  
       message = self.create_message_for(user)  
       self.send_message(message)  
  
   def create_message_for(self, user: 'User') -> 'Message':  
       subject = self.translator.translate('Confirm your mail address')  
       body = self.templating.render('confirm.html.tpl', {'Code': user.get_сode()})  
       message = Message(subject, body)  
       return message  
  
   def send_message(self, message: 'Message') -> None:  
       self.mailer.send(message)

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

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

Нарушение принципа единственной ответственности

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

  • у класса много переменных экземпляра;
  • в классе много открытых методов;
  • каждый метод класса использует разные переменные экземпляра;
  • конкретные задачи делегируются закрытым методам.

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

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

class ConfirmationMailMailer:  
   def __init__(self, confirmation_mail_mactory: Any, mailer: Any):  
       self.confirmation_mail_factory = confirmation_mail_mactory  
       self.mailer = mailer  
  
   def sendTo(self, user: 'User') -> None:  
       message = self.create_message_for(user)  
       self.send_message(message)  
  
   def create_message_for(self, user: 'User') -> 'Message':  
       return self.confirmation_mail_factory.create_message_for(user)  
  
   def send_message(self, message: 'Message') -> None:  
       self.mailer.send(message)  
  
  
class ConfirmationMailFactory:  
   def __init__(self, templating: Any, translator: Any):  
       self.templating = templating  
       self.translator = translator  
  
   def create_message_for(self, user: 'User') -> Message:  
       """  
       Create an instance of Message based on the given user.  
       """  
       message = ...  
       return message

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


📂 SOLID | Последнее изменение: 26.04.2024 09:48