SOLID

На одном из стримов меня попросили рассказать о SOLID, на сколько это важно, нужно и стоил ли вообще заморачиваться. 

SOLID – это сокращение 5 основных принципов для пяти основных принципов объектно-ориентированного программирования и на мой взгляд их желательно знать и понимать. Но как я часто люблю говорить – ненужно тупо клон что-то делать, потому что это тупо. 

Это как паттерны. Многие начинают изучать паттерны с синглтона, потому что. . . А даже не знаю почему он везде первый, его нельзя назвать самым простым. Он простой, но не скажу, что на много проще остальных. Я не скажу, что он самый полезный, хотя и бесполезным не является. Но от того, что вы знаете синглтон – тупо его везде пытаться применять, потому что это тупо. Нужно понимать, где его использовать и где от этого паттерны будет преимущество. 

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

 

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

Single responsibility или Принцип единственной ответственности. У класса должно быть только одно назначение и это на мой взгляд самый важный принцип, который НИКОГДА нельзя нарушать. Каждый метод должен выполнять только одну маленькую задачу. Каждый класс должен выполнять задачу более общую, но это все же одна задача. 

И вот тут возникает вопрос – как определить эту одну задачу? 

Хорошим признаком будут данные. Один из основных признаков ООП – инкапсуляция, которая представляет собой объединение данных и методов, которые работают с этими данными. То есть смотрим на данные и делим их на группы. Я часто ООП объясняю на зданиях, домах, сараях. Допустим, что у нас есть квартира и вы хотите описать ее в виде классов. В квартире скорей всего есть дверь и лампочка. Просто логически думаем – эти данные хоть как-то связаны между собой? Нет? Тогда никогда, просто никогда метод открытия двери и метод включения света не должны быть в одном классе. 

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

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

Научи дурака молиться он и лоб расшибет. Если посмотреть на глазок у двери, то они бывают разные и может быть их выделить в отдельный класс? Да, если у вас глазки реально бывают разные. А на нет и суда нет. Что-то я слишком много говорю пословицами, но в них мудрости тоже очень много. 

Просто тут нужно знать меру. Даже если глазок можно отделить в отдельный класс нужно просто ответить для себя на вопрос – а будет ли от этого выгода? Будет ли реально несколько глазков. Если хотя бы на один из вопросов ответ будет “Да”, то делить нужно. Чем мельче класс, тем больше нужно задавать себе вопрос – а будет ли выгода? В интернете при описании этого принципа очень часто вижу примеры, в которых классы дробят до одного метода. 

Over engineering приводит к тому, что код становится не красивее, а сложнее в использовании и поддержке. 

 

Принцип открытости/закрытости

Open–closed или принцип открытости/закрытости гласит, что классы должны быть открыты для расширения, но закрыты для модификации. Красиво звучит, но в реальности мало кто этому принципу следует. 

Тут снова научи дурака молиться он и лоб расшибет – почему-то некоторые считают, что нельзя даже баги фиксить, а нужно вместо этого создавать новые методы. Мол если кто-то уже написал какой-то хак, который лечит костылем баг, то мы сломаем этот код. Хаки ломать нужно и делать это надо беспощадно. Баги фиксить необходимо и для этого новая реализация CalcV2 не нужна. 

Нельзя менять смысл методов или классов. Если метод должен звонить в звонок, то он никогда в жизни не может поменяться на включение света. Если вы вдруг установили умный дом, который при входе в квартиру автоматически включает свет, то нужно создать новый метод – позвонить_и_включить_свет, а не изменять логику уже существующего. 

Нельзя менять поведение. Можно создать новый метод с новым поведением и удалить старый, если он так сильно не нужен. Никогда не замечали в языках или фреймворках такие вещи как deprecated (не рекомендуемый к использованию)? Это как раз потому, что авторы и разработчики просто не меняют поведение методов, а создают новые и помечают старые, как не рекомендуемые к использованию и со временем удаляют. 

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

 

Принцип подстановки Лисков

Liskov substitution principle или Принцип подстановки Лисков – скопирую определение из википедии: объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. См. также контрактное программирование.

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

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

Хотя я допускаю нарушение этого принципа, когда это действительно нужно, но я сам все же не нарушаю его. 

 

Принцип разделения интерфейса

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

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

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

 

Dependency inversion principle или Принцип инверсии зависимостей 

Ну это наверно сейчас не вызывает вопросов. Ну по крайней мере не должно. Сейчас уже стало правилом, что классы должны получать зависимости. 

Сюда же относят и необходимость использовать зависимость на абстракциях, то есть на интерфейсы. Класс, который зависит от конкретного класса жить будет, но вот если понадобиться заменить зависимость, то тут возникают проблемы. Если же использовать инверсию с интерфейсами, тот можно использовать фреймворки, которые автоматизируют за нас инициализацию Dependency Injection реализаций. 

Так что тут я снова согласен на 100% с этим принципом и стараюсь его не нарушать. 



Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание

Комментарии

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

Добавить Комментарий

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

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

Пишите мне