Согласно принципу чистого кода Роберта Мартина, принцип единой ответственности означает, что «у класса должна быть только одна причина для изменения». Давайте попробуем расшифровать это довольно расплывчатое утверждение и посмотрим, как оно помогает нам создавать лучшее объектно-ориентированное программное обеспечение. Если это так.
Однажды я упомянул SRP в своем посте о SOLID , сказав, что он на самом деле не помогает программистам понять старую добрую концепцию «высокой сплоченности» , которая была введена Ларри Константином еще в 1974 году. Теперь давайте рассмотрим это на примере и проанализируем, как мы можем улучшить класс с учетом SRP, и станет ли он более объектно-ориентированным .
Давайте попробуем класс AwsOcket
из jcabi-s3 (я упростил код):
1
2
3
4
5
|
class AwsOcket { boolean exists() { /* ... */ } void read( final OutputStream output) { /* ... */ } void write( final InputStream input) { /* ... */ } } |
Поправьте меня, если я ошибаюсь, но согласно SRP этот класс отвечает за слишком много вещей: 1) проверка существования объекта в AWS S3, 2) чтение его содержимого и 3) изменение его содержимого. Правильно? Это не хороший дизайн, и его нужно изменить.
Чтобы изменить его и сделать его ответственным только за одну вещь, мы должны ввести геттер, который вернет клиента AWS, а затем создаст три новых класса: ExistenceChecker
, ContentReader
и ContentWriter
. Они будут проверять, читать и писать. Теперь, чтобы прочитать содержимое и распечатать его на консоли, я сейчас делаю это:
1
2
3
|
if (ocket.exists()) { ocket.read(System.out); } |
Завтра, если я проведу рефакторинг класса, я буду делать это:
1
2
3
|
if ( new ExistenceChecker(ocket.aws()).exists()) { new ContentReader(ocket.aws()).read(System.out); } |
Помимо того факта, что эти шашки, читатели и писатели на самом деле не классы, а чистые обладатели процедур, использование этого ocket
превращается в кошмар. Мы больше не можем знать, что будет с ним, когда мы его где-нибудь передадим. Мы не можем, например, гарантировать, что исходящий от него контент расшифровывается или декодируется на лету. Мы просто не можем это украсить. Это уже не объект, а держатель клиента AWS, который где-то используется некоторыми другими классами.
Да, теперь он отвечает только за одну вещь: инкапсулирует ссылку на клиента AWS. Это идеальный класс для SRP. Но это больше не объект.
То же самое произойдет с любым классом, если вы в полной мере примените принцип SRP: он станет держателем данных или других объектов с набором сеттеров и геттеров поверх них. Может быть, с одним дополнительным методом в дополнение к этим.
Я хочу сказать, что SRP — неправильная идея.
Хорошая идея — сделать классы маленькими и сплоченными, но сделать их ответственными «за одну вещь» — вводящее в заблуждение упрощение концепции «высокой сплоченности». Это только превращает их в глупых носителей чего-то другого, вместо того, чтобы быть инкапсуляторами и декораторами меньших сущностей, чтобы создавать большие.
В нашей борьбе за эту ложную идею SRP мы теряем гораздо более важный принцип, который на самом деле касается настоящего объектно-ориентированного программирования и мышления: инкапсуляция. Гораздо менее важно, за сколько вещей отвечает объект, чем насколько он защищает объекты, которые он инкапсулирует. Объект-монстр с сотней методов представляет собой гораздо меньшую проблему, чем DTO с пятью парами геттеров и сеттеров! Это связано с тем, что DTO распространяет проблему по всему коду, где мы даже не можем ее найти, в то время как объект-монстр всегда перед нами, и мы всегда можем реорганизовать его в более мелкие части.
На первом месте — инкапсуляция, на втором месте — размер.
Опубликовано на Java Code Geeks с разрешения Егора Бугаенко, партнера нашей программы JCG . Смотрите оригинальную статью здесь: SRP — обман
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |