Пишем код по S.O.L.I.D.

Совсем недавно мы говорили о том, что такое чистый код в контексте Android-разработки. Теперь расскажем, как писать код в соответствии с SOLID — принципами проектирования хорошего кода.

1. SRP — Принцип единой ответственности

В данном случае каждый класс должен отвечать за что-нибудь одно. Для изменения класса не должно быть больше одной причины. Да, вы можете добавлять в класс всё, что пожелаете, но делать этого не нужно. А вот разделять большие классы на более мелкие и избегать God Class — хорошая практика.

Для примера представим, что у нас есть RecyclerView.Adapter с логикой внутри onBindViewHolder:

Так как RecyclerView.Adapter имеет логику внутри onBindViewHolder, налицо противоречие принципу единой ответственности.

2. OCP — Принцип открытости-закрытости

Принято, чтобы программные объекты были открыты для расширения, однако закрыты для модификации. Это значит, что когда вы пишете класс A, а потом ваши коллеги по команде желают внести изменения в его функцию, они могут это сделать без проблем, расширив класс A и не внося при этом изменений.

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

3. LSP — принцип подстановки Лисков

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

Это значит, что подкласс должен переопределять методы родительского класса, не нарушающие функциональность родительского класса. Допустим, вы создаете интерфейсный класс с прослушивателем onClick(), а потом используете прослушиватель в MyActivity и назначаете ему всплывающее действие в момент вызова onClick().

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

Согласно этому принципу, ни один клиент не должен зависеть от неиспользуемых им методов. Допустим, вы хотите создать класс A с последующей его реализацией в другом классе B. Не надо для этого переопределять внутри класса B все методы класса A.

Как обычно, не обойтись без примера. Представьте, что внутри следующего кода нам надо выполнить SearchView.OnQueryTextListener() и только метод onQuerySubmit().

Чтобы этого добиться, можно просто создать обратный вызов и класс, распространяемый на SearchView.OnQueryTextListener().

А вот реализация:

Если у нас Kotlin, можем применить функцию-расширение:

А теперь магия:

5. DIP — Принцип инверсии зависимостей

Он зависит от абстракций и не зависит от конкрементов. Также этот принцип определяется следующими пунктами: 1. Высокоуровневые модули не должны зависеть от низкоуровневых. Оба модуля должны зависеть от абстракций. 2. Абстракции не должны зависеть от деталей, а детали должны зависеть от абстракций.

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

В качестве простого примера можно привести шаблон MVP. Если у вас есть объект интерфейсов, помогающий взаимодействовать с конкретными классами, то это значит, что классам пользовательского интерфейса (Activity / Fragment) не надо знать фактическую реализацию методов в Presenter. Но если существуют какие-нибудь изменения внутри, то классы пользовательского интерфейса не должны знать об этих изменениях.

Лучше всего рассматривать этот принцип на примере:

Таким образом, мы создаём интерфейс, абстрагирующий реализацию presenter, при этом наш класс представления сохраняет ссылку на PresenterInterface.

По материалам статьи «Understanding Clean Code in Android».