Найти в Дзене
ZDG

SOLID-ный код для солидных господ. Часть 4

Предыдущая часть:

Мы дошли до буквы I, которая обозначает Interface Segregation, или разграничение интерфейсов.

Сразу скажу, что данный принцип самый скучный и как бы это сказать, самоочевидный. Такое ощущение, что его придумали и вставили в SOLID просто потому что не хватало одной красивой буквы.

Он звучит так:

Клиенты не должны зависеть от интерфейсов, которые они не используют.

Что за клиенты? Таким словом автор принципа – Роберт Мартин – назвал все классы, которые реализуют тот или иной интерфейс.

А что значит зависеть?

Предположим, для описания поведения животных сделан интерфейс Animal, в котором должны быть реализованы методы run(), fly(), swim(), scream() и т.д.

Далее мы начинаем описывать классы некоторых животных – они клиенты этого интерфейса. Например, кошка Cat. Она умеет бегать: run() и кричать: scream(). Например, утка Duck. Она умеет летать: fly(), плавать: swim() и кричать: scream(). Например, рыба Fish. Она умеет плавать: swim().

Если класс реализует какой-то интерфейс, он обязан реализовать все методы этого интерфейса. Но рыба не может бегать и кричать, утка не может бегать, а кошка не может (точнее, боится) плавать.

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

Это и приводит к проблемам. Например, при добавлении каждого нового объекта-клиента интерфейса нужно для всех его неподдерживамых методов писать затычки. А если мы решили добавить в интерфейс Animal ещё одну способность, например dig(), то тем самым мы ломаем все уже имеющиеся классы клиентов. В них теперь надо или реализовать метод dig(), или добавить ещё одну пустышку. А что, если таких классов были уже сотни?

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

Например, функции полёта можно вынести в FlyingAnimal, функции плавания в SwimmingAnimal, а функции крика в ScreamingAnimal. Тогда класс Duck будет реализовывать все эти три интерфейса:

class Duck implements FlyingAnimal, SwimmingAnimal, ScreamingAnimal

а класс Fish только один:

class Fish implements SwimmingAnimal

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

По сути все эти принципы есть отражение одной и той же методологии под разными углами.

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

Читайте дальше: