Серия принципов SOLID, часть 1
«Класс должен делать как можно меньше полезных вещей; то есть он должен нести единую ответственность».
- Санди Мец, Практическое объектно-ориентированное проектирование в Ruby: учебник по Agile.
Давайте рассмотрим пример кода, чтобы проиллюстрировать преимущества следования принципу единой ответственности (SRP). Этот пример взят из приложения командной строки Tic Tac Toe.
Вот возможная реализация класса Board.
Как бы вы определили ответственность этого класса? Это может быть трудный вопрос, чтобы ответить. Смутно, это касается правления, которое кажется сплоченной работой. Но здесь пахнет кодом. Это длинный класс с множеством публичных методов, что может свидетельствовать о нарушении SRP.
Давайте копнем немного глубже и рассмотрим поведение класса Board.
Следующие методы работают с состоянием доски, отвечая на запросы от взаимодействующих объектов:
- get_space(строка, столбец)
- доступно?(ввод)
- комбинации
Метод mark_space также работает с состоянием доски, устанавливая значение данного пробела:
- mark_space(маркер, перемещение)
Метод отображения имеет другой тип работы. Он взаимодействует с классом консоли для отображения отформатированной доски в пользовательском интерфейсе.
Поскольку у Board есть обязанности, связанные как с состоянием доски, так и с отображением доски в пользовательском интерфейсе, у него есть несколько причин для изменения. Например, если вы решили изменить массив значений на другую структуру данных, такую как вложенный массив или хеш, класс необходимо будет изменить. Если вы хотите изменить способ форматирования доски, класс должен измениться.
«…Каждый раз, когда он меняется, есть вероятность сломать все классы, которые от него зависят. Вы увеличиваете вероятность неожиданного сбоя приложения, если зависите от классов, которые делают слишком много».
- Санди Мец, Практическое объектно-ориентированное проектирование в Ruby: учебник по Agile.
Чтобы разделить обязанности, вы можете переместить методы display и formatted_board из класса Board в новый класс Display, который обрабатывает взаимодействие с пользовательским интерфейсом.
Вот возможная реализация класса Display:
Обратите внимание, что Display вызывает board.get_space(row, column), чтобы избежать жесткой зависимости от массива значений Board, что еще больше разделяет их обязанности.
Следование SRP делает приложение более устойчивым к изменениям. Если Board изменит способ хранения значений, это не сломает Display. Если Display изменит формат доски, Board не сломается. Когда у каждого класса есть одна обязанность, реализация этих обязанностей инкапсулируется, и вносить изменения становится легче.